spring security 概述& 配置文件详解

通常,安全任务是由 应用服务器 完成用户认证和对资源的授权,这些任务也可以委托给Spring security处理这些任务从而减轻应用服务器负担,Spring安全基本上通过实施标准的javax.servlet.Filter来处理这些任务,您需要声明下面的 过滤器 在web. xml

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

过滤器(springSecurityFilterChain)委托给Spring对在过滤器中定义的安全过滤器对上下文应用进行处理。在DelegatingFilterProxy类(实施javax.servlet.Filter的的)的doFilter方法??里,Spring应用程序上下文将被名为'springSecurityFilterChain'进行检查。

'springSecurityFilterChain'有一个别名filterChainProxy:

<alias name="filterChainProxy" alias="springSecurityFilterChain"/>

下面的问题是:

  • 谁初始化/定义此filterChainProxy?
  • 哪些安全过滤器在Spring应用程序上下文中定义?
  • 这些安全过滤器如何不同于在web.xml中定义的正常过滤器?

 

filterChainProxy是在应用程序上下文的安全命名空间<HTTP>元素定义时初始化的。这里是<HTTP>元件的基本结构:

<sec:http auto-config="true">
 <sec:intercept-url pattern="/**" access="ROLE_USER" />
</sec:http>

<sec:authentication-manager id="authenticationManager">
  <sec:authentication-provider>
    <sec:user-service>
      <sec:user name="admin" password="password" authorities="ROLE_USER, ROLE_ADMIN" />
      <sec:user name="user" password="password" authorities="ROLE_USER" />
    </sec:user-service>
  </sec:authentication-provider>
</sec:authentication-manager>

来自于Spring框架的HttpSecurityBeanDefinitionParser读取这个‹http›元素,然后注册 filterChainProxy到Spring的应用上下文. http元素的auto-config设置为真,实际就是处理如下简写:

<sec:http>
  <sec:form-login />
  <sec:http-basic />
  <sec:logout />
</sec:http>

下面回答第二个问题:哪些安全过滤器在Spring应用程序上下文中定义?

该<HTTP>命名空间块总是创建一个SecurityContextPersistenceFilter和一个ExceptionTranslationFilter和FilterSecurityInterceptor。这些都是固定的,不能被替换的替代品。

在默认情况下,当我们添加<HTTP>元素时,上面的三个过滤器将被添加。正如我们已经设置自动配置为true,BasicAuthenticationFilter,LogoutFilter和UsernamePasswordAuthenticationFilter也被添加到过滤器链。

现在,如果你看看任何这些过滤器的源代码,这些也都是标准javax.servlet.Filter的实现。但是,通过定义这些过滤器在应用程序上下文中,而不是在web.xml中,应用服务器将控制权交给Spring来处理安全相关的任务。Spring的filterChainProxy将负责请求的安全过滤。这回答了第三个问题。

为了获得更精细的控制,可以定义自己的FilterChainProxy

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
  <sec:filter-chain-map path-type="ant">
    <sec:filter-chain pattern="/images/*" filters="none"/>
    <sec:filter-chain pattern="/**" filters="securityContextFilter, logoutFilter, formLoginFilter, servletApiFilter, anonFilter, exceptionTranslator, 
    filterSecurityInterceptor, customFilter1, customeFilter2" />
  </sec:filter-chain-map>
</bean>

从上面的XML中,我们看到我们不希望图像被应用于的任何过滤器检查,请求的其余部分都被指定有一个过滤器列表。但这种注册自己的过滤器链一般是没有必要的。Spring通过<HTTP>元素,提供了一些钩子,通过它我们可以得到关于如何安全应用更精细的控制。因此,我们将着眼于通过<HTTP>元素进行配置细节。

下面我们看看Spring是如何完成以下功能:

  1. 验证: HttpBasicAuthentication 和表单登录验证
  2. 通过ACL实现授权控制
  3. 退出
  4. 匿名登录Anonymous Login support
  5. Remember-me 验证
  6. 并发会话管理。

验证

  HttpBasicAuthentication和基于表单的身份验证登录 - 身份验证可以通过两种方式来处理。我们要定义适用所有的应用程序的身份验证程序UserDetailsService:

package org.springframework.security.core.userdetails;

import org.springframework.dao.DataAccessException;

/**
 * Core interface which loads user-specific data.
 * It is used throughout the framework as a user DAO and is the strategy used by the
 * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider DaoAuthenticationProvider}.
 * The interface requires only one read-only method, which simplifies support for new data-access strategies.
 * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
 * @see UserDetails
 * @author Ben Alex
 */
public interface UserDetailsService {
    /**
     * Locates the user based on the username. In the actual implementation, the search may possibly be case
     * insensitive, or case insensitive depending on how the implementation instance is configured. In this case, the
     * <code>UserDetails</code> object that comes back may have a username that is of a different case than what was
     * actually requested..
     *
     * @param username the username identifying the user whose data is required.
     *
     * @return a fully populated user record (never <code>null</code>)
     *
     * @throws UsernameNotFoundException if the user could not be found or the user has no GrantedAuthority
     * @throws DataAccessException if user could not be found for a repository-specific reason
     */
    UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException, DataAccessException;
}

Spring提供这个接口的两个实现:

(1)存储用户的 login/password 细节在应用上下文

<sec:authentication-manager id="authenticationManager">
  <sec:authentication-provider>
     <sec:user-service>
      <sec:user name="admin" password="password" authorities="ROLE_ADMIN,ROLE_USER"/>
      <sec:user name="user" password="password" authorities="ROLE_USER"/>
    </sec:user-service>
  </sec:authentication-provider>
</sec:authentication-manager>

‹authentication-provider›实际是配置DaoAuthenticationProvider类,这个Dao实际是UserDetailsService 的实现. 我们提供用户名和密码在XML中,当用户很多时,应该存储在数据库中,相应实现是org.springframework.security.core.userdetails.memory.InMemoryDaoImpl

(2)存储用户的 login/password 细节在数据库

<sec:authentication-manager id="authenticationManager">
  <sec:authentication-provider>
    <sec:jdbc-user-service data-source-ref="dataSource" />
  </sec:authentication-provider>

</sec:authentication-manager>

这对应org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl. 类,如果你看看这个类,你可以发现,用户名和密码都存储在用户表,并且可以分配给用户的角色存储在授权表。下面是这个类从数据库查询获取用户凭据和权限:

-- Fetch user credentials: 
select username,password,enabled from users where username = ?
-- Fetch user authorities: 
select username,authority from authorities where username = ?

假设你有您的用户详细信息存储在其他一些表的遗留数据库,那么我们可以配置Spring获取用户凭据和权限。说我有一个成员表里面有ID,用户名,密码和角色表有角色。下面是我们如何需要配置:

<sec:authentication-manager id="authenticationManager">
  <sec:authentication-provider>
    <!-- TBD <password-encoder hash="md5"/> -->
    <sec:jdbc-user-service id="userDetailsService" data-source-ref="dataSource" 
      users-by-username-query=
        "SELECT username, password, true as enabled
         FROM MEMBER
         WHERE username=?"
      authorities-by-username-query=
        "SELECT member.username, role.role as authorities
         FROM ROLE role, MEMBER member
         WHERE role.member_id=member.id and member.username=?"/>
  </sec:authentication-provider>
</sec:authentication-manager>

下面开始进行验证。

HttpBasicAuthentication是如下配置

<sec:http auto-config="true">
<sec:http-basic />
</sec:http>

默认情况下,启用此选项,浏览器通常会显示用户登录的登录对话框。而不是我们可以配置的登录页面。它最大的问题,大多数浏览器都有倾向于缓存的会话,不同的用户无法在通过刷新浏览器重新登录。

<http-basic>实际上定义了一个BasicAuthenticationFilter过滤器。一旦认证成功,认证对象将被投入Spring的SecurityContext。安全上下文可以通过类SecurityContextHolder进行访问。下面是优化BasicAuthenticationFilter bean声明:

<sec:custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthenticationFilter" />

<bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</bean>

<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <property name="loginFormUrl" value="/login.jsp"/>
</bean>

基于表单的验证

<sec:form-login login-page="/login.jsp"/>激活表单认证。

Spring提供了多个挂钩。目标URL指定用户进行身份验证后应该去页面,如果和身份验证失败,url定义认证失败的页面。

<sec:form-login login-page="/login.jsp" default-target-url="/app/messagePost" 
authentication-failure-url="/login.jsp?error=true"/>

更多属性有: always-use-default-target, authentication-success-handler-ref and authentication-failure-handler-ref.authentication-success-handler-ref gets called on successful authentication and authentication-failure-handler-ref 

这里是AuthenticationSuccessHandler 和AuthenticationFailureHandler.接口:

public interface AuthenticationSuccessHandler {

    void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException;
}

 

public interface AuthenticationFailureHandler {

    void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException;
}

Spring内建了两个实现SimpleUrlAuthenticationSuccessHandler 和SavedRequestAwareAuthenticationSuccessHandler分别实现这两个接口。

SavedRequestAwareAuthenticationSuccessHandler的目的是将用户带回定向到登录页面的之前的那个页面, ‹form-login› 缺省是定义回到之前的那个页面,我们可以优化定制登录成功后重定向到我们指定的那个页面。

SimpleUrlAuthenticationFailureHandler和ExceptionMappingAuthenticationFailureHandler是进行失败处理的,后者继承前者。

只是在SimpleUrlAuthenticationFailureHandler 时规定一个出错页面的URL,ExceptionMappingAuthenticationFailureHandler( org.springframework的子类。 security.core.AuthenticationException )我们指定的身份验证类型的异常,用户将被重定向到多个URL 。

当我们定义我们的自定义登录页面,我们分别标记的用户名和密码字段为j_username和j_password和提交操作将默认为j_spring_security_check 。我们也可以配置这些字段名,并通过指定的属性提交行动:分别为username-parameter, password-parameter 和 login-processing-url 。

完整的filter如下:

<sec:custom-filter position="FORM_LOGIN_FILTER" ref="formLoginFilter" />
<bean id="formLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="filterProcessesUrl" value="/j_spring_security_check"/>
  <property name="usernameParameter" value="username "/>
  <property name="passwordParameter" value="password"/>
  <property name="authenticationSuccessHandler">
    <bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler ">
      <property name="alwaysUseDefaultTargetUrl" value="true"/>
      <property name="defaultTargetUrl" value="/success.jsp"/>
    </bean>
  </property>
  <property name="authenticationFailureHandler">
    <!--bean class=" org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler "/-->
    <bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
      <property name="exceptionMappings">
        <props>
          <prop key="org.springframework.security.authentication.BadCredentialsException">/login/badCredentials</prop>
          <prop key="org.springframework.security.authentication.CredentialsExpiredException">/login/credentialsExpired</prop>
          <prop key="org.springframework.security.authentication.LockedException">/login/accountLocked</prop>
          <prop key="org.springframework.security.authentication.DisabledException">/login/accountDisabled</prop>
        </props>
      </property>
    </bean>
  </property>
 </bean>

问题是用户名和密码是明文。这可以通过使用加密技术进行编码的密码处理。 Spring提供了一个内置该使用身份验证提供程序使用<password-encoder>元素的支持。

<sec:authentication-manager id="authenticationManager">
  <sec:authentication-provider>
    <sec:password-encoder hash="md5"/>
    <sec:jdbc-user-service data-source-ref="dataSource" />
  </sec:authentication-provider>

</sec:authentication-manager>

通过ACL的授权

  通过‹http›的‹intercept-url›进行配置:

<sec:http access-decision-manager-ref="accessDecisionManager">
  <sec:intercept-url pattern="/app/messageList*" access="ROLE_USER,ROLE_ANONYMOUS"/>
  <sec:intercept-url pattern="/app/messagePost*" access="ROLE_USER"/>
  <sec:intercept-url pattern="/app/messageDelete*" access="ROLE_ADMIN"/>
  <sec:intercept-url pattern="/app/*" access="ROLE_USER"/>

  <form-login login-page="/login.jsp" default-target-url="/app/messagePost" 
    authentication-failure-url="/login.jsp?error=true"/>
  <!-- Other settings -->
</sec:http>

  每个 intercept-url 规定了一个 url模式和角色,具有这个角色的用户可以访问这些URL。注意url-pattern总是以 ‘*’结束,如果 ‘*’ 没有规定,提供黑客在url中提供一些参数绕过安全机制。

Spring将这些URL拦截到FilterSecurityInterceptor. 下面是没有使用‹intercept-url›:另外一种类似配置。

<sec:custom-filter position="FILTER_SECURITY_INTERCEPTOR" ref="filterSecurityInterceptor" />
<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="accessDecisionManager" ref="accessDecisionManager"/>
  <property name="securityMetadataSource">
  <sec:filter-security-metadata-source lowercase-comparisons="true" request-matcher="ant" use-expressions="true">
    <sec:intercept-url pattern="/app/messageList*" access="ROLE_USER,ROLE_ANONYMOUS"/>
    <sec:intercept-url pattern="/app/messagePost*" access="ROLE_USER"/>
    <sec:intercept-url pattern="/app/messageDelete*" access="ROLE_ADMIN"/>
    <sec:intercept-url pattern="/app/*" access="ROLE_USER"/>
  </sec:filter-security-metadata-source>
  </property>
</bean>

从上面的代码可以看出,匿名用户只能访问messageList的页面,其他页面必须登录。

如果你仔细观察的bean声明,有一个属性'的AccessDecisionManager “ 。这样做的目的是什么?

这个bean这实际上实现访问控制决策。它有实现AccessDecisionManager接口。 Spring提供了三个内置的访问决策管理。

  在了解访问决策管理器是如何工作之前,我们需要知道AccessDecisionVoter是什么。AccessDecisionManager实际上是由一个或多个决定是否访问的投票者的组合体。这个组合封装了允许/拒绝/放弃观看资源的用户逻辑。投票者决定结果是通过ACCESS_GRANTED , ACCESS_DENIED和ACCESS_ABSTAIN中的AccessDecisionVoter接口中定义的常量字段来表示。我们可以定义自定义访问决策,并注入到我们的访问决策管理器中。

看看内置决策管理者:

AffirmativeBased :至少一个投票者必须决定授予访问权限
ConsensusBased :多数投票者必须授予访问权限
UnanimousBased :所有投票者都必须投票或放弃投票授予访问权限(无投票表决拒绝访问)

默认情况下, AffirmativeBased访问决策管理器将由两个投票者初始化:RoleVoter和AuthenticatedVoter 。

如果用户具有访问资源的角色,RoleVoter授权访问,角色必须有“ ROLE_ ”前缀,下面我们看到也可以定制其他前缀。

AuthenticatedVoter是在用户被验证是授权访问,接受的身份验证级别有:IS_AUTHENTICATED_FULLY , IS_AUTHENTICATED_REMEMBERED和IS_AUTHENTICATED_ANONYMOUSLY

下面展示假设我们要定义一个自定义的投票者,并把它添加到访问决策管理器:

<sec:http access-decision-manager-ref="accessDecisionManager" auto-config="true">
  <!-- filters declaration go here-->
</sec:http>

<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
  <property name="decisionVoters">
    <list>
      <bean class="org.springframework.security.access.vote.RoleVoter">
       <!-- Customize the prefix-->
       <property name="rolePrefix" value="ROLE_"/>
      </bean>
      <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
      <bean class="com.pramati.security.voters.CustomVoter"/>
    </list>
  </property>
</bean>

登录退出

  登录退出配置如下:

<sec:http>

  <!-- Other filter declarations here -->

  <sec:logout />

</sec:http>

缺省登出页面的URL是/j_spring_security_logout,可以规定 logout-url attribute.优化这个URL.

退出成功后,缺省是根路径,通过下面可以指定退出成功后的URL:

<sec:logout logout-url="/j_logMeOut" logout-success-url="/app/messageList"/>

如果你想在登陆页面不是默认的,而是一个特定的在不同的场景不同URL,那么我们必须实现LogoutSuccessHandler并提供了一个参考<logout>元素

<sec:logout logout-url="/j_logMeOut" success-handler-ref="customLogoutSuccessHandler"/>

然后定义Bean:

<sec:custom-filter position="LOGOUT_FILTER" ref="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_logMeOut"/>
</bean>

匿名登录

  缺省Spring创建anonymous角色。当你规定角色是ROLE_ANONYMOUS’ 或‘IS_AUTHENTICATED_ANONYMOUSLY’, 任何人都可以访问资源。

在AffirmativedBased 访问控制器中,RoleVoter看到‘ROLE_ANONYMOUS’设置就授权访问,类似AuthenticatedVoter 看到‘IS_AUTHENTICATED_ANONYMOUSLY’.授权访问。

假设你要分配给匿名用户不同的角色名,则可以覆盖默认的配置如下:

<sec:http>
  <sec:intercept-url pattern="/login.jsp*" filters="none"/>
  <sec:intercept-url pattern="/*" access="ROLE_USER"/>

  <!-- Defines a custom role in place of ROLE_ANONYMOUS. 
  ROLE_ANONYMOUS will no more work, use ROLE_GUEST instead of it-->
  <sec:anonymous username="guest" granted-authority="ROLE_GUEST" />
</sec:http>

<p style="text-align: justify;">Here is the how the underlying filter can be defined if you don't want to use ?anonymous? element:</p>

1
<sec:custom-filter position="ANONYMOUS_FILTER" ref="anonymousFilter" />
<bean id="anonymousFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter" >
    <property name="userAttribute" value="ROLE_GUEST" />
</bean>

记得我Remember-me验证

  这是指网站能够记住会话之间主要的标识。Spring通过发送一个cookie到成功进行身份验证的浏览器,在Cookie中是如下:

  base64(username + “:” + expirationTime + “:” + md5Hex(username + “:” + expirationTime + “:” password + “:” + key));

  当浏览器发出下一个请求到服务器,它也随之发送这个cookie。Spring执行以下操作:
(一)从后端为给定的用户名获取密码
(二)根据uername获取从数据库中pasword,并计算用户名 密码,expirationTime和key的md5Hex,并比较它在cookie的值
(三)如果它们匹配 - 成功登录!如果不匹配,那么你已经提供的伪造Cookie或用户名/密码/密钥中的一个发生了变化。

激活 remember-me 通过http下面的配置:

<sec:http>

<!-- Other filter declarations here -->

<sec:remember-me key="myAppKey"/>

</sec:http>

有一点要注意的是,有一个潜在的安全问题在这里,令牌可以被捕获并可能被滥用,因为它是有效的,直到它过期。使用滚动令牌可避免。这里是如何实现基于令牌的记得我的服务:

<sec:http access-decision-manager-ref="accessDecisionManager">

   <!-- Other filter declarations here -->

   <remember-me services-alias="rememberMeService" data-source-ref="dataSource"/>
   <!-- <remember-me data-source-ref="dataSource" key="pramati"/> -->

</sec:http>

<bean id="tokenRepository" class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
   <property name="dataSource" ref="dataSource"/>
   <property name="createTableOnStartup" value="true"/>
</bean>

<bean id="rememberMeService" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
   <property name="userDetailsService" ref="userDetailsService"/>
   <property name="tokenRepository" ref="tokenRepository"/>
</bean>

( a)在数据库中必须创建新表persistent_logins,因为我们已经指定“ createTableOnStartup '在构造” tokenRepository “时 。下面是SQL创建表:

create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null);

( b)我们没有更多的定义我们自己的安全令牌。 Spring会自动生成令牌,并把persistent_tokens表更新。当用户从一个浏览器通过选择“记住我”的选项登录时,创建本表的一条记录,下一次用户从同一个浏览器登录时,用户会被自动记录,并将在数据库中的令牌值更改为一个新的值,但该系列值保持不变。假设现在用户从不同的浏览器转而选择记住我登录时,一个新的记录将为该浏览器创建。当他访问从浏览器访问应用程序时,后续更新会发生。

  因此,使用这种方法的好处是,攻击者将只能使用一个偷来的cookie,一直到受害者用户下一次访问应用程序之前,而不是非动态令牌时需要记住cookie的完整生命周期。当受害者接下来访问网站,他将使用相同的cookie 。这时 Spring会抛出一个CookieTheftException ,它可以用来通知盗窃发生的用户。

下面是为安全链的自定义过滤器

<sec:custom-filter position="REMEMBER_ME_FILTER" ref="rememberMeFilter" />

<bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
  <property name="rememberMeServices" ref="rememberMeServices"/>
  <property name="authenticationManager" ref="theAuthenticationManager" />
</bean>

<bean id="tokenRepository" class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
   <property name="dataSource" ref="dataSource"/>
   <property name="createTableOnStartup" value="false"/>
</bean>

<bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
   <property name="userDetailsService" ref="userDetailsService"/>
   <property name="tokenRepository" ref="tokenRepository"/>
</bean>

<bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.rememberme.RememberMeAuthenticationProvider"/>

 

并发会发管理

假设我们不希望用户在应用的同时多个地方登录,下面是我们如何做到这一点:

<sec:http>

  <!-- Other filter declarations here -->

  <sec:session-management session-authentication-error-url="/login.jsp?error=alreadyLoggedin" >
    <sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true"
              expired-url="/login.jsp?error=alreadyLoggedin"/>
  </sec:session-management>
</sec:http>

或者我们在web.xml中定义监听者,当用户登出以后发出一个事件org.springframework.security.core.session.SessionDestroyedEvent:

<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>

Spring使用类似于我们一直在讨论的安全过滤器。除此之外,还采用ApplicationEvents。当Spring看到所需要的并发控制,它可以维持与主要关联会话列表。Map结构看起来像(在org.springframework.security.core.session.SessionRegistryImpl实际定义):

ConcurrentMap<Object,Set<String>> principals =
new ConcurrentHashMap<Object,Set<String>>();

这里映射的键是用户对象,值设置为与它相关联的会话ID。所以,当这个数据集的大小超过在<concurrency-control>元素中定义MAX-会话的值将引发异常。当Spring看到定义的并发控制元件,SessionRegistryImpl(Map的定义地方)将其内部ConcurrentSessionControlStrategy注入UsernamePasswordAuthenticationFilter。当用户认证成功,Spring放入Map一条记录。

当用户注销时,在web.xml中定义的侦听器将发出SessionDestroyedEvent,SessionRegistryImpl侦听此事件,并从维持map中删除会话ID条目。没有删除掉,用户将永远无法重新登录,即使他们登出另一个会话或会话超时。因此,这里是<concurrency-control>等效的配置:

<sec:http>
  <sec:custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
  <sec:custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />

  <!-- Other filter declarations here -->

  <sec:session-management session-authentication-strategy-ref="sessionAuthenticationStrategy"/>
</sec:http>

<bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
 <property name="sessionRegistry" ref="sessionRegistry" />
 <property name="expiredUrl" value="/session-expired.htm" />
</bean>

<bean id="myAuthFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
  <property name="sessionAuthenticationStrategy" ref="sessionAuthenticationStrategy" />
  <property name="authenticationManager" ref="authenticationManager" />
</bean>

<bean id="sessionAuthenticationStrategy" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
  <constructor-arg name="sessionRegistry" ref="sessionRegistry" />
  <property name="maximumSessions" value="1" />
</bean>

<bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />


  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值