通过Spring Security和CAS进行单点注销 [英] Single Sign Out with Spring Security and CAS

查看:185
本文介绍了通过Spring Security和CAS进行单点注销的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用纯Spring Java Config,我很难让Spring和CAS执行单一注销.我可以使用单点登录进行以下配置.我使用一个简单的JSP页面将表单POST到url https://nginx.shane.com/app/logout,然后将CSRF值包含在POST的数据中.一切似乎都正常,但当我进入安全页面时,它无需登录即可直接进入.有任何想法吗?

Using pure Spring Java Config I'm having troubles getting Spring and CAS to perform Single Sign Out. I have Single Sign On working with the configuration below. I use a simple JSP page to do a form POST to the url https://nginx.shane.com/app/logout and I include the CSRF value in the POST'd data. It all appears to work with no errors but when I go to a secured page it just lets me back in without requiring to login. 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());
}

}

我还添加了一个WebListener来处理会话破坏事件:

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

@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);
}
}

这是日志输出

[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.springframework.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.springframework.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/'] []

推荐答案

(幸运),我遇到了类似的问题;)当CAS尝试调用您的应用程序以注销时会发生.一方面,CAS尝试传递sessionId以执行注销,另一方面,SpringSecurity希望获得CSRF令牌,该令牌不是由CAS发送的,因为它仅发送GET请求. CsrfFilter找不到csrf令牌并中断过滤器链.用户不知道这一点,因为CAS隐式调用注销请求.请求直接从CAS服务器发送到应用程序服务器,而不是通过在Web浏览器中重定向用户.

(Un)lucky I had similar problem;) It occures when CAS tries to call your application to log out. On the one hand CAS tries to pass sessionId to perform logout, on the other hand SpringSecurity expects to obtain CSRF token, which was not send by CAS as it sends GET request only. CsrfFilter doesn't find csrf token and interrupts the filter chain. User is not aware of that since CAS calls logout request implicitly. Request goes directly from CAS server to the application server, not by redirecting user in the Web browser.

为了使其正常工作,您需要配置HttpSecurity以排除/不包括LogoutFilter filterProcessesUrl(在您的情况下为 j_spring_security_logout ,因为您使用的是默认值).

In order to make it work you need to configure HttpSecurity to exclude/not to include LogoutFilter filterProcessesUrl (which is j_spring_security_logout in your case as you use the default one).

假设您要在尝试创建新管理员时要检查CSRF,请立即进行以下配置:

Assuming that you want to check CSRF when trying to create new admin, for insatnce, you need to configure it as follows:

@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 have added

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

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

请注意,您不能使用匹配所有模式(/admin/**),因为您可能还想调用一些get请求,并且CSRF筛选器会希望它们随后发送令牌.

Pay attention that you cannot use match all pattern (/admin/**) since you presumably want to call some get requests as well and CSRF filter will expect them to send the token then.

3.2.x之前的Spring Security不会出现这种问题,因为在那里引入了跨站点请求伪造(CSRF)支持.

Such problem will not arise with Spring Security previous than 3.2.x, since the Cross Site Request Forgery (CSRF) support was introduced there.

希望这些帮助:)

这篇关于通过Spring Security和CAS进行单点注销的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆