I am trying to modify the underlying application using only Spring Security to use CAS to enable SSO. But I get a redirect loop somewhere, and I cannot find out what is wrong. I made two more mock applications and there are no problems with CAS because they work. I am using the Java configuration instead of XML, taken from here . I tried the XML configuration as an example, but I still get the same result. My guess would be for a problem with authenticationManager that cannot find the user from Spring Security. The log at least points to an anonymous user and throws an AccessDeniedException. But it works for two other mock applications that have a similar configuration (I even tried to copy it, but the error still occurs). I tried to fix this for several days without success, so any help is appreciated. I am using Tomcat 8, Spring 4.2 and Ja-sig CAS 4.0.0, all on Windows 8.
My WebSecurityConfig:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired @Resource(name="CASuserDetailsService") private AuthenticationUserDetailsService userDetailsService; @Bean public ServiceProperties serviceProperties() { ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setService("https://localhost:8443/i9t-YM/j_spring_cas_security_check"); serviceProperties.setSendRenew(false); return serviceProperties; } @Bean public CasAuthenticationProvider casAuthenticationProvider() { CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider(); casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService()); casAuthenticationProvider.setServiceProperties(serviceProperties()); casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator()); casAuthenticationProvider.setKey("some_id_for_this_cas_prov"); return casAuthenticationProvider; } @Bean public AuthenticationUserDetailsService authenticationUserDetailsService() { return userDetailsService; } @Bean public Cas20ServiceTicketValidator cas20ServiceTicketValidator() { return new Cas20ServiceTicketValidator("https://localhost:8443/cas"); } @Bean public CasAuthenticationFilter casAuthenticationFilter() throws Exception { CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter(); casAuthenticationFilter.setAuthenticationManager(authenticationManager()); casAuthenticationFilter.setFilterProcessesUrl("https://localhost:8443/i9t-YM/j_spring_cas_security_check"); return casAuthenticationFilter; } @Bean public CasAuthenticationEntryPoint casAuthenticationEntryPoint() { CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint(); casAuthenticationEntryPoint.setLoginUrl("https://localhost:8443/cas/login"); casAuthenticationEntryPoint.setServiceProperties(serviceProperties()); return casAuthenticationEntryPoint; } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(casAuthenticationProvider());
TestCasAuthenticationUserDetailsService (note that I tried to get it to work in several ways ...)
@Service("CASuserDetailsService") public class TestCasAuthenticationUserDetailsService implements AuthenticationUserDetailsService, UserDetailsService { @Autowired private DataSource dataSource; @Override public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException { List<GrantedAuthority> authorities = new ArrayList<>(); System.out.println(token.getName()); System.out.println(token.toString()); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new User(token.getName(), token.getName(), authorities); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List<GrantedAuthority> authorities = new ArrayList<>(); System.out.println(username); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new User(username, username, authorities); } }
My web.xml:
<servlet> <servlet-name>springServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-context.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <error-page> <exception-type>java.lang.Exception</exception-type> <location>/erro.html</location> </error-page> <error-page> <error-code>404</error-code> <location>/404.html</location> </error-page> <filter> <filter-name>CAS-SSO-Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS-SSO-Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <security-constraint> <web-resource-collection> <web-resource-name>i9t-YM</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint>
ApplicationContext-security.xml from false applications:
<security:http auto-config="true" entry-point-ref="casEntryPoint"> <security:intercept-url pattern="/*" access="ROLE_USER" /> <security:custom-filter position="CAS_FILTER" ref="casFilter" /> </security:http> <security:user-service id="userService"> <security:user name="joe" authorities="ROLE_USER" /> </security:user-service> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="casAuthenticationProvider" /> </security:authentication-manager> <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> <property name="service" value="https://localhost:8443/casTest/j_spring_cas_security_check" /> <property name="sendRenew" value="false" /> </bean> <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager" /> </bean> <bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <property name="loginUrl" value="https://localhost:8443/cas/login" /> <property name="serviceProperties" ref="serviceProperties" /> </bean> <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <property name="authenticationUserDetailsService"> <bean class=" org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <constructor-arg ref="userService" /> </bean> </property> <property name="serviceProperties" ref="serviceProperties" /> <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <constructor-arg index="0" value="https://localhost:8443/cas" /> </bean> </property> <property name="key" value="some_id_for_this_cas_prov" /> </bean> <bean id="proxyGrantingTicketStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl" />
SSL works great for them too. An error is logged if there is a loop:
2015-08-27 11:29:59,026 INFO [org.jasig.cas.CentralAuthenticationServiceImpl] - <Granted service ticket [ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org] for service [https://localhost:8443/i9t-YM/j_spring_cas_security_check] for user [joe]> 2015-08-27 11:29:59,027 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: joe WHAT: ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org for https://localhost:8443/i9t-YM/j_spring_cas_security_check ACTION: SERVICE_TICKET_CREATED APPLICATION: CAS WHEN: Thu Aug 27 11:29:59 BRT 2015 CLIENT IP ADDRESS: 0:0:0:0:0:0:0:1 SERVER IP ADDRESS: 0:0:0:0:0:0:0:1 ============================================================= > /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter' /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' HttpSession returned null object for SPRING_SECURITY_CONTEXT No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@1c993da0. A new one will be created. /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter' /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 4 of 12 in additional filter chain; firing Filter: 'CsrfFilter' /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 5 of 12 in additional filter chain; firing Filter: 'LogoutFilter' Request 'GET /j_spring_cas_security_check' doesn't match 'POST /logout /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 6 of 12 in additional filter chain; firing Filter: 'CasAuthenticationFilter' Checking match of request : '/j_spring_cas_security_check'; against 'https://localhost:8443/i9t-ym/j_spring_cas_security_check' serviceTicketRequest = false proxyReceptorConfigured = false proxyReceptorRequest = false proxyTicketRequest = false requiresAuthentication = false /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 7 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter' pathInfo: both null (property equals) queryString: arg1=ticket=ST-9-DElbuW6RP24GocThfiBt-cas01.example.org; arg2=ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org (property not equals) saved request doesn't match /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 8 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter' /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter' Populated SecurityContextHolder with anonymous token: 'org.sprin gframework.security.authentication.AnonymousAuthenticationToken@ 905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin gframework.security.web.authentication.WebAuthenticationDetails@ 0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 0CA64FA23DD44EECC261887599F2541B; Granted Authorities: ROLE_ANONYMOUS' /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter' /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter' /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor' Request '/j_spring_cas_security_check' matched by universal pattern '/**' Secure object: FilterInvocation: URL: /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org; Attributes: [hasRole('ROLE_USER')] Previously Authenticated: org.sprin gframework.security.authentication.AnonymousAuthenticationToken@ 905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin gframework.security.web.authentication.WebAuthenticationDetails@ 0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 0CA64FA23DD44EECC261887599F2541B; Granted Authorities: ROLE_ANONYMOUS Voter: org.sp ringframework.security.web.access.expression.WebExpressionVoter@ 2c4d2096, returned: -1 Access is denied (user is anonymous); redirecting to authentication entry point org.springframework.security.access.AccessDeniedException: Access is denied at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:232) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:123) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:96) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:614) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:617) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1527) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1484) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) Trying to match using Ant [pattern='/**', GET] Request '/j_spring_cas_security_check' matched by universal pattern '/**' Trying to match using NegatedRequestMatcher [requestMatcher=Ant [pattern='/**/favicon.ico']] Checking match of request : '/j_spring_cas_security_check'; against '/**/favicon.ico' matches = true Trying to match using NegatedRequestMatcher [requestMatcher=MediaTypeRequestMatcher [contentNegotiationSt rategy=org.springframework.web.accept.ContentNegotiationManager@ 37c9ddce, matchingMediaTypes=[application/json], useEquals=false, ignoredMediaTypes=[*/*]]] httpRequestMediaTypes=[text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8] Processing text/html application/json .isCompatibleWith text/html = false Processing application/xhtml+xml application/json .isCompatibleWith application/xhtml+xml = false Processing application/xml;q=0.9 application/json .isCompatibleWith application/xml;q=0.9 = false Processing */*;q=0.8 Ignoring Did not match any media types matches = true Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]] matches = true All requestMatchers returned true DefaultSavedRequest added to Session: DefaultSavedRequest[https://localhost:8443/i9t-YM/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org] Calling Authentication entry point. SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession. SecurityContextHolder now cleared, as request processing completed
I tried a number of solutions from other questions here, but I don’t know if I am just doing something really stupid that I don’t see, or if I messed up the configuration. Nothing missing, just say what I add. Thanks in advance!
Edit: This is not a ticket check from the application, but it should work. Another application works as intended:
2015-08-28 08:44:39,049 INFO [org.jasig.cas.CentralAuthenticationServiceImpl] - <Granted service ticket [ST-44-QmOXrKwachmUcM16DqV4-cas01.example.org] for service [https://localhost:8443/casTest/j_spring_cas_security_check] for user [joe]> 2015-08-28 08:44:39,049 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: joe WHAT: ST-44-QmOXrKwachmUcM16DqV4-cas01.example.org for https://localhost:8443/casTest/j_spring_cas_security_check ACTION: SERVICE_TICKET_CREATED APPLICATION: CAS WHEN: Fri Aug 28 08:44:39 BRT 2015 CLIENT IP ADDRESS: 0:0:0:0:0:0:0:1 SERVER IP ADDRESS: 0:0:0:0:0:0:0:1 ============================================================= > 2015-08-28 08:44:39,063 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: audit:unknown WHAT: ST-44-QmOXrKwachmUcM16DqV4-cas01.example.org ACTION: SERVICE_TICKET_VALIDATED APPLICATION: CAS WHEN: Fri Aug 28 08:44:39 BRT 2015 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= >
EDIT2: A very strange addition: the rest of the sample applications use only servlets and work with Spring 3.2. Now I tried to copy the original into this sample and tried to lower it until it worked. The problem is that I cannot get it to work with any @Controller annotation or similar ... But the redirect is gone. If I upgrade my maven and go from 4.2 to 3.2, the loop will disappear. But if I'm on 4.2, without changing anything except the version, there’s a loop!