Search code examples
javaspringspring-securitycas

Can't make CAS Single Sign Out work with Spring Security


I'm not finding any actual guides as to implement the Single Sign Out CAS feature on my apps. I've tried a number of answers here on SO, but none worked(like this and this). Also, there's no examples to be found of the Spring Security+CAS using the Java configuration, so i'm also a bit lost on that. I cannot even figure out if this is the actual URL that i should be using, as the documentation tells me to use "/j_spring_security_logout", and that's just redirecting me to a blank index page, as my index page is working if i access it normally(albeit the console shows all the correct requests, like the JS and CSS). Would really appreciate some guidance, as there's NO documentation that i could find that are using the Java annotation. Thanks in advance!

My WebSecurityConfig:

@Configuration

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static String CAS_URL = "https://localhost:8443/cas";
    private static String APP_URL = "https://localhost:8443/i9t-YM";

    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService(APP_URL+"/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 authenticationUserDetailsService() {
        return new TestCasAuthenticationUserDetailsService();
    }

    @Bean
    public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
        return new Cas20ServiceTicketValidator(CAS_URL);
    }

    @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(CAS_URL+"/login");
        casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
        return casAuthenticationEntryPoint;
    }

    @Bean
    public SingleSignOutFilter SingleSignOutFilter(){
        return new SingleSignOutFilter();
    }

    @Bean
    public LogoutFilter requestLogoutFilter(){
        SecurityContextLogoutHandler handler = new SecurityContextLogoutHandler();
        handler.setClearAuthentication(true);
        handler.setInvalidateHttpSession(true);
        LogoutFilter logoutFilter = new LogoutFilter(APP_URL, handler);
        return logoutFilter;
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(casAuthenticationProvider());
        auth.inMemoryAuthentication().withUser("joe").password("joe").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilter(casAuthenticationFilter());
        http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint());
        http.addFilterBefore(requestLogoutFilter(), LogoutFilter.class);
        http.addFilterBefore(SingleSignOutFilter(), CasAuthenticationFilter.class);
        http.httpBasic().and().authorizeRequests().antMatchers("/index.html", "/home.html", "/login.html", "/")
                .permitAll().anyRequest().authenticated()
        .and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
                .csrf().csrfTokenRepository(csrfTokenRepository())
                ;
        http.logout()
        .deleteCookies("remove").invalidateHttpSession(true).logoutUrl("cas/logout")
        .logoutSuccessUrl("/");
        //http.exceptionHandling().accessDeniedPage("/403.html");
    }

    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }

}

The SSOut Filter on my Web.xml, dunno exactly why i added it:

<filter>
  <filter-name>characterEncodingFilter</filter-name>
  <filter-class>
    org.springframework.web.filter.CharacterEncodingFilter
  </filter-class>
  <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>characterEncodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
  <listener-class>
    org.jasig.cas.client.session.SingleSignOutHttpSessionListener
  </listener-class>
</listener>

Solution

  • This is my configuration for single sign out over spring security with cas integration:

    <bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" />
    
    <bean id="requestSingleLogoutFilter"
        class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <constructor-arg
            value="${cas.server.address}/logout?service=${cas.server.address}" />
        <constructor-arg>
            <bean
                class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
        </constructor-arg>
        <property name="filterProcessesUrl" value="/j_spring_cas_security_logout" />
    </bean>
    

    And you should add these filters to your springSecurityFilterChain:

    <sec:filter-chain pattern="/logout*" 
        filters="securityContextPersistenceFilter,singleLogoutFilter,casAuthenticationFilter" />
    <sec:filter-chain pattern="/j_spring_cas_security_logout*"
        filters="requestSingleLogoutFilter" />