Single Sign-On with Spring Security and CAS

Using a pure Spring Java configuration I'm having trouble getting Spring and CAS to run Single Sign Out. I have Single Sign On working with the configuration below. I use a simple JSP page to create a POST form for the https://nginx.shane.com/app/logout URL, and I include the CSRF value in the POST'd data. Everything seems to work without errors, but when I go to a secure page, it just allows me to return without having to log in. Any ideas?

@Configuration @EnableWebSecurity public class SecurityWebAppConfig extends WebSecurityConfigurerAdapter { @Bean protected ServiceProperties serviceProperties() { ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setService("https://nginx.shane.com/app/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("an_id_for_this_auth_provider_only"); return casAuthenticationProvider; } @Bean public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService() { return new TestCasAuthenticationUserDetailsService(); } @Bean public Cas20ServiceTicketValidator cas20ServiceTicketValidator() { return new Cas20ServiceTicketValidator("https://nginx.shane.com/cas"); } @Bean public CasAuthenticationFilter casAuthenticationFilter() throws Exception { CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter(); casAuthenticationFilter.setAuthenticationManager(authenticationManager()); return casAuthenticationFilter; } @Bean public CasAuthenticationEntryPoint casAuthenticationEntryPoint() { CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint(); casAuthenticationEntryPoint.setLoginUrl("https://nginx.shane.com/cas/login"); casAuthenticationEntryPoint.setServiceProperties(serviceProperties()); return casAuthenticationEntryPoint; } @Bean public SingleSignOutFilter singleSignOutFilter() { // This filter handles a Single Logout Request from the CAS Server return new SingleSignOutFilter(); } @Bean public LogoutFilter requestLogoutFilter() { // This filter redirects to the CAS Server to signal Single Logout should be performed SecurityContextLogoutHandler handler = new SecurityContextLogoutHandler(); handler.setClearAuthentication(true); handler.setInvalidateHttpSession(true); LogoutFilter logoutFilter = new LogoutFilter("https://nginx.shane.com/", handler); return logoutFilter; } @Override protected void configure(HttpSecurity http) throws Exception { http.addFilter(casAuthenticationFilter()); http.addFilterBefore(requestLogoutFilter(), LogoutFilter.class); http.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class); http.exceptionHandling() .authenticationEntryPoint(casAuthenticationEntryPoint()); http.authorizeRequests() .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')") .antMatchers("/dba/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_DBA')"); http.logout() .addLogoutHandler(handler) .deleteCookies("remove") .invalidateHttpSession(true) .logoutUrl("/logout") .logoutSuccessUrl("/"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(casAuthenticationProvider()); } 

}

I also added a WebListener to handle the event that the session destroyed:

 @WebListener public class SecurityWebListener implements HttpSessionListener { private SingleSignOutHttpSessionListener listener = new SingleSignOutHttpSessionListener(); @Override public void sessionCreated(HttpSessionEvent se) { listener.sessionCreated(se); } @Override public void sessionDestroyed(HttpSessionEvent se) { listener.sessionDestroyed(se); } } 

Here is the log output

 [org.springframework.security.web.FilterChainProxy] [/logout at position 6 of 14 in additional filter chain; firing Filter: 'LogoutFilter'] [] [org.springframework.security.web.util.matcher.AntPathRequestMatcher] [Checking match of request : '/logout'; against '/logout'] [] [org.springframework.security.web.authentication.logout.LogoutFilter] [Logging out user 'org.spr ingframework.security.cas.authentication.CasAuthenticationToken@ 836ad34b: Principal: org.springframework.security.core.userdetails.User@586034f : Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin gframework.security.web.authentication.WebAuthenticationDetails@ fffdaa08: RemoteIpAddress: 127.0.0.1; SessionId: FA880C15EF09C033E1CA0C8E4785905F; Granted Authorities: ROLE_ADMIN Assertion: org.jasig.cas.client.validation.AssertionImpl@fcd38ec Credentials (Service/Proxy Ticket): ST-23-1UandqRxBcG6HCTx0Pdd-cas01.example.org' and transferring to logout destination] [] [org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler] [Invalidating session: FA880C15EF09C033E1CA0C8E4785905F] [] [org.jasig.cas.client.session.HashMapBackedSessionMappingStorage] [Attempting to remove Session=[FA880C15EF09C033E1CA0C8E4785905F]] [] [org.jasig.cas.client.session.HashMapBackedSessionMappingStorage] [Found mapping for session. Session Removed.] [] [org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler] [Using default Url: /] [] [org.springframework.security.web.DefaultRedirectStrategy] [Redirecting to '/app/'] [] 
+6
source share
1 answer

(Un) lucky, I had a similar problem;) This happens when CAS tries to call your application to exit the system. On the one hand, CAS is trying to pass sessionId to perform a logout, on the other hand, SpringSecurity expects to receive a CSRF token that has not been sent by CAS, since it only sends a GET request. CsrfFilter does not find the csrf token and breaks the filter chain. The user is unaware of this because the CAS invokes the logout request implicitly. The request is sent directly from the CAS server to the application server, and not by redirecting the user in a web browser.

To do this, you need to configure HttpSecurity to exclude / not enable LogoutFilter filterProcessesUrl (which is located in j_spring_security_logout in your case when you use it by default).

Assuming you want to check the CSRF when trying to create a new administrator, for insatnce you need to configure it like this:

 @Override protected void configure(HttpSecurity http) throws Exception { http.addFilter(casAuthenticationFilter()); http.addFilterBefore(requestLogoutFilter(), LogoutFilter.class); http.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class); http.exceptionHandling() .authenticationEntryPoint(casAuthenticationEntryPoint()); http.authorizeRequests() .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')") .antMatchers("/dba/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_DBA')"); http.csrf() .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/admin/create")); http.logout() .addLogoutHandler(handler) .deleteCookies("remove") .invalidateHttpSession(true) .logoutUrl("/logout") .logoutSuccessUrl("/"); } 

Just to indicate I added

http.csrf().requireCsrfProtectionMatcher(new AntPathRequestMatcher("/admin/create")); .

Note that you cannot use match all pattern (/ admin / **), since you probably want to also trigger some receive requests, and the CSRF filter will wait for them to send a token.

This problem does not occur when using Spring Security prior to 3.2.x, since it introduced support for the cross-site request request routine (CSRF).

Hope this help :)

+3
source

Source: https://habr.com/ru/post/983407/


All Articles