【问题标题】:How is SecurityContextLogoutHandler's clearAuthentication not threadsafe?SecurityContextLogoutHandler 的 clearAuthentication 怎么不是线程安全的?
【发布时间】:2016-08-16 14:24:09
【问题描述】:

Spring 的 SecurityContextLogoutHandler 指出 clearAuthentication 标志用于:

SecurityContext 中删除Authentication 以防止并发请求出现问题。

SecurityContext 中删除Authentication 可以防止哪些具体问题?为什么简单地使会话无效(这是SecurityContextLogoutHandler 的另一个职责)不够?

不清除SecurityContext 是否担心SecurityContextPersistenceFilter 可能会将当前身份验证保留为新会话ID?有效地让用户只使用新会话登录?

【问题讨论】:

    标签: java spring spring-mvc spring-security


    【解决方案1】:

    clearAuthentication 标志已添加到 this commit 并带有注释

    以前当用户尝试访问时可能会出现竞争情况 一个缓慢的资源,然后注销,这将导致用户没有被登录

    SecurityContextLogoutHandler 现在将从 SecurityContext 以防止出现这种情况。

    它修复了这个issue(github 上的same issue)。引用:

    如果 SecurityContextPersistenceFilter 执行需要大量时间,则如果会话从另一个线程无效,则 HttpSessionSecurityContextRepository 将身份验证恢复到会话。

    我正在使用 Spring + JSF + DWR 框架 + GWT 事件服务(ajax 推送)。在任何时候,至少有一个线程在服务器端等待推送事件。此请求由 SecurityContextPersistenceFilter 处理,它记住请求到达服务器时的身份验证。如果在处理此过滤器期间会话失效(通过在管理区域中通过 id 使会话失效的另一个选项卡中单击注销)然后 HttpSessionSecurityContextRepository 将过时的身份验证放入新会话(由 JSF 框架创建,因此会话是在处理 SecurityContextPersistenceFilter 期间更改)。 如果将一些处理延迟插入到 SecurityContextPersistenceFilter,这很容易重现。

    SaveToSessionResponseWrapper 应该记住初始的 HttpSession 并检查原始会话是否无效,因此它不会将当前身份验证设置为新会话。

    【讨论】:

      【解决方案2】:

      http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html 在请求之间存储 SecurityContext

      根据应用程序的类型,可能需要制定策略来存储用户操作之间的安全上下文。在典型的 Web 应用程序中,用户登录一次,随后由其会话 ID 标识。服务器缓存持续时间会话的主体信息。在 Spring Security 中,存储请求之间的 SecurityContext 的责任落在了 SecurityContextPersistenceFilter 上,它默认将上下文存储为 HTTP 请求之间的 HttpSession 属性。它为每个请求将上下文恢复到 SecurityContextHolder,并且至关重要的是,在请求完成时清除 SecurityContextHolder。 出于安全目的,您不应直接与 HttpSession 交互。 这样做根本没有任何理由 - 始终使用 SecurityContextHolder。

      许多其他类型的应用程序(例如,无状态的 RESTful Web 服务)不使用 HTTP 会话,并且会在每个请求上重新进行身份验证。但是,包含 SecurityContextPersistenceFilter 仍然很重要确保在每次请求后清除 SecurityContextHolder。

      [注意] 注意 在单个会话中接收并发请求的应用程序中,相同的 SecurityContext 实例将在线程之间共享。即使使用了 ThreadLocal,它也是从每个线程的 HttpSession 中检索到的同一个实例。如果您希望临时更改线程正在运行的上下文,这会产生影响。如果您只使用 SecurityContextHolder.getContext(),并在返回的上下文对象上调用 setAuthentication(anAuthentication),那么 Authentication 对象将在共享相同 SecurityContext 实例的所有并发线程中发生变化。 您可以自定义 SecurityContextPersistenceFilter 的行为,为每个请求创建一个全新的 SecurityContext,防止一个线程中的更改影响另一个线程。 或者,您可以在临时更改上下文的位置创建一个新实例. SecurityContextHolder.createEmptyContext() 方法总是返回一个新的上下文实例。

      【讨论】:

        【解决方案3】:

        什么是SecurityContextLogoutHandler?

        SecurityContextLogoutHandler 是一个实现 LogoutHandler 的处理程序。

        SecurityContextLogoutHandler 做什么?

        1. 它通过修改 SecurityContextHolder 来执行注销。
        2. 如果 isInvalidateHttpSession() 也会使 HttpSession 无效 为真且会话不为空。
        3. 它还会从当前的 如果 clearAuthentication 设置为 true(默认),则为 SecurityContext。

        SecurityContextHolder 线程安全吗?

        是的,默认策略 (MODE_THREADLOCAL) 是线程安全的(只要您不尝试动态更改策略)。但是,如果您希望衍生线程继承父线程的 SecurityContext,则应设置 MODE_INHERITABLETHREADLOCAL。

        此外方面没有任何“线程逻辑”,它们与建议的方法在同一个线程中执行。

        归功于@axtavt

        什么是 Spring Security 中的身份验证?

        身份验证: 框架尝试使用提供的凭据来识别最终用户。可以针对插入 Spring Security 的第三方系统进行身份验证。

        让我们考虑一个大家都熟悉的标准身份验证场景。

        1. 系统会提示用户使用用户名和密码登录。
        2. 系统(成功)验证密码是否正确 用户名。
        3. 获取该用户的上下文信息(他们的列表 角色等)。

        为用户建立安全上下文 用户继续执行某些操作,该操作可能受到访问控制机制的保护,该机制根据当前的安全上下文信息检查操作所需的权限。

        前三项构成身份验证过程,因此我们将看看它们是如何在 Spring Security 中发生的。

        1. 获取用户名和密码并组合成实例 UsernamePasswordAuthenticationToken (的一个实例 身份验证接口,我们之前看到的)。
        2. 令牌被传递给 AuthenticationManager 的实例 验证。
        3. AuthenticationManager 返回一个完全填充的身份验证 成功验证的实例。
        4. 安全上下文是通过调用建立的 SecurityContextHolder.getContext().setAuthentication(...),通过 在返回的身份验证对象中。

        SecurityContextPersistentFilter

        这个名字很明确。 SecurityContextPersistentFilter 接口的目的是将安全上下文存储在某个存储库中。 为了完成这项任务,过滤器将作业委托给 SecurityContextRepository 接口。 Spring为此接口提供了一个默认实现:org.springframework.security.web.context.HttpSessionSecurityContextRepository。这是不言自明的。安全上下文的存储库就是当前用户 HTTP 会话。 下面是SecurityContextPersistentFilter的XML配置

        <!-- Filter to store the Authentication object in the HTTP Session -->   
        <bean id="securityContextPersistentFilter"
            class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
            <property name="securityContextRepository" ref="securityContextRepository" />
        </bean>
        
        
        <bean id="securityContextRepository"
            class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
        

        注销过滤器

        LogoutFilter 负责注销当前用户并使安全上下文无效。使 HTTP 会话无效的任务再次委托给另一个参与者 SecurityContextLogoutHandler。

        这个处理程序被注入到 LogoutFilter 构造函数中:

        <bean id="logoutFilter"
            class="org.springframework.security.web.authentication.logout.LogoutFilter">
            <constructor-arg value="/pages/Security/logout.html" />
            <constructor-arg>
                <list>
                    <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
                </list>
            </constructor-arg>
            <property name="filterProcessesUrl" value="/j_myApplication_logout"/>
        </bean>
        

        &lt;constructor-arg value="/pages/Security/logout.html" /&gt; - 它定义了注销页面的 URL。

        SecurityContextLogoutHandler 作为构造函数参数注入&lt;bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/&gt;

        注销操作的 HTML URL 由 &lt;property name="filterProcessesUrl" value="/j_myApplication_logout"/&gt; 处的 filterProcessesUrl 参数定义

        资源链接:

        1. https://doanduyhai.wordpress.com/2012/01/22/spring-security-part-i-configuration-and-security-chain/
        2. https://doanduyhai.wordpress.com/2012/02/05/spring-security-part-ii-securitycontextpersistentfilter-logoutfilter/
        3. http://shazsterblog.blogspot.com/2014/02/spring-security-custom-filterchainproxy.html
        4. http://docs.spring.io/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/context/SecurityContextPersistenceFilter.html

        【讨论】:

          【解决方案4】:

          SecurityContextLogoutHandler 使 Servlet 会话无效,以标准 Servlet 方式,调用 HttpSession 对象上的 invalidate 方法,并从 Spring Security 中清除 SecurityContext。 SecurityContextLogoutHandler 实现 LogoutHandler 接口 传统上,在 Java Web 应用程序中,用户会话信息是使用 HttpSession 对象管理的。 在 Spring Security(会话清除)中,在低级别,情况仍然如此, Spring security 引入了处理会话或用户会话信息的新方法。 在使用 Spring Security 的应用程序中,您很少会直接访问 Session 对象来检索用户 细节。相反,您将使用 SecurityContext(及其实现类)和 SecurityContextHolder (及其实现类)。 SecurityContextHolder 允许快速访问 SecurityContext, SecurityContext 允许快速访问 Authentication 对象,而 Authentication 对象允许快速访问 访问用户详细信息。

          例如下面的程序说明访问认证对象和显示消息

          @Controller
          @RequestMapping("/admin")
          public class AdminController {
          
          @RequestMapping(method = RequestMethod.POST, value = "/movies")
          @ResponseBody
          public String createMovie(@RequestBody String movie) {
          System.out.println("Adding movie!! "+movie);
          return "created";
          }
          
          @RequestMapping(method = RequestMethod.GET, value = "/movies")
          @ResponseBody
          public String createMovie() {
          UserDetails user = (UserDetails)SecurityContextHolder.getContext().getAuthentication().
          getPrincipal();
          System.out.println("returned movie!");
          return "User "+user.getUsername()+" is accessing movie x";
          }
          }
          

          一旦身份验证完成,它就会创建一个新会话; 创建会话后,它包含用户的信息 .on logout 你需要 不仅要使会话无效,还需要清除会话信息 默认在

            `isInvalidateHttpSession(`)  
          

          检查会话是否有效 如果会话有效

          setInvalidateHttpSession(boolean invalidateHttpSession)
          

          被称为 .然而,使会话对象无效但会话对象仍然包含信息。 要清除会话信息,您需要调用

             setClearAuthentication(boolean clearAuthentication)
          

          如果你不使用第三种方法,那么该方法将成为线程安全的,它可以在低级别检索信息

          【讨论】:

            猜你喜欢
            • 2015-04-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-04-05
            • 2011-04-05
            • 2010-12-10
            相关资源
            最近更新 更多