<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是在应用程序上下文的安全命名空间<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是如何完成以下功能:
- 验证: HttpBasicAuthentication 和表单登录验证
- 通过ACL实现授权控制
- 退出
- 匿名登录Anonymous Login support
- Remember-me 验证
- 并发会话管理。
验证
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" />