Spring Security详解

发布时间:

SpringSecurity详解
在认识SpringSecurity之前,所有的权限验证逻辑都混杂在业务逻辑中,用户的每个操作以前可能都需要对用户是否有进行该项操作的权限进行判断,达到认证授权的目的。类似这样的权限验证逻辑代码被分散在系统的许多地方,难以维护。AOP(AspectOrientedProgramming)和SpringSecurity为我们的应用程序很好的解决了此类问题,正如系统日志,事务管理等这些系统级的服务一样,我们应该将它作为系统一个单独的“切面”进行管理,以达到业务逻辑与系统级的服务真正分离的目的,SpringSecurity将系统的安全逻辑从业务中分离出来。
本文代码运行环境:JDK6.0
spring-framework-2.5.4spring-security-2.0.0
JavaEE5
Web容器:ApacheTomcat6.0IDE工具:
Eclipse3.3+MyEclipse6.5操作系统:Linux(Fedora8)
这只是我个人的学习总结而已,还请高手们指出本文的不足之处。
SpringSecurity简介
这里提到的SpringSecurity也就是被大家广为熟悉的AcegiSecurity,
2007年底AcegiSecurity正式成为SpringPortfolio项目,并更名为SpringSecurity.SpringSecurity是一个能够为基于Spring的企业应用系统提供描述安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了SpringIoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

通过在许多项目中实践应用以及社区的贡献,如今的SpringSecurity
已经成为SpringFramework下最成熟的安全系统,它为我们提供了强大而灵活的企业级安全服务,如:

认证授权机制Web资源访问控制业务方法调用访问控制
领域对象访问控制AccessControlList(ACL)单点登录(CentralAuthenticationService)X509认证
信道安全(ChannelSecurity)管理等功能
当保护Web资源时,SpringSecurity使用Servlet过滤器来拦截Http
请求进行身份验证并强制安全性,以确保WEB资源被安全的访问。如下图是SpringSecurity的主要组件图(摘自《SpringinAction》):
1SpringSecurity的基本组件
无论是保护WEB资源还是保护业务方法或者领域对象,SpringSecurity
都的通过上图中的组件来完成的。本文主要阐述如何使用SpringSecurityWEB应用程序的资源进行安全访问控制,并通过一个简单的实例来对SpringSecurity提供的各种过滤器的功能和配置方法进行描述。
保护Web资源
SpringSecurity提供了很多的过滤器,它们拦截Servlet请求,并将
这些请求转交给认证处理过滤器和访问决策过滤器进行处理,并强制安全性,证用户身份和用户权限以达到保护Web资源的目的。对于Web资源我们大约可以

只用6个过滤器来保护我们的应用系统,下表列出了这些安全过滤器的名称作用以及它们在系统中的执行顺序:




确保请求是在安全通道(HTTPHTTPS)之上传输的接受认证请求,并将它们转交给认证管理器进行身份验证
接受CAS服务票据,验证YaleCAS(单点登录)是否已经对用户进行了认证处理使用HTTP基本认证的身份验证请求
处理认证信息在请求间的存储(比如在HTTP会话中)
确保用户己经认证,并且满足访问一个受保护Web资源的权限需求

通道处理过滤器认证处理过滤器CAS处理过滤器HTTP基本授权过滤器集成过滤器安全强制过滤器
接下来,通过一个实例来说明它们的具体使用方法和如何在Spring中进
行配置。
1
建立SpringSecurity项目
首先在MyEclipse中创建一个WebProject,并使用MyEclipse工具导
Spring项目的依赖JAR包,并生成默认的,这里暂时不会用到这个文件,本文只是通过一个简单的实例来说明如何配置使用SpringSecurity,不会涉及到数据库,而是使用一个用户属性(users.properties)文件来保存用户信息(包括用户名,密码及相应的权限),但在实际的项目中,我们很少会这样做,而是应该把用户信息存在数据库中,下一篇文章中将会详细介绍并用到这个文件来配Hibernate,这里我们保留它。
现在还需要为项目导入SpringSecurityJAR包,它没有包括在Spring
Framework中,你可以从http://www.springframework.org/download/下载,并spring-security-core-2.0.0.jar(这是核心代码库)和
spring-security-core-tiger-2.0.0.jar(和annotation有关的,比如使用注解对方法进行安全访问控制,在下一篇中会用到)拷贝到项目的lib目录下,中也包括两个实例(tutorialcontacts),并且两个实例中都包括了如何使Spring2.0的命名空间来配置SpringSecurity,无论你对Spring2.0命名空间的使用是否了解,它将使我们的配置文件大大缩短,简化开发,提高生产效率。到此,我们的SpringSecurity项目就建好了,项目目录结构如下图所示:

2项目目录结构
2配置web.xml
SpringSecurity使用一组过滤器链来对用户进行身份验证和授权。首
先,在web.xml文件中添加FilterToBeanProxy过滤器配置:
1234567891011
12filter>13
init-param>
<filter>
<filter-name>springSecurityFilterChainfilter-name><filter-class>
org.springframework.security.util.FilterToBeanProxyfilter-class><init-param>
<param-name>targetClassparam-name><param-value>
org.springframework.security.util.FilterChainProxy
param-value>
org.springframework.security.util.FilterToBeanProxy实现了
Filter接口,它通过调用WebapplicationContextUtils类的getWebApplicationnContext(servletContext)方法来获取Spring的应用上下文句柄,并通过getBean(beanName)方法来获取Spring受管Bean的对象,即

这里targetClass参数配置的Bean,并通过调用FilterChainProxyinit()方法来启动SpringSecurity过滤器链进行各种身份验证和授权服务(FilterChainProxy类也是实现了Filter接口)从而将过滤功能委托给SpringFilterChainProxy受管Bean(它维护着一个处理验证和授权的过滤器列表,列表中的过滤器按照一定的顺序执行并完成认证过程),这样即简化了web.xml文件的配置,又能充分利用SpringIoC功能来完成这些过滤器执行所需要的其它资源的注入。
当用户发出请求,过滤器需要根据web.xml配置的请求映射地址来拦截
用户请求,这时SpringSecurity开始工作,它会验证你的身份以及当前请求的资源是否与你拥有的权限相符,从而达到保护Web资源的功能,下面是本例所要过滤的用户请求地址:
1234567891011121314
15filter-mapping>
<url-pattern>/*url-pattern>
<filter-name>springSecurityFilterChainfilter-name><filter-mapping>filter-mapping>
<url-pattern>/j_spring_security_checkurl-pattern><filter-name>springSecurityFilterChainfilter-name>
<filter-mapping>
3配置applicationContext-security.xml
3.1FilterChainProxy过滤器链
FilterChainProxy会按顺序来调用一组filter,使这些filter即能完
成验证授权的本质工作,又能享用SpringIoc的功能来方便的得到其它依赖的资源。FilterChainProxy配置如下:
12345
<bean
id="filterChainProxy"
class="org.springframework.security.util.FilterChainProxy"><propertyname="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISONPATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderA

wareRequestFilter,6
exceptionTranslationFilter,78910
bean>
property>
filterSecurityInterceptor
]]>value>
rememberMeProcessingFilter,anonymousProcessingFilter,
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON定义URL在匹配之前
必须先转为小写,PATTERN_TYPE_APACHE_ANT定义了使用Apacheant的匹配模式,/**定义的将等号后面的过滤器应用在那些URL上,这里使用全部URL过滤,每个过滤器之间都适用逗号分隔,它们按照一定的顺序排列。
提示:
特别需要注意的是,即使你配置了系统提供的所有过滤器,这个过滤器链会很长,但是千万不要使用换行,否则它们不会正常工作,容器甚至不能正常启动。
下面根据FilterChainProxy的配置来介绍各个过滤器的配置,各个过滤
器的执行顺序如以上配置。
首先是通道处理过滤器,如果你需要使用HTTPS,这里我们就使用HTTP
进行传输,所以不需要配置通道处理过滤器,然后是集成过滤器,配置如下:
123/>
class="org.springframework.security.context.HttpSessionContextIntegrationFilter"<bean
id="httpSessionContextIntegrationFilter"
httpSessionContextIntegrationFilter是集成过滤器的一个实现,在
用户的一个请求过程中,用户的认证信息通过SecurityContextHolder(使用ThreadLoacl实现)进行传递的,所有的过滤器都是通过SecurityContextHolder来获取用户的认证信息,从而在一次请求中所有过滤器都能共享
Authentication(认证),减少了HttpRequest参数的传送,下面的代码是从安全上下文的获取Authentication对象的方法:
123
Authentication
authentication=
context.getAuthentication(;
SecurityContextcontext
=
SecurityContextHolder.getContext(;
但是,ThreadLoacl不能跨越多个请求存在,所以,集成过滤器在请求
开始时从Http会话中取出用户认证信息并创建一个SecurityContextHolderAuthentication对象保存在其中,在请求结束之后,在从

SecurityContextHolder中获取Authentication对象并将其放回Http会话中,共下次请求使用,从而达到了跨越多个请求的目的。集成过滤器还有其它的实现,可以参考相关文档。
提示:
集成过滤器必须在其它过滤器之前被使用。
logoutFilter(退出过滤器),退出登录操作:
123456789101112131415161718192021
bean>
constructor-arg>
list>
<bean
class="org.springframework.security.ui.logout.Secu
rityContextLogoutHandler"/>
<refbean="rememberMeServices"/>
<list>
<constructor-arg><constructor-arg
value="/index.jsp"/>
class="org.springframework.security.ui.logout.LogoutFilter">
<bean
id="logoutFilter"
LogoutFilter的构造函数需要两个参数,第一个是退出系统后系统跳转到的URL,第二个是一个LogoutHandler类型的数组,这个数组里的对象都实现了LogoutHandler接口,并实现了它的logout方法,用户在发送退出请求后,会一次执行LogoutHandler数组的对象并调用它们的logout方法进行一些后续的清理操作,主要是从SecurityContextHolder对象中清楚所有用户的认证信息(Authentication对象),将用户的会话对象设为无效,这些都时由
SecurityContextLogoutHandler来完成。LogoutFilter还会清除Cookie记录,它由另外一个Bean来完成(RememberMeServices)。
bean="rememberMeServices"/>标记指向了我们另外配置的一个
Bean:

1<beanid="rememberMeServices"
class="org.springframework.security.ui.rememberme.TokenBasedRememberM
eServices"23
p:key="springsecurity"
p:userDetailsService-ref="userDetailsService"/>
TokenBasedRememberMeServices继承自系统的
AbstractRememberMeServices抽象类(实现了RememberMeServicesLogoutHandler两个接口)RememberMeServices接口的loginSuccess方法负责在用户成功登录之后将用户的认证信息存入Cookie中,这个类在后续的过滤器执行过程中也会被用到。
另一个userDetailsService属性也是指向了我们配置的Bean,它负责
从数据库中读取用户的信息,这个类的详细配置将在后面的部分详细介绍,这里只是简单的认识一下。
过滤器链的下个配置的过滤器是authenticationProcessingFilter(认
证过程过滤器)我们使用它来处理表单认证,当接受到与filterProcessesUrl所定义相同的请求时它开始工作:
123Filter"456789
p:authenticationManager-ref="authenticationManager"p:authenticationFailureUrl="/login.jsp?login_error=1"p:defaultTargetUrl="/default.jsp"
p:filterProcessesUrl="/j_spring_security_check"p:rememberMeServices-ref="rememberMeServices"/>
class="org.springframework.security.ui.webapp.AuthenticationProcessing
<bean
id="authenticationProcessingFilter"
下面列出了认证过程过滤器配置中各个属性的功能:1.authenticationManager
认证管理器
2.authenticationFailureUrl定义登录失败时转向的页面3.defaultTargetUrl
4.filterProcessesUrl
web.xml中配置过)
5.rememberMeServices
定义登录成功时转向的页面定义登录请求的地址(在
在验证成功后添加cookie信息

这里也用到了rememberMeServices,如果用户认证成功,将调用
RememberMeServicesloginSuccess方法将用户认证信息写入Cookie中,这里也可以看到使用IoC的好处。
决定用户是否有权限访问受保护资源的第一步就是要确定用户的身份,
最常用的方式就是用户提供一个用户名和密码以确认用户的身份是否合法,这一步就是由认证过程过滤器调用authenticationManager(认证管理器)来完成的。org.springframework.security.AuthenticationManager接口定义了一个
authenticate方法,它使用Authentication作为入口参数(只包含用户名和密码),并在验证成功后返回一个完整的Authentication对象(包含用户的权限信息GrantedAuthority数组对象),authenticationProcessingFilter(认证过程过滤器)会将这个完整的Authentication对象存入SecurityContext中,如果认证失败会抛出一个AuthenticationException并跳转到authenticationFailureUrl定义的URL.认证管理其配置如下:
123456789101112131415161718
bean>
list>
property>
class="org.springframework.security.providers.rememberme.RememberMeAuthenticati
p:key="springsecurity"/>
onProvider"
<bean
class="org.springframework.security.providers.anonymous.AnonymousAuthentication
p:key="springsecurity"/>
Provider"
class="org.springframework.security.providers.ProviderManager"p:sessionController-ref="concurrentSessionController">
<property
name="providers">
<ref<bean
bean="daoAuthenticationProvider"/>
<list>
<bean
id="authenticationManager"
正如在配置中看到的一样,系统使用
org.springframework.security.providers.ProviderManager(提供者管理器)类作为认证管理器的一个实现,事实上这个类是继承自实现了
AuthenticationManager接口的AbstractAuthenticationManager类。需要注意的是ProviderManager(提供者管理器)自己并不实现身份验证,而是把这项工作交给了多个认证提供者(提供者集合)或者说的多个认证来源。提示:

SpringSecurity为我们提供的所有认证提供者实现都是
org.springframework.security.providers.AuthenticationProvider
接口的实现类,它们都实现了此接口的authenticate方法,如果你正在看源代码,会发现这个authenticate方法事实上和Authe
nticationManager(认证管理器)接口的authenticate方法完全一样。providers属性定义了提供者管理器的集合,ProviderManager(提供者
管理器)逐一遍历这个认证提供者的集合并调用提供者的authenticate方法,如果一个提供者认证失败会尝试另外一个提供者直到某一个认证提供者能够成功的验证该用户的身份,以保证获取不同来源的身份认证。下面表格列出了系统提供的一些认证提供者:

DaoAuthenticationProvider
AnonymousAuthenticationProviderRememberMeAuthenticationProviderAuthByAdapterProvider
CasAuthenticationProviderJaasAuthenticationProviderRemoteAuthenticationProviderRunAsImplAuthenticationProviderX509AuthenticationProviderTestingAuthenticationProvider
从数据库中读取用户信息验证身份匿名用户身份认证
已存cookie中的用户信息身份认证使用容器的适配器验证身份
根据Yale中心认证服务验证身份,用于实现单点登陆
JASS登陆配置中获取用户信息验证身
根据远程服务验证用户身份
对身份已被管理器替换的用户进行验证X509认证中获取用户信息验证身份单元测试时使用
从上面的表中可以看出,系统为我们提供了不同的认证提供者,每个认证提供者会对自己指定的证明信息进行认证,如DaoAuthenticationProvider仅对UsernamePasswordAuthenticationToken这个证明信息进行认证。
在实际项目中,用户的身份和权限信息可能存储在不同的安全系统中(如
数据库,LDAP服务器,CA中心)。
作为程序员,我们可以根据需要选择不同的AuthenticationProvider
(认证提供者)来对自己的系统提供认证服务。
这里我们着重介绍DaoAuthenticationProvider,它从数据库中读取用
户信息验证身份,配置如下:
1
<bean
id="daoAuthenticationProvider"
class="org.springframework.security.providers.dao.DaoAuthenticationPr
ovider"23
p:passwordEncoder-ref="passwordEncoder"p:userDetailsService-ref="userDetailsService"/>

45/>
<beanid="passwordEncoder"
class="org.springframework.security.providers.encoding.Md5PasswordEncoder"
还记得前面配置的RememberMeServices吗?它也有一个和
DaoAuthenticationProvider同样的属性userDetailsService,这是系统提供的一个接口
(org.springframework.security.userdetails.UserDetailsService),在这里我们把它单独提出来进行介绍。
首先我们需要了解SpringSecurity为我们提供的另外一个重要的组件,
org.springframework.security.userdetails.UserDetails接口,它代表一个应用系统的用户,该接口定义与用户安全信息相关的方法:
StringgetUsername():获取用户名;StringgetPassword():获取密码;
booleanisAccountNonExpired():用户帐号是否过期;booleanisAccountNonLocked():用户帐号是否锁定;booleanisCredentialsNonExpired():用户的凭证是否过期;booleanisEnabled():用户是否处于激活状态。
当以上任何一个判断用户状态的方法都返回false时,用户凭证就被视
为无效。UserDetails接口还定义了获取用户权限信息的getAuthorities()法,该方法返回一个GrantedAuthority[]数组对象,GrantedAuthority是用户权限信息对象,这个对象中定义了一个获取用户权限描述信息的getAuthority()方法。
UserDetails即可从数据库中返回,也可以从其它如LDAP中返回,这取
决与你的系统中使用什么来存储用户信息和权限以及相应的认证提供者。这里我们只重点介绍DaoAuthenticationProvider(从数据库中获取用户认证信息的提供者),本人水平有限,在项目中还没有机会用到其它提供者。说到这里,这个封装了用户详细信息的UserDetails该从哪儿获取呢?这就是我们接下来要介绍的UserDetailsService接口,这个接口中只定义了唯一的UserDetailsloadUserByUsername(Stringusername)方法,它通过用户名来获取整个UserDetails对象。
看到这里你可能会有些糊涂,因为前面提到的Authentication对象中也
存放了用户的认证信息,需要注意Authentication对象才是SpringSecurity使用的进行安全访问控制用户信息安全对象。实际上,Authentication对象有未认证和已认证两种状态,在作为参数传入认证管理器

(AuthenticationManager)authenticate方法时,是一个未认证的对象,从客户端获取用户的身份信息(如用户名,密码),可以是从一个登录页面,也可以从Cookie中获取,并由系统自动构造成一个Authentication对象。而这里提到的UserDetails代表一个用户安全信息的源(从数据库,LDAP服务器,CA中心返回),SpringSecurity要做的就是将这个未认证的Authentication象和UserDetails进行匹配,成功后将UserDetails中的用户权限信息拷贝到Authentication中组成一个完整的Authentication对象,共其它组件共享。这样,我们就可以在系统中获取用户的相关信息了,需要使用到
Authentication对象定义的ObjectgetPrincipal()方法,这个方法返回一个Object类型的对象,通常可以将它转换为UserDetails,从而可以获取用户名,密码以及权限等信息。代码如下:
123
GrantedAuthority[]authority
=
details.getAuthorities(;
UserDetails
details
=
(UserDetailsauthentication.getPrincipal(;
前面介绍了DaoAuthenticationProvider,它可以从数据库中读取用户
信息,同样也可以从一个用户属性文件中读取,下一篇文章中我们在介绍如何从数据库中读取用户信息,当然还会涉及到更深入的东西,比如根据自己系统的需要自定义UserDetailsUserDetailsService,这个只是让你对整个系统有个简单的了解,所以我们使用用户属性文件(users.properties)来存储用户信息:
1234567
user3=user3,disabled,ROLE_USERuser2=user2,ROLE_USERuser1=user1,ROLE_USERadmin=admin,ROLE_SUPERVISOR
配置userDetailsService:
12345
sFactoryBean"67
8bean>
p:location="/WEB-INF/users.properties"/>
property>
class="org.springframework.security.userdetails.memory.InMemoryDaoImpl">
<property
<bean
name="userProperties">
class="org.springframework.beans.factory.config.Propertie
<bean
id="userDetailsService"
InMemoryDaoImpl类是UserDetailsService接口的一个实现,它从属性文件里读取用户信息,SpringSecurity使用一个属性编辑器将用户信息为我们

组织成一个org.springframework.security.userdetails.memory.UserMap的对象,我们也可以直接为它提供一个用户权限信息的列表,详见applicationContext-security.xml配置文件。
UserMap字符串的每一行都用键值对的形式表示,前面是用户名,然后
是等号,后面是赋予该用户的密码/权限等信息,它们使用逗号隔开。比如:
1
admin=admin,ROLE_SUPERVISOR
定义了一个名为admin的用户登录密码为admin,该用户拥有
ROLE_SUPERVISOR权限,再如users.properties文件中配置的名为user3的用户登录密码为user3,该用户拥有ROLE_USER权限,disabled定义该用户不可用,为被激活(UserDetailsisEnabled方法)。
即使是系统的开发者或者说是最终用户,都不应该看到系统中有明文的密码。所以,SpringSecurity考虑的还是很周到的,为我们提供的密码加密的功能。正如你在Dao认证提供者(DaoAuthenticationProvider)中看到的,
passwordEncoder属性配置的就是一个密码加密程序(密码编码器)。这里我们使用MD5加密,可以看配置文件中的scott用户,你还能看出他的密码是什么吗?当然这里只是演示功能,其它用户还是没有改变,你可以自己试试。系统为我们提供了一些常用的密码编码器(这些编码器都位于org.springframework.security.providers.encoding包下):
PlaintextPasswordEncoder(默认)——不对密码进行编码,直接返回
未经改变的密码;
Md4PasswordEncoder——对密码进行消息摘要(MD4)编码;Md5PasswordEncoder——对密码进行消息摘要(MD5)编码;ShaPasswordEncoder——对密码进行安全哈希算法(SHA)编码。
你可以根据需要选择合适的密码编码器,你也可以设置编码器的种子源
(saltsource)。一个种子源为编码提供种子(salt),或者称编码的密钥,这里不再赘述。
这里附加介绍了不少东西,希望你还没有忘记在
AuthenticationManager(认证管理器)中还配置了一个名为sessionControllerBean,这个Bean可以阻止用户在进行了一次成功登录以后在进行一次成功的登录。applicationContext-security.xml配置文件添加sessionController的配置:
123
class="org.springframework.security.concurrent.ConcurrentSessionControllerImpl"<bean
id="concurrentSessionController"

456789
<bean
p:maximumSessions="1"
p:exceptionIfMaximumExceeded="true"p:sessionRegistry-ref="sessionRegistry"/>id="sessionRegistry"
class="org.springframework.security.concurrent.SessionRegistryImpl"/>
maximumSessions属性配置了只允许同一个用户登录系统一次,
exceptionIfMaximumExceeded属性配置了在进行第二次登录是是否让第一次登录失效。这里设置为true不允许第二次登录。要让此功能生效,我们还需要在web.xml文件中添加一个监听器,以让SpringSecurity能获取Session的生命周期事件,配置如下:
1234
5listener>
<listener>
<listener-class>
org.springframework.security.ui.session.HttpSessionEventPublisherlistener-class>
HttpSessionEventPublisher类实现
javax.servlet.http.HttpSessionListener接口,在Session被创建的时候通过调用ApplicationContextpublishEvent(ApplicationEventevent)发布HttpSessionCreatedEvent类型的事件,HttpSessionCreatedEvent类继承自org.springframework.context.ApplicationEvent类的子类HttpSessionApplicationEvent抽象类。
concurrentSessionController使用sessionRegistry来完成对发布的
Session的生命周期事件的处理,
org.springframework.security.concurrent.SessionRegistryImpl(实现了SessionRegistry接口)SessionRegistryImpl类还实现了SpringFramework的事件监听org.springframework.context.ApplicationListener接口,并实现了该接口定义的onApplicationEvent(ApplicationEventevent)方法用于处理ApplicationEvent类型的事件,如果你了解SpringFramework的事件处理,那么这里你应该可以很好的理解。
认证管理器到此介绍完毕了,认证过程过滤器也介绍完了,接下来我们
继续介绍过滤器链的下一个过滤器
securityContextHolderAwareRequestFilter:
12
<bean
id="securityContextHolderAwareRequestFilter"
class="org.springframework.security.wrapper.SecurityContextHolderAwareRequ
estFilter"/>
这个过滤器使用装饰模式(DecorateModel),装饰的
HttpServletRequest对象。其WapperServletRequest包装类

HttpServletRequestWrapper的子类(如SavedRequestAwareWrapperSecurityContextHolderAwareRequestWrapper),附上获取用户权限信息,request参数,headerscookies的方法。
rememberMeProcessingFilter过滤器配置:id="rememberMeProcessingFilter"
class="org.springframework.security.ui.rememberme.RememberMePr
ocessingFilter"
p:authenticationManager-ref="authenticationManager"p:rememberMeServices-ref="rememberMeServices"/>
SecurityContextHolder中不存在Authentication用户授权信息时,
rememberMeProcessingFilter就会调用rememberMeServicesautoLogin()方法从cookie中获取用户信息自动登录。
anonymousProcessingFilter过滤器配置:
12gFilter"34
p:key="springsecurity"
p:userAttribute="anonymousUser,ROLE_ANONYMOUS"/>
<bean
id="anonymousProcessingFilter"
class="org.springframework.security.providers.anonymous.AnonymousProcessin
如果不存在任何授权信息时,自动添加匿名用户身份至
SecurityContextHolder中,就是这里配置的userAttribute,系统为用户分配一个ROLE_ANONYMOUS权限。
exceptionTranslationFilter(异常处理过滤器),该过滤器用来处理
在系统认证授权过程中抛出的异常,主要是处理AccessDeniedExceptionAuthenticationException两个异常并根据配置跳转到不同URL:
12345789<bean10

id="accessDeniedHandler"
class="org.springframework.security.ui.AccessDeniedHandlerImpl"p:errorPage="/accessDenied.jsp"/>id="authenticationEntryPoint"
class="org.springframework.security.ui.webapp.AuthenticationProcessingFi

lterEntryPoint"1112
p:loginFormUrl="/login.jsp"p:forceHttps="false"/>
accessDeniedHandler用于处理AccessDeniedException异常,当用户没有权限访问当前请求的资源时抛出此异常,并跳转自这里配置的/accessDenied.jsp面。
authenticationEntryPoint(认证入口点),这里定义了用户登录的页
面。系统为我们提供了3个认证入口点的实现:
BasicProcessingFilterEntryPoint
AuthenticationProcessingFilterEntryPointCasProcessingFilterEntryPoint

通过向浏览器发送一个HTTP401(未授权消浏览器弹出登录对话框,提示用户登录将用户重定向到一个基于HTML表单的登录将用户重定向至一个YaleCAS登录页面
这里我们使用AuthenticationProcessingFilterEntryPoint认证入口
点,提供给用户一个友好的登录界面,只是为了给用户更好的体验。
filterSecurityInterceptor(过滤器安全拦截器)该过滤器首先调用
认证管理器来判断用户是否已被成功验证,如果没有被验证则重定向到登录界面。否则,从Authentication获取用户的权限信息,然后从objectDefinitionSource中获取URL所对应的权限,最后调用
accessDecisionManager(访问决策管理器)来判断用户当前拥有的权限是否与当前受保护的URL资源对应的权限匹配,如果匹配就可以访问该URL资源,否则将抛出AccessDeniedException异常并返回客户端浏览器一个403错误(如果用户定义了accessDenied页面则会被重定向到该页,见:异常处理过滤器
exceptionTranslationFilter中配置的accessDeniedHandlerBean),访问决策管理的的工作机制将在随后更详细介绍,这里先给出过滤器安全拦截器的配置如下:
123tor"456789101112
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
p:accessDecisionManager-ref="accessDecisionManager">
<propertyname="objectDefinitionSource">
<value>
p:authenticationManager-ref="authenticationManager"
class="org.springframework.security.intercept.web.FilterSecurityIntercep
<bean
id="filterSecurityInterceptor"

131415161718
19bean>
property>
/admins/**=ROLE_SUPERVISOR
/user/**=ROLE_USER,IS_AUTHENTICATED_REMEMBERED/default.jsp=ROLE_USER,IS_AUTHENTICATED_REMEMBERED/**=IS_AUTHENTICATED_ANONYMOUSLY
]]>value>
从配置可以看出来,过滤器安全拦截器用到了我们前面配置的认证管理
器,过滤器安全拦截器使用authenticationManager并调用它的providers(提供者列表)来对用户的身份进行验证并获取用户拥有的权限。如果用户被成功认证,过滤器安全拦截器将会使用accessDecisionManager(访问决策管理器)来判断已认证的用户是否有权限访问受保护的资源,这些受保护的资源由objectDefinitionSource属性定义。
访问决策管理器(accessDecisionManager):
123456r"/>7
catedVoter"/>8910
bean>
list>
property>
<bean
class="org.springframework.security.vote.Authenti
<bean
id="accessDecisionManager"
class="org.springframework.security.vote.AffirmativeBased"
p:allowIfAllAbstainDecisions="false"><propertyname="decisionVoters">
<list>
<bean
class="org.springframework.security.vote.RoleVote
身份验证只是SpringSecurity安全机制的第一步,访问决策管理器验证用户是否有权限访问相应的资源(filterSecurityInterceptor
objectDefinitionSource属性定义的访问URL需要的属性信息)。
org.springframework.security.AccessDecisionManager接口定义了
用于验证用户是否有权限访问受保护资源的decide方法,另一个supports方法根据受保护资源的配置属性(即访问这些资源所需的权限)来判断该访问决策管理器是否能做出针对该资源的访问决策。decide方法最终决定用户有无访问权限,如果没有则抛出AccessDeniedException异常(面前也提到过,你应该在回过头去看看)。
与认证管理器类似,访问决策管理器也不是由自己来实现访问控制的,
而是通过一组投票者来投票决定(通过调用投票者的vote方法),访问决策管

理器统计投票结果并最终完成决策工作。下表列出了系统提供的3个访问决策管理器的实现:
访问决策管理器AffirmativeBasedConsensusBasedUnanimousBased

当至少有一个投票者投允许访问票时允许访
当所有投票者都投允许访问票时允许访问当没有投票者投拒绝访问票时允许访问
decisionVoters属性为访问决策管理器定义了一组进行投票工作的投
票者,那么这些投票者是如何进行投票的呢?这就需要提
org.springframework.security.vote.AccessDecisionVoter接口,所有的投票者都实现了这个接口并实现了其中的vote方法。该接口中还定义了3int型的常量:
intACCESS_GRANTED=1;(投赞成票)intACCESS_ABSTAIN=0;(投弃权票)intACCESS_DENIED=-1;(投反对票)
每个决策投票者都返回这3个常量中一个,这取决与用户是否有权限访
问当前请求的资源,访问决策管理器再对这些投票结果进行统计。认证投票者的配置如上面所示。
loggerListener是一个可选项,它和我们前面配置的Bean或者过滤器
没有关系,只是监听系统的一些事件(实现了ApplicationListener监听接口)被它监听的事件包括AuthenticationCredentialsNotFoundEvent事件,AuthorizationFailureEvent事件,AuthorizedEvent事件,
PublicInvocationEvent事件,相信你从他们的名字就能看出来是一些什么样的事件,除非你的e文比我还差劲。loggerListener配置如下:
1
<bean
id="loggerListener"
class="org.springframework.security.event.authentica
tion.LoggerListener"/>
到此,本例所涉及到的所有配置都介绍完了,在下一篇中会介绍方法安
全拦截器,以及如何使用它来保护我们的方法调用,以及前面提到过的会在下一篇中介绍的,这里不在一一列出。
接下来就是JSP页面了,首先是login.jsp:
1234
<c:if
test="${not<font
emptyparam.login_error}">
登录失败,请重试。错误原因:<br/>
color="red"><c:if
test="${not
empty
SPRING_SECURITY_LAST_EXCEPTION}">

5out>678910111213e"
c:if><form
action="<table>
<tr>
font>
c:if>
<c:outvalue="${SPRING_SECURITY_LAST_EXCEPTION}">c:
value="/j_spring_security_check"/>"method="post">
<td><label<td><input
for="username">username:label>td>type="text"
id="username"name="j_usernam
value="value="${SPRING_SECURITY_LAST_USE
RNAME}"/>"/>td>14151617
sword"value=""/>td>181920
ember_me">两周内记住我td>2122232425
form>
tr>
<tr><tdcolspan="2"><input<input
table>
type="reset"
type="submit"
value="提交"/>
value="重置"/>td>tr>
tr>
<tr><td>td>
<td><input
type="checkbox"
name="_spring_security_rem
tr><tr>
<td><label<td><input
for="password">password:label>td>type="password"
id="password"
name="j_pas
如果你有看源代码,上面的某些参数,以及本文所有提及的东西你都不
应该感到陌生。其它页面也不在列出了,还有就是如何让它运行起来,这些我相信你都能自己搞定。
附件1:linux/springsecurity.rar">springsecurity.rar(不包括JAR
包)
补上使用命名空间配置实现的代码,命名空间的详细资料请参考Spring
Security中文参考文档,翻译得很好,这里就不在累述了,配置文件中也有比较详细的注释。另外例子中还包括了自定义UserDetailService的实现已经如何Ehcache缓存用户信息,详细的信息将在下一篇中讲述。

Spring Security详解

相关推荐