如何使用Spring Security绕过匿名用户的会话超时?

krugob8w  于 2023-02-12  发布在  Spring
关注(0)|答案(2)|浏览(192)

我已经使用Spring Security两周了,除了匿名用户和会话超时之外,它运行得很好。

    • 使用案例1**

1.匿名用户可以访问站点并查看公共页面(/home、/about、/signup等),只要他们愿意(无会话超时)。
1.如果用户选择受保护的页面,则会出现登录屏幕。

    • 使用案例2**

1.注册用户登录并可以查看受保护的页面。
1.如果他们的会话超时,则会显示invalidSessionUrl页并引导用户登录。
我已经读了大量的SO和博客文章,但我似乎找不到用例#1的解决方案。我使用的是Spring 4.1.6.RELEASE、Spring Security 4.0.2 RELEASE和Tomcat8。
下面是我的安全配置:

protected void configure(HttpSecurity http) throws Exception {
    http
    .authorizeRequests()
        .antMatchers("/", "/home", "/about", "/login**", "/thankyou", "/user/signup**", "/errors/**").permitAll() 
        .regexMatchers("/user/signup/.*").permitAll()
        .antMatchers("/recipe/listRecipes*").hasAuthority("GUEST")
        .antMatchers("/recipe/addRecipe*").hasAuthority("AUTHOR")
        .antMatchers("/admin/**").hasAuthority("ADMIN")
        .anyRequest().authenticated()
        .expressionHandler(secExpressionHandler())
        .and()
    .formLogin()
        .loginPage("/login")
        .failureUrl("/login?err=1")
        .permitAll()
        .and()
    .logout()
        .logoutSuccessUrl("/thankyou")
        .deleteCookies( "JSESSIONID" )
        .invalidateHttpSession(false)
        .and()
    .exceptionHandling()
        .accessDeniedPage("/errors/403")
        .and()
    .rememberMe()
        .key("recipeOrganizer")
        .tokenRepository(persistentTokenRepository())
        .tokenValiditySeconds(960)      //.tokenValiditySeconds(1209600)
        .rememberMeParameter("rememberMe");

    http
    .sessionManagement()
        .sessionAuthenticationErrorUrl("/errors/402")   
        .invalidSessionUrl("/errors/invalidSession")
        .maximumSessions(1)
        .maxSessionsPreventsLogin(true)
        .expiredUrl("/errors/expiredSession")
        .and()
        .sessionFixation().migrateSession();

会话过期后匿名用户的任何操作都会导致无效会话异常。问题出在安全筛选器链中(参见下面的日志)AnonymousAuthenticationFilter(#11)创建新会话,但SessionManagementFilter(#12)检索先前过期的会话,将其与新会话进行比较,并抛出invalidsession异常。我希望这对登录的用户发生,但是,在抛出异常时,安全上下文已经被破坏,所以我无法知道先前的会话是来自匿名用户还是登录用户。
我考虑的解决方案是:
1.将全局会话超时设置为24小时或更长时间,然后在LoginSuccessHandler和RememberMeSuccessHandler中调整注册用户的超时。
1.关闭公共页面的安全性。
1.创建一个单独的cookie来指示用户的类型(匿名与已登录),并在InvalidSessionStrategy中查询该cookie以将匿名用户重定向到/主页。
1.创建一个自定义过滤器,首先执行该过滤器以识别匿名用户(可能吗?),然后简单地扩展当前会话。
1.编写一些javascript或jquery来定期检查会话超时,并为匿名用户重置它。
解决方案#1可能会导致服务器上会话过期的问题(不确定这是不是一件大事)。#2对我来说感觉不对,特别是如果一个公共页面可能包括有关登录用户的任何信息. #3可能工作,除了匿名用户可以点击除主页以外的公共链接,但被重定向到主页,因为我不认为有'有什么方法可以在InvalidSessionStrategy中判断他们试图访问的是哪个链接?我不确定#4是否有效-还没有尝试过。#5可能也有效,但它会增加网络流量?
我希望有人能给我一个实用的解决方案。这是许多网站都要处理的问题,但我正在绕圈子试图解决。提前感谢任何建议或提示。
下面是一个日志的一部分来说明发生了什么。出于测试的目的,我将会话超时设置为60秒。

Initial access to website
20:49:29.823 DEBUG: org.springframework.security.web.FilterChainProxy - / at position 11 of 14 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
20:49:29.839 DEBUG: org.springframework.security.web.authentication.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
20:49:29.839 DEBUG: org.springframework.security.web.FilterChainProxy - / at position 12 of 14 in additional filter chain; firing Filter: 'SessionManagementFilter'
20:49:29.839 DEBUG: org.springframework.security.web.FilterChainProxy - / at position 13 of 14 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
20:49:29.839 DEBUG: org.springframework.security.web.FilterChainProxy - / at position 14 of 14 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
20:49:29.839 DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/'
20:49:29.839 DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /; Attributes: [permitAll]
20:49:29.854 DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
20:49:29.871 DEBUG: org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@ff577, returned: 1
20:49:29.871 DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Authorization successful
20:49:30.136 DEBUG: net.mycompany.myapp.config.SessionListener - AuthenticationSuccess - principal: anonymousUser
20:49:30.167 DEBUG: net.mycompany.myapp.config.SessionListener - AuthenticationSuccess - authority contains: ROLE_ANONYMOUS
20:49:30.167 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session created on: Mon Sep 21 20:49:30 CDT 2015
20:49:30.167 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session last accessed on: Mon Sep 21 20:49:30 CDT 2015
20:49:30.167 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session expires after: 60 seconds
20:49:30.167 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session ID: CA34FE74B56B8EF94181B1231A7D4FF6

Session times out after 60 seconds
20:50:46.015 INFO : net.mycompany.myapp.config.SessionDestroyedListener - destroyedEvent
20:50:46.015 INFO : net.mycompany.myapp.config.SessionDestroyedListener - object:org.springframework.security.web.session.HttpSessionDestroyedEvent[source=org.apache.catalina.session.StandardSessionFacade@17827f3e]
20:50:46.015 INFO : net.mycompany.myapp.config.SessionListener - sessionDestroyed: Session created on: Mon Sep 21 20:49:30 CDT 2015
20:50:46.015 INFO : net.mycompany.myapp.config.SessionListener - sessionDestroyed: Session last accessed on: Mon Sep 21 20:49:32 CDT 2015
20:50:46.015 INFO : net.mycompany.myapp.config.SessionListener - sessionDestroyed: Session expires after: 60 seconds
20:50:46.015 INFO : net.mycompany.myapp.config.SessionListener - sessionDestroyed: Session ID: CA34FE74B56B8EF94181B1231A7D4FF6

Click on "/home" link (unsecured)
20:50:53.927 DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/home'; against '/resources/**'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 1 of 14 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 2 of 14 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
20:50:53.927 DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - No HttpSession currently exists
20:50:53.927 DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: null. A new one will be created.
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 3 of 14 in additional filter chain; firing Filter: 'HeaderWriterFilter'
20:50:53.927 DEBUG: org.springframework.security.web.header.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@4c668dec
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 4 of 14 in additional filter chain; firing Filter: 'CsrfFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 5 of 14 in additional filter chain; firing Filter: 'LogoutFilter'
20:50:53.927 DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Request 'GET /home' doesn't match 'POST /logout
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 6 of 14 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
20:50:53.927 DEBUG: org.springframework.security.web.util.matcher.AntPathRequestMatcher - Request 'GET /home' doesn't match 'POST /login
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 7 of 14 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 8 of 14 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 9 of 14 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 10 of 14 in additional filter chain; firing Filter: 'RememberMeAuthenticationFilter'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 11 of 14 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
20:50:53.927 DEBUG: org.springframework.security.web.authentication.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
20:50:53.927 DEBUG: org.springframework.security.web.FilterChainProxy - /home at position 12 of 14 in additional filter chain; firing Filter: 'SessionManagementFilter'
20:50:53.927 DEBUG: org.springframework.security.web.session.SessionManagementFilter - Requested session ID CA34FE74B56B8EF94181B1231A7D4FF6 is invalid.
20:50:53.927 DEBUG: org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy - Starting new session (if required) and redirecting to '/errors/invalidSession'
20:50:53.927 DEBUG: net.mycompany.myapp.config.SessionListener - AuthenticationSuccess - principal: anonymousUser
20:50:53.927 DEBUG: net.mycompany.myapp.config.SessionListener - AuthenticationSuccess - authority contains: ROLE_ANONYMOUS
20:50:53.927 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session created on: Mon Sep 21 20:50:53 CDT 2015
20:50:53.927 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session last accessed on: Mon Sep 21 20:50:53 CDT 2015
20:50:53.942 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session expires after: 60 seconds
20:50:53.942 INFO : net.mycompany.myapp.config.SessionListener - sessionCreated: Session ID: 6F7FD6230BC075B7768648BBBC08E3F4
20:50:53.942 DEBUG: org.springframework.security.web.session.HttpSessionEventPublisher - Publishing event: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.apache.catalina.session.StandardSessionFacade@412cbe09]
20:50:53.942 DEBUG: org.springframework.security.web.DefaultRedirectStrategy - Redirecting to '/myapp/errors/invalidSession'
ar5n3qh5

ar5n3qh51#

我在这篇文章发表几天后也提出了类似的问题:Why does anonymous user get redirected to expiredsessionurl by Spring Security,我已经给出了答案。上面的解决方案#2是可行的,但我不喜欢关闭所有的安全性,即使是公共页面。我最终实现的是#3和#4的组合。完整答案请参见链接的问题。

s71maibg

s71maibg2#

这是一个老问题,但我现在也有类似的问题(Spring Security 5. 5. 1)
我找到了一些答案但对我来说太脆弱了:

这对我很有效(使用Spring Security的自定义过滤器)

/**
 * https://doanduyhai.wordpress.com/2012/04/21/spring-security-part-vi-session-timeout-handling-for-ajax-calls/
 * 1. detect session expiry and ajax request --> send custom error code so that
 * client side can detect and handle as necessary if not, it will be a redirect
 * to login page and with 200 response
 *
 * https://forum.primefaces.org/viewtopic.php?t=16735 (use xml partial response for redirect)
 * @author RadianceOng
 */
public class AjaxTimeoutRedirectFilter extends GenericFilterBean {

    static Logger lg = Logger.getLogger(AjaxTimeoutRedirectFilter.class);

    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
    private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
    
    String invalidSessionUrl;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            chain.doFilter(request, response);
        } catch (IOException ex) {
            throw ex;
        } catch (Exception ex) {
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            Exception ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);

            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
            }

            if (ase != null) {
                if (ase instanceof AuthenticationException) {
                    throw (AuthenticationException) ase;
                } else if (ase instanceof AccessDeniedException) {

                    AccessDeniedException ade = (AccessDeniedException) ase;

                    if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {
                        logger.trace("User session expired or not logged in yet and trying to access unauthorized resource");

                        HttpServletRequest httpReq = (HttpServletRequest) request;
                        HttpServletResponse resp = (HttpServletResponse) response;
                        if (isAjaxRequest(httpReq)) {
                            logger.trace("Ajax call detected, redirecting");
                            
                            
                            /**
                             * Session expiry check is copied from SessionManagementFilter
                             * accessdenied check is derived from ExceptionTranslationFilter
                             * No idea what is the order... This is a workaround.
                             */
                            
                            final String redirectUrl = invalidSessionUrl;
                            sendAjaxRedirect(resp, httpReq, redirectUrl);
                        } else {
                            logger.trace("Normal call detected, redirecting");
                            new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl).onInvalidSessionDetected(httpReq, resp);
                        }
                    } else {
                        throw ade;
                    }
                }
            } else {
                throw ex;
            }

        }
    }

    public String getInvalidSessionUrl() {
        return invalidSessionUrl;
    }

    public void setInvalidSessionUrl(String invalidSessionUrl) {
        this.invalidSessionUrl = invalidSessionUrl;
    }
    
    private boolean isAjaxRequest(HttpServletRequest httpReq) {
        return new AjaxRedirectUtils().isAjaxRequest(httpReq);
    }

    private void sendAjaxRedirect(HttpServletResponse resp, HttpServletRequest httpReq, final String redirectUrl) throws IOException {
        new AjaxRedirectUtils().sendAjaxRedirect(resp, httpReq, redirectUrl);
    }
    
    private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {

        /**
         * @see
         * org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
         */
        protected void initExtractorMap() {
            super.initExtractorMap();

            registerExtractor(ServletException.class, new ThrowableCauseExtractor() {
                public Throwable extractCause(Throwable throwable) {
                    ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class);
                    return ((ServletException) throwable).getRootCause();
                }
            });
        }

    }
}

在SpringSecurity的XML配置中,在EXCEPTION_TRANSLATION_FILTER之后添加过滤器,以便它可以检测被拒绝的访问

<http>
<custom-filter ref="ajaxTimeoutRedirectFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
</http>

请注意,如果使用内置会话管理,请确保不要使用invalid-session-url或其他将生效的相关内容

<http>
 <session-management>
 </session-management>
</http>

这个过滤器还支持 AJAX 重定向,我没有考虑这个问题,因为它与这个问题无关
总之,这样做的目的是只在访问被拒绝时才重定向。因此,未登录用户不会被重定向,除非他们访问未经授权的页面。会话过期的登录用户在访问未经授权的页面时将被重定向。

相关问题