I have an application using Spring security to control the access to its endpoints.
There are two endpoints /aaa and /bbb secured as:
<security:http pattern="/aaa/**">
<security:anonymous enabled="false" />
<security:intercept-url pattern="/aaa/**" access="isAuthenticated()" />
<security:intercept-url pattern="/aaa/**" access="#oauth2.hasScope('aaa_scope')" />
</security:http>
<security:http pattern="/bbb/**">
<security:anonymous enabled="false" />
<security:intercept-url pattern="/bbb/**" access="isAuthenticated()" />
<security:intercept-url pattern="/bbb/**" access="#oauth2.hasScope('bbb_scope')" />
</security:http>
I need to move /bbb under /aaa, i.e. to make it into /aaa/bbb while maintaining the original security checks
for the "bbb" part (now relocated)
and for the "aaa" part except for the "bbb" part parked under it.
How do I express this in Spring security XML?
What would a descriptor incantation look like?
====================
I tried the following naive combination
<security:http pattern="/aaa/bbb/**">
<security:anonymous enabled="false" />
<security:intercept-url pattern="/aaa/bbb/**" access="isAuthenticated()" />
<security:intercept-url pattern="/aaa/bbb/**" access="#oauth2.hasScope('bbb_scope')" />
</security:http>
<security:http pattern="/aaa/**">
<security:anonymous enabled="false" />
<security:intercept-url pattern="/aaa/**" access="isAuthenticated()" />
<security:intercept-url pattern="/aaa/**" access="#oauth2.hasScope('aaa_scope')" />
</security:http>
but empirically observed that it allows the holder of aaa_scope (not having bbb_scope) to access /aaa/bbb.
Apparently failing an access via first descriptor causes the request not to be aborted right there, but going on try the remaining descriptors.
Thanks for advice.
App security scenario:
Spring MVC app running 3 instances on PaaS
App is split in to 2 security domains. Managed w/ 2 DispatchServlets located at /app and /kmlservice
App must use https on all pages except for any paths within /kmlservice
App must use custom login page to access all paths within /app
App must use persistent remember-me scheme with LDAP auth
When we converted to https, we are getting infinite redirect loops when attempting to navigate to the /app/login page or any other page. In fact even unprotected pages infinitely reroute to themselves.
Here is an example of the redirect logs we are seeing on attempting to navigate to /app/login:
stdout.log: DEBUG: org.springframework.security.web.access.channel.ChannelProcessingFilter - Request: FilterInvocation: URL: /app/login; ConfigAttributes: [REQUIRES_SECURE_CHANNEL]
stdout.log: DEBUG: org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint - Redirecting to: https:/some_url.com/app/login
stdout.log: DEBUG: org.springframework.security.web.DefaultRedirectStrategy - Redirecting to 'https:/some_url.com/app/login'
stdout.log: DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/app/login'; against '/kmlservice/**'
stdout.log: DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/app/login'; against '/resources/**'
stdout.log: DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/app/login'; against '/app/**'
stdout.log: DEBUG: org.springframework.security.web.FilterChainProxy - /app/login at position 1 of 12 in additional filter chain; firing Filter: 'ChannelProcessingFilter'
stdout.log: DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/app/login'; against '/app/logout'
stdout.log: DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/app/login'; against '/app/accessdenied'
stdout.log: DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/app/login'; against '/app/useful_path'
stdout.log: DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/app/login'; against '/app/help'
stdout.log: DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/app/login'; against '/app/login'
stdout.log: DEBUG: org.springframework.security.web.access.channel.ChannelProcessingFilter - Request: FilterInvocation: URL: /app/login; ConfigAttributes: [REQUIRES_SECURE_CHANNEL]
stdout.log: DEBUG: org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint - Redirecting to: https:/some_url.com/app/login
stdout.log: DEBUG: org.springframework.security.web.DefaultRedirectStrategy - Redirecting to 'https:/some_url.com/app/login'
Here's what the config looks like:
<http pattern="/kmlservice/**" use-expressions="true" auto-config="true">
<intercept-url pattern="/**" access="isAuthenticated()" />
<http-basic />
</http>
<http pattern="/resources/**" security="none" />
<http pattern="/app/**" use-expressions="true">
<form-login login-page="/app/login"
authentication-failure-url="/app/accessdenied" default-target-url="/app" />
<intercept-url pattern="/app/logout" access="permitAll"
requires-channel="https" />
<intercept-url pattern="/app/accessdenied" access="permitAll"
requires-channel="https" />
<intercept-url pattern="/app/useful_path"
access="hasRole('ROLE_HAS_ACCESS')" requires-channel="https" />
<intercept-url pattern="/app/help" access="permitAll"
requires-channel="https" />
<intercept-url pattern="/app/login" access="IS_AUTHENTICATED_ANONYMOUSLY"
requires-channel="https" />
<intercept-url pattern="/app/**" access="isAuthenticated()"
requires-channel="https" />
<access-denied-handler error-page="/403" />
<logout logout-success-url="/app/logout" delete-cookies="JSESSIONID" />
<remember-me user-service-ref="userDetailsService"
data-source-ref="dataSource" />
</http>
I have tried to remove
<intercept-url pattern="/app/**" access="isAuthenticated()" requires-channel="https" />
which seemed to make no difference
Is there any other useful config I could provide?
Thanks.
If https terminates at your router, as is often the case with PaaS configurations, then your servlet container needs some way to work out whether the incoming request was actually secure or not. Spring Security uses the standard servlet API method isSecure to decide whether it needs to redirect or not. I'd guess that in your case, the servlet container can't tell whether the external request to the router was made over HTTPS or not.
Tomcat, for example, can be configured with the RemoteIpValve to check for particular headers and set the request properties accordingly. I don't know whether you have any control over this, but you there's an equivalent filter which you can use instead. Of course this also requires that you know how your PaaS is setup and whether it forwards headers like X-Forwarded-Proto to your app at all.
It is going in indefinite loop because URL /app/login is secured as it is marked with IS_AUTHENTICATED_ANONYMOUSLY.
Change the access value to permitAll
<intercept-url pattern="/app/login" access="permitAll" requires-channel="https" />
I want to put security for all the URL's except the login screen URL in spring security,
but I don't want to use session management.
Please help me out in this issue.
my security context file is below
<security:http pattern="/" security="none" />
<security:http auto-config="false" use-expressions="true" create-session="stateless" access-denied-page="/" entry-point-ref="authenticationEntryPoint" >
<security:intercept-url pattern="/**" access="isAuthenticated()" />
<security:intercept-url pattern="/logout" access="permitAll" />
<security:intercept-url pattern="/logout.jsp" access="permitAll" />
<security:logout logout-url="/j_spring_security_logout" />
<security:custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER"/>
</security:http>
The ordering of your intercept-url tags is wrong. Quote from the reference docs:
You can use multiple elements to define different access requirements for different sets of URLs, but they will be evaluated in the order listed and the first match will be used. So you must put the most specific matches at the top.
Move the intercept-url with the universal match pattern to the bottom of the list.
my application compiles to ROOT.war which basically means I do not have a context root other than /. There are pages which are to be secured; however there are URLs which do not need it; for example my http://localhost:8080/ gives the home page of this application; and there are similar pages like about us, contact us, etc where security is not need. So I add this to the configuration
<security:intercept-url pattern="/" access="permitAll" />
<security:intercept-url pattern="/resources/**" access="permitAll" />
<security:intercept-url pattern="/register/confirm" access="isAuthenticated()" />
<security:intercept-url pattern="/register/accept" access="isAuthenticated()" />
<security:intercept-url pattern="/shopper/**" access="isAuthenticated()" />
but this just says that users are allowed to access these URL "without authentication"if you access these URLs the security filters are all applied, as seen below in the debug logs
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 1 of 12 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - No HttpSession currently exists
DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: null. A new one will be created.
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 3 of 12 in additional filter chain; firing Filter: 'LogoutFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 4 of 12 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 5 of 12 in additional filter chain; firing Filter: 'OpenIDAuthenticationFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 6 of 12 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 7 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 8 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG: org.springframework.security.web.authentication.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - / at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/'; against '/'
DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /; Attributes: [permitAll]
DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
DEBUG: org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter#2b06c17b, returned: 1
DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Authorization successful
DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - RunAsManager did not change Authentication object
DEBUG: org.springframework.security.web.FilterChainProxy - / reached end of additional filter chain; proceeding with original chain
DEBUG: org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'appServlet' processing GET request for [/
]
When I try using this configuration (in sequence mentioned below):
<!-- No Security required for the ROOT Context -->
<security:http pattern="/**" security="none" />
<!-- Apply secyrity for shopper URLs -->
<security:http auto-config="true" use-expressions="true" access-denied-page="/denied">
<security:intercept-url pattern="/" access="permitAll" />
<security:intercept-url pattern="/resources/**" access="permitAll" />
<security:intercept-url pattern="/register/confirm" access="isAuthenticated()" />
<security:intercept-url pattern="/register/accept" access="isAuthenticated()" />
<security:intercept-url pattern="/shopper/**" access="isAuthenticated()" /
....
</security:http>
<security:http pattern="/resources/**" security="none" />
It breaks down giving the error
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/auth/login'; against '/'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/auth/login'; against '/resources/**'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/auth/login'; against '/register/confirm'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/auth/login'; against '/register/accept'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/auth/login'; against '/shopper/**'
DEBUG: org.springframework.security.config.http.DefaultFilterChainValidator - No access attributes defined for login page URL
INFO : org.springframework.beans.factory.support.DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory#5bebacc8: defining beans [placeholderConfig,dataSource,entityManagerFactory,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,transactionManager,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0,registrationService,shopperService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,org.springframework.security.filterChains,org.springframework.security.filterChainProxy,org.springframework.security.web.PortMapperImpl#0,org.springframework.security.config.authentication.AuthenticationManagerFactoryBean#0,org.springframework.security.authentication.ProviderManager#0,org.springframework.security.web.context.HttpSessionSecurityContextRepository#0,org.springframework.security.core.session.SessionRegistryImpl#0,org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy#0,org.springframework.security.web.savedrequest.HttpSessionRequestCache#0,org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler#0,org.springframework.security.access.vote.AffirmativeBased#0,org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0,org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator#0,org.springframework.security.authentication.AnonymousAuthenticationProvider#0,org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint#0,org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#0,org.springframework.security.openid.OpenIDAuthenticationFilter#0,org.springframework.security.openid.OpenIDAuthenticationProvider#0,org.springframework.security.userDetailsServiceFactory,org.springframework.security.web.DefaultSecurityFilterChain#0,org.springframework.security.web.DefaultSecurityFilterChain#1,org.springframework.security.authentication.dao.DaoAuthenticationProvider#0,org.springframework.security.authentication.DefaultAuthenticationEventPublisher#0,org.springframework.security.authenticationManager,passwordEncoder,registrationAwareUserDetailsService,registrationAwareAuthSuccessHandler,org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor#0]; root of factory hierarchy
INFO : org.hibernate.impl.SessionFactoryImpl - closing
ERROR: org.springframework.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChainProxy': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: A universal match pattern ('/**') is defined before other patterns in the filter chain, causing them to be ignored. Please check the ordering in your <security:http> namespace or FilterChainProxy bean configuration
I cannot really understand the reason behind this. Do I have to implement my own request pattern matcher?
Solution
<beans:bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy" >
<beans:constructor-arg>
<beans:list>
<security:filter-chain pattern="/resources/**"
filters="none" />
<security:filter-chain pattern="/aboutus"
filters="none" />
<security:filter-chain pattern="/contactus"
filters="none" />
<security:filter-chain pattern="/news"
filters="none" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
security="none" with pattern /** captures all URLs, so no other rule could be applied. What's why you receive error in second example.
But it's possible to define different filter-chains for different URL-patterns. I don't have example of this with new syntax, but here is example with old syntax (order of filter-chains is important):
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/dwr/**" filters="securityContextPersistenceFilter,securityContextHolderAwareRequestFilter,rememberMeAuthenticationFilter,anonymousAuthenticationFilter" />
<sec:filter-chain pattern="/**" filters="channelProcessingFilter,securityContextPersistenceFilter,logoutFilter,authenticationFilter,securityContextHolderAwareRequestFilter,rememberMeAuthenticationFilter,anonymousAuthenticationFilter,sessionManagementFilter,exceptionTranslationFilter,filterSecurityInterceptor,switchUserProcessingFilter" />
</sec:filter-chain-map>
</bean>
Here's the syntax I have finally chosen to stick with; for it makes the XML a lot easier to read and comprehend
<!-- Non secure URLs -->
<security:http pattern="/" security='none' />
<security:http pattern="/home" security='none' />
<security:http pattern="/aboutus" security='none' />
...
...
<security:http pattern="/resources/**" security='none' />
<security:http pattern="/favicon.ico" security='none' />
<!-- URLs under security config -->
<security:http auto-config="true" use-expressions="true" pattern="/admin/**" access-denied-page="/denied">
<security:intercept-url pattern="/admin/**" access="ROLE_ADMIN" requires-channel="https" />
</security:http>
The default URL for my web app is http://localhost:8080/Icd/
I want to display my custom login page which is /index.jsp.
However , when I configure the spring security to do so , I am getting too many redirects problem . Below the code present in the security.xml file .
Let me know if I am missing something .
<security:http auto-config="true" >
<security:intercept-url pattern="/" access="ROLE_ANONYMOUS" />
<security:intercept-url pattern="/*" access="ROLE_USER" />
<security:form-login login-page="/index.jsp" />
</security:http>
<security:authentication-provider>
<security:user-service>
<security:user name="david" password="david" authorities="ROLE_USER,ROLE_ADMIN" />
<security:user name="alex" password="alex" authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
When you put
<security:intercept-url pattern="/*" access="ROLE_USER" />
you're saying that every page requires ROLE_USER to be accessed (which includes the login page itself)
This (untested) may do the trick:
<security:intercept-url pattern="/index.jsp" access="permitAll"/>
<security:intercept-url pattern="/*" access="ROLE_USER" />
Try specifying your configuration like the following:
<security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
<security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
<security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
<security:form-login
login-page="/krams/auth/login"
authentication-failure-url="/krams/auth/login?error=true"
default-target-url="/krams/main/common"/>
<security:logout
invalidate-session="true"
logout-success-url="/krams/auth/login"
logout-url="/krams/auth/logout"/>
</security:http>
This one uses a custom login page. For more info, you can check the full application at http://krams915.blogspot.com/2010/12/spring-security-3-mvc-using-simple-user.html