【总结-含源码】Spring Security学习总结一(补命名空间配置)
Posted on 2008-08-20 10:25 tangtb 阅读(43111) 评论(27) 编辑 收藏 所属分类: Spring 、 Spring Security
Spring Security学习总结一
在认识Spring Security以前,全部的权限验证逻辑都混杂在业务逻辑中,用户的每一个操做之前可能都须要对用户是否有进行该项 操做的权限进行判断,来达到认证受权的目的。相似这样的权限验证逻辑代码被分散在系统的许多地方,难以维护。AOP(Aspect Oriented Programming)和Spring Security为咱们的应用程序很好的解决了此类问题,正如系统日志,事务管理等这些系统级的服务同样,咱们应 该将它做为系统一个单独的”切面”进行管理,以达到业务逻辑与系统级的服务真正分离的目的,Spring Security将系统的安全逻辑 从业务中分离出来。html
本文代码运行环境:
java
JDK6.0linux
spring-framework-2.5.4程序员
spring-security-2.0.0web
JavaEE5算法
Web容器:spring
Apache Tomcat6.0数据库
IDE工具:数组
Eclipse3.3+MyEclipse6.5浏览器
操做系统:
Linux(Fedora 8)
这只是我我的的学习总结而已,还请高手们指出本文的不足之处。
一 Spring Security 简介
这里提到的Spring Security也就是被你们广为熟悉的Acegi Security,2007年末Acegi Security正式成为Spring Portfolio项目,并改名为Spring Security。Spring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架。 它提供了一组能够在Spring应用上下文中配置的Bean,充分利用了Spring IoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减小了为企业系统安全控制编写大量重复代码的工做。
经过在许多项目中实践应用以及社区的贡献,现在的Spring Security已经成为Spring Framework下最成熟的安全系统,它为咱们提供了强大而灵活的企业级安全服务,如:
Ø 认证受权机制
Ø Web资源访问控制
Ø 业务方法调用访问控制
Ø 领域对象访问控制Access Control List(ACL)
Ø 单点登陆(Central Authentication Service)
Ø X509认证
Ø 信道安全(Channel Security)管理等功能
当保护Web资源时,Spring Security使用Servlet 过滤器来拦截Http请求进行身份验证并强制安全性,以 确保WEB资源被安全的访问。以下图是Spring Security的主要组件图(摘自《Spring in Action》):
图1 Spring Security的基本组件
不管是保护WEB资源仍是保护业务方法或者领域对象,Spring Security都的经过上图中的组件来完成 的。本文主要阐述如何使用Spring Security对WEB应用程序的资源进行安全访问控制,并经过一个简单的 实例来对Spring Security提供的各类过滤器的功能和配置方法进行描述。
二 保护Web资源
Spring Security提供了不少的过滤器,它们拦截Servlet请求,并将这些请求转交给认证处理过滤器和访问决策过滤器进行处理,并强制安全性,认证用户身份和用户权限以达到保护Web资源的目的。对于Web资源咱们大约能够只用6个过滤器来保护咱们的应用系统,下表列出了这些安全过滤器的名称做用以及它们在系统中的执行顺序:
过 滤 器 |
做 用 |
通道处理过滤器 |
确保请求是在安全通道(HTTP和HTTPS)之上传输的 |
认证处理过滤器 |
接受认证请求,并将它们转交给认证管理器进行身份验证 |
CAS处理过滤器 |
接受CAS服务票据,验证Yale CAS(单点登陆)是否已经对用户进行了认证 |
HTTP基本受权过滤器 |
处理使用HTTP基本认证的身份验证请求 |
集成过滤器 |
处理认证信息在请求间的存储(好比在HTTP会话中) |
安全强制过滤器 |
确保用户己经认证,而且知足访问一个受保护Web资源的权限需求 |
接下来,经过一个实例来讲明它们的具体使用方法和如何在Spring中进行配置。
1 创建Spring Security项目
首先在MyEclipse中建立一个Web Project,并使用MyEclipse工具导入Spring项目的依赖JAR包,并生成默认的,这里暂时不会用到这个文件,本文只是经过一个简单的实例来讲明如何配置使用Spring Security,不会涉及到数据库,而是使用一个用户属性(users.properties)文件来保存用户信息(包括用户名,密码及相应的权限),但在实际的项目中,咱们不多会这样作,而是应该把用户信息存在数据库中,下一篇文章中将会详细介绍并用到这个文件来配置Hibernate,这里咱们保留它。
如今还须要为项目导入Spring Security的JAR包,它没有包括在Spring Framework中,你能够从http://www.springframework.org/download/下载,并将spring-security-core-2.0.0.jar(这是核心代码库)和spring-security-core-tiger-2.0.0.jar(和annotation有关的,好比使用注解对方法进行安全访问控制,在下一篇中会用到)拷贝到项目的lib目录下,其中也包括两个实例(tutorial和contacts),而且两个实例中都包括了如何使用Spring 2.0的命名空间来配置Spring Security,不管你对Spring 2.0命名空间的使用是否了解,它将使咱们的配置文件大大缩短,简化开发,提升生产效率。到此,咱们的Spring Security项目就建好了,项目目录结构以下图所示:
图2 项目目录结构
2 配置web.xml
Spring Security使用一组过滤器链来对用户进行身份验证和受权。首先,在web.xml文件中添加FilterToBeanProxy过滤器配置:
2 <filter-name>springSecurityFilterChain</filter-name>
3 <filter-class>
4 org.springframework.security.util.FilterToBeanProxy
5 </filter-class>
6 <init-param>
7 <param-name>targetClass</param-name>
8 <param-value>
9 org.springframework.security.util.FilterChainProxy
10 </param-value>
11 </init-param>
12 </filter>
13
org.springframework.security.util.FilterToBeanProxy实现了Filter接口,它经过调用WebapplicationContextUtils类的getWebApplicationnContext(servletContext)方法来获取Spring的应用上下文句柄,并经过getBean(beanName)方法来获取Spring受管Bean的对象,即这里targetClass参数配置的Bean,并经过调用FilterChain
Proxy的init()方法来启动Spring Security过滤器链进行各类身份验证和受权服务(FilterChainProxy类也是实现了Filter接口),从而将过滤功能委托给Spring的FilterChainProxy受管Bean(它维护着一个处理验证和受权的过滤器 列表,列表中的过滤器按照必定的顺序执行并完成认证过程),这样即简化了web.xml文件的配置,又能充分利用 Spring的IoC功能来完成这些过滤器执行所须要的其它资源的注入。
当用户发出请求,过滤器须要根据web.xml配置的请求映射地址来拦截用户请求,这时Spring Security开始工做,它会验证你的身份以及当前请求的资源是否与你拥有的权限相符,从而达到保护Web资源的功能,下面是本例所要过滤的用户请求地址:
2
3 <filter-name>springSecurityFilterChain</filter-name>
4
5 <url-pattern>/j_spring_security_check</url-pattern>
6
7 </filter-mapping>
8
9 <filter-mapping>
10
11 <filter-name>springSecurityFilterChain</filter-name>
12
13 <url-pattern>/*</url-pattern>
14
15 </filter-mapping>
提示: /j_spring_security_check是Spring Security默认的进行表单验证的过滤地址,你也能够修改成别的名称,可是须要和 applicationContext-security.xml中相对应,固然还会涉及到其它一些默认值(多是一个成员变量,也多是别的请 求地址),在下文咱们将看到,建议你在阅读此文的同时,应该参照Spring Security项目的源代码,便于你更好的理解。 |
3 配置applicationContext-security.xml
3.1 FilterChainProxy过滤器链
FilterChainProxy会按顺序来调用一组filter,使这些filter即能完成验证受权的本质工做,又能享用Spring Ioc的功能来方便的获得其它依赖的资源。FilterChainProxy配置以下:
class="org.springframework.security.util.FilterChainProxy">
2 <property name="filterInvocationDefinitionSource">
3 <value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
4 PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,logoutFilter,
5 authenticationProcessingFilter,securityContextHolderAwareRequestFilter,
6 rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,
7 filterSecurityInterceptor
8 ]]></value>
9 </property>
10 </bean>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON 定义URL在匹配以前必须先转为小写,PATTERN_TYPE_APACHE_ANT 定义了使用Apache ant的匹配模式,/**定义的将等号后面的过滤器应用在那些URL上,这里使用所有URL过滤,每一个过滤器之间都适用逗号分隔,它们按照必定的顺序排列。
提示: 特别须要注意的是,即便你配置了系统提供的全部过滤器,这个过滤器链会很长,可是千万不要使用换行,不然它们不会正常工做, 容器甚至不能正常启动。 |
下面根据FilterChainProxy的配置来介绍各个过滤器的配置,各个过滤器的执行顺序如以上配置。
首先是通道处理过滤器,若是你须要使用HTTPS,这里咱们就使用HTTP进行传输,因此不须要配置通道处理过滤器,而后是集成过滤器,配置以下:
2
3 class="org.springframework.security.context.HttpSessionContextIntegrationFilter"/>
httpSessionContextIntegrationFilter是集成过滤器的一个实现,在用户的一个请求过程当中,用户的认证信息经过SecurityContextHolder(使用ThreadLoacl实现)进行传递的,全部的过滤器都是经过SecurityContextHolder来获取用户的认证信息,从而在一次请求中全部过滤器都能共享Authentication(认证),减小了HttpRequest参数的传送,下面的代码是从安全上下文的获取Authentication对象的方法:
2
3 Authentication authentication = context.getAuthentication();
但 是,ThreadLoacl不能跨越多个请求存在,因此,集成过滤器在请求开始时从Http会话中取出用户认证信息并建立一个 SecurityContextHolder将Authentication对象保存在其中,在请求结束以后,在从 SecurityContextHolder中获取Authentication对象并将其放回Http会话中,共下次请求使用,从而达到了跨越多个请求 的目的。集成过滤器还有其它的实现,能够参考相关文档。
提示: 集成过滤器必须在其它过滤器以前被使用。 |
logoutFilter(退出过滤器) ,退出登陆操做:
2
3 class="org.springframework.security.ui.logout.LogoutFilter">
4
5 <constructor-arg value="/index.jsp"/>
6
7 <constructor-arg>
8
9 <list>
10
11 <!-- 实现了LogoutHandler接口(logout方法) -->
12
13 <ref bean="rememberMeServices"/>
14
15 <bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler"/>
16
17 </list>
18
19 </constructor-arg>
20
21 </bean>
LogoutFilter的构造函数须要两个参数,第一个是退出系统后系统跳转到的URL,第二个是一个LogoutHandler类型的数组,这个数组里的对象都实现了LogoutHandler接口,并实现了它的logout方法,用户在发送退出请求后,会一次执行LogoutHandler数组的对象并调用它们的 logout方法进 行一些后续的清理操做,主要是从SecurityContextHolder对象中清楚全部用户的认证信息(Authentication对象),将用户 的会话对象设为无效,这些都时由SecurityContextLogoutHandler来完成。LogoutFilter还会清除Cookie记录, 它由另一个Bean来完成(RememberMeServices)。
<ref bean="rememberMeServices"/>标记指向了咱们另外配置的一个Bean:
class="org.springframework.security.ui.rememberme.TokenBasedRememberMeServices"
2 p:key="springsecurity"
3 p:userDetailsService-ref="userDetailsService"/>
TokenBasedRememberMeServices继承自系统的AbstractRememberMeServices抽象类(实现了RememberMeServices和 LogoutHandler两个接口), RememberMeServices接口的loginSuccess方法负责在用户成功登陆以后将用户的认证信息存入Cookie中,这个类在后续的过滤器执行过程当中也会被用到。
另外一个userDetailsService属性也是指向了咱们配置的Bean, 它负责从数据库中读取用户的信息,这个类的详细配置将在后面的部分详细介绍,这里只是简单的认识一下。
过滤器链的下个配置的过滤器是authenticationProcessingFilter(认证过程过滤器),咱们使用它来处理表单认证,当接受到与filterProcessesUrl所定义相同的请求时它开始工做:
2
3 class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter"
4
5 p:authenticationManager-ref="authenticationManager"
6 p:authenticationFailureUrl="/login.jsp?login_error=1"
7 p:defaultTargetUrl="/default.jsp"
8 p:filterProcessesUrl="/j_spring_security_check"
9 p:rememberMeServices-ref="rememberMeServices"/>
下面列出了认证过程过滤器配置中各个属性的功能:
1.authenticationManager 认证管理器
2.authenticationFailureUrl 定义登陆失败时转向的页面
3.defaultTargetUrl 定义登陆成功时转向的页面
4.filterProcessesUrl 定义登陆请求的地址(在web.xml中配置过)
5.rememberMeServices 在验证成功后添加cookie信息
这里也用到了rememberMeServices,若是用户认证成功,将调用RememberMeServices的loginSuccess方法将用户认证信息写入Cookie中,这里也能够看到使用IoC的好处。
决定用户是否有权限访问 受保护资源的第一步就是要肯定用户的身份,最经常使用的方式就是用户提供一个用户名和密码以确认用户的身份是否合法,这一步就是由认证过程过滤器调用 authenticationManager(认证管理器)来完成的。org.springframework.security.AuthenticationManager接口定义了一个authenticate方法,它使用Authentication做为入口参数(只包含用户名和密码),并在验证成功后返回一个完整的Authentication对象(包含用户的权限信息GrantedAuthority数组对象),authenticationProcessingFilter(认证过程过滤器)会将这个完整的Authentication对象存入SecurityContext中,若是认证失败会抛出一个AuthenticationException并跳转到authenticationFailureUrl 定义的URL。认证管理其配置以下:
2
3 class="org.springframework.security.providers.ProviderManager"
4 p:sessionController-ref="concurrentSessionController">
5 <property name="providers">
6 <list>
7 <ref bean="daoAuthenticationProvider"/>
8 <bean
9
10 class="org.springframework.security.providers.anonymous.AnonymousAuthenticationProvider"
11 p:key="springsecurity"/>
12 <bean
13
14 class="org.springframework.security.providers.rememberme.RememberMeAuthenticationProvider"
15 p:key="springsecurity"/>
16 </list>
17 </property>
18 </bean>
正如在配置中看到的同样,系统使用org.springframework.security.providers.ProviderManager(提供者管理器)类做为认证管理器的一个实现,事实上这个类是继承自实现了AuthenticationManager接口的 AbstractAuthenticationManager类。须要注意的是ProviderManager(提供者管理器)本身并不实现身份验证,而是把这项工做交给了多个认证提供者(提供者集合)或者说的多个认证来源。
提示: Spring Security为咱们提供的全部认证提供者实现都是org.springframework.security.providers .AuthenticationProvider 接口的实现类,它们都实现了此接口的authenticate方法,若是你正在看源代码,会发现这个authenticate方法事实上和Authe nticationManager(认证管理器)接口的authenticate方法彻底同样。 |
providers属性定义了提供者管理器的集合,ProviderManager(提供者管理器)逐一遍历这个认证提供者的集合并调用提供者的authenticate方法,若是一个提供者认证失败会尝试另一个提供者直到某一个认证提供者可以成功的验证该用户的身份,以保证获取不一样来源的身份认证。下面表格列出了系统提供的一些认证提供者:
提 供 者 |
做 用 |
DaoAuthenticationProvider |
从数据库中读取用户信息验证身份 |
AnonymousAuthenticationProvider |
匿名用户身份认证 |
RememberMeAuthenticationProvider |
已存cookie中的用户信息身份认证 |
AuthByAdapterProvider |
使用容器的适配器验证身份 |
CasAuthenticationProvider |
根据Yale中心认证服务验证身份, 用于实现单点登录 |
JaasAuthenticationProvider |
从JASS登录配置中获取用户信息验证身份 |
RemoteAuthenticationProvider |
根据远程服务验证用户身份 |
RunAsImplAuthenticationProvider |
对身份已被管理器替换的用户进行验证 |
X509AuthenticationProvider |
从X509认证中获取用户信息验证身份 |
TestingAuthenticationProvider |
单元测试时使用 |
从上面的表中能够看出,系统为咱们提供了不一样的认证提供者,每一个认证提供者会对本身指定的证实信息进行认证,如DaoAuthenticationProvider仅对UsernamePasswordAuthenticationToken这个证实信息进行认证。
在实际项目中,用户的身份和权限信息可能存储在不一样的安全系统中(如数据库,LDAP服务器,CA中心)。
做为程序员,咱们能够根据须要选择不一样的AuthenticationProvider(认证提供者)来对本身的系统提供认证 服务。
这里咱们着重介绍DaoAuthenticationProvider,它从数据库中读取用户信息验证身份,配置以下:
class="org.springframework.security.providers.dao.DaoAuthenticationProvider"
2 p:passwordEncoder-ref="passwordEncoder"
3 p:userDetailsService-ref="userDetailsService"/>
4 <bean id="passwordEncoder"
5 class="org.springframework.security.providers.encoding.Md5PasswordEncoder"/>
还记得前面配置的RememberMeServices吗?它也有一个和DaoAuthenticationProvider一样的属性userDetailsService,这是系统提供的一个接口(org.springframework.security.userdetails.UserDetailsService),在这里咱们把它单独提出来进行介绍。
首先咱们须要了解Spring Security为咱们提供的另一个重要的组件,org.springframework.security.userdetails .UserDetails接口,它表明一个应用系统的用户,该接口定义与用户安全信息相关的方法:
String getUsername():获取用户名;
String getPassword():获取密码;
boolean isAccountNonExpired():用户账号是否过时;
boolean isAccountNonLocked():用户账号是否锁定;
boolean isCredentialsNonExpired():用户的凭证是否过时;
boolean isEnabled():用户是否处于激活状态。
当以上任何一个判断用户状态的方法都返回false时,用户凭证就被视为无效。UserDetails接口还定义了获取用户权限信息的getAuthorities()方法,该方法返回一个GrantedAuthority[]数组对象,GrantedAuthority是用户权限信息对象,这个对象中定义了一个获取用户权限描述信息的getAuthority()方法。
UserDetails便可从数据库中返回,也能够从其它如LDAP中返回,这取决与你的系统中使用什么来存储用户信息和权限以及相应的认证提供者。这里咱们只重点介绍DaoAuthenticationProvider(从数据库中获取用户认证信息的提供者),本人水平有限,在项目中尚未机会用到其它提供者。说到这里,这个封装了用户详细信息的UserDetails该从哪儿获取呢?这就是咱们接下来要介绍的UserDetailsService接口,这个接口中只定义了惟一的UserDetails loadUserByUsername(String username)方法,它经过用户名来获取整个UserDetails对
象。
看到这里你可能会有些糊涂,由于前面提到的Authentication对象中也存放了用户的认证信息,须要注意Authentication对象才是Spring Security使用的进行安全访问控制用户信息安全对象。实际上,Authentication对象有未认证和已认证两种状态,在做为参数传入认证管理器(AuthenticationManager)的authenticate方法时,是一个未认证的对象,它从客户端获取用户的身份信息(如用户名,密码),能够是从一个登陆页面,也能够从Cookie中获取,并由系统自动构形成一个Authentication对象。而这里提到的UserDetails表明一个用户安全信息的源(从数据库,LDAP服务器,CA中心返回),Spring Security要作的就是将这个未认证的Authentication对象和UserDetails进行匹配,成功后将UserDetails中的用户权限信息拷贝到Authentication中组成一个完整的Authentication对象,共其它组件共享。
这样,咱们就能够在系统中获取用户的相关信息了,须要使用到Authentication对象定义的Object getPrincipal()方法,这个方法返回一个Object类型的对象,一般能够将它转换为UserDetails,从而能够获取用户名,密码以及权限等信息。代码以下:
2
3 GrantedAuthority[] authority = details.getAuthorities();
前面介绍了DaoAuthenticationProvider,它能够从数据库中读取用户信息,一样也能够从一个用户属性文件中读取,下一篇文章中咱们在介绍如何从数据库中读取用户信息,固然还会涉及到更深刻的东西,好比根据本身系统的须要自定义UserDetails和UserDetailsService,这个只是让你对整个系统有个简单的了解,因此咱们使用用户属性文件(users.properties)来存储用户信息:
2
3 user1=user1,ROLE_USER
4
5 user2=user2,ROLE_USER
6
7 user3=user3,disabled,ROLE_USER
配置userDetailsService:
2
3 class="org.springframework.security.userdetails.memory.InMemoryDaoImpl">
4 <property name="userProperties">
5 <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean"
6 p:location="/WEB-INF/users.properties"/>
7 </property>
8 </bean>
InMemoryDaoImpl类是UserDetailsService接口的一个实现,它从属性文件里读取用户信息,Spring Security使用一个属性编辑器将用户信息为咱们组织成一个org.springframework.security.userdetails.memory.UserMap类的对象,咱们也能够直接为它提供一个用户权限信息的列表,详见applicationContext-security.xml配置文件。
UserMap字符串的每一行都用键值对的形式表示,前面是用户名,而后是等号,后面是赋予该用户的密码/权限等信息,它们使用逗号隔开。好比:
定义了一个名为admin的用户登陆密码为admin,该用户拥有ROLE_SUPERVISOR权限,再如users.properties文件中配置的名为user3的用户登陆密码为user3,该用户拥有ROLE_USER权限,disabled定义该用户不可用,为被激活(UserDetails 的isEnabled方法)。
即便是系统的开发者或者说是最终用户,都不该该看到系统中有明文的密码。因此,Spring Security考虑的仍是很周到的,为咱们提供的密码加密的功能。正如你在Dao认证提供者(DaoAuthenticationProvider)中看到的,passwordEncoder属性配置的就是一个密码加密程序(密码编码器)。这里咱们使用MD5加密,能够看
配置文件中的scott用户,你还能看出他的密码是什么吗?固然这里只是演示功能,其它用户仍是没有改变, 你能够本身试试。系统为咱们提供了一些经常使用的密码编码器(这些编码器都位于org.springframework.secu rity.providers.encoding包下):
PlaintextPasswordEncoder(默认)——不对密码进行编码,直接返回未经改变的密码;
Md4PasswordEncoder ——对密码进行消息摘要(MD4)编码;
Md5PasswordEncoder ——对密码进行消息摘要(MD5)编码;
ShaPasswordEncoder ——对密码进行安全哈希算法(SHA)编码。
你能够根据须要选择合适的密码编码器,你也能够设置编码器的种子源(salt source)。一个种子源为编码提供种子(salt),或者称编码的密钥,这里再也不赘述。
这里附加介绍了很多东西,但愿你尚未忘记在AuthenticationManager(认证管理器)中还配置了一个名为sessionController的Bean,这个Bean能够阻止用户在进行了一次成功登陆之后在进行一次成功的登陆。在 applicationContext-security.xml配置文件添加sessionController的配置:
2
3 class="org.springframework.security.concurrent.ConcurrentSessionControllerImpl"
4 p:maximumSessions="1"
5 p:exceptionIfMaximumExceeded="true"
6 p:sessionRegistry-ref="sessionRegistry"/>
7 <bean id="sessionRegistry"
8
9 class="org.springframework.security.concurrent.SessionRegistryImpl"/>
maximumSessions属性配置了只容许同一个用户登陆系统一次,exceptionIfMaximumExceeded属性配置了在进行第二次登陆是是否让第一次登陆失效。这里设置为true不容许第二次登陆。要让此功能生效,咱们还 须要在web.xml文件中添加一个监听器,以让Spring Security能获取Session的生命周期事件,配置以下:
2 <listener-class>
3 org.springframework.security.ui.session.HttpSessionEventPublisher
4 </listener-class>
5 </listener>
HttpSessionEventPublisher类实现javax.servlet.http.HttpSessionListener接口,在Session被建立的时候经过调用ApplicationContext的publishEvent(ApplicationEvent event)发布HttpSessionCreatedEvent类型的事件,HttpSessionCreatedEvent类继承自org.springframework.context.ApplicationEvent类的子类 HttpSessionApplicationEvent抽象类。
concurrentSessionController使用sessionRegistry来完成对发布的Session的生命周期事件的处理,org.springframework.security.concurrent.SessionRegistryImpl(实现了SessionRegistry接口), SessionRegistryImpl类还实现了Spring Framework 的事件监听org.springframework.context.Application Listener接口,并实现了该接口定义的onApplicationEvent(ApplicationEvent event)方法用于处理Applic ationEvent类型的事件,若是你了解Spring Framework的事件处理,那么这里你应该能够很好的理解。
认证管理器到此介绍完毕了,认证过程过滤器也介绍完了,接下来咱们继续介绍过滤器链的下一个过滤器
securityContextHolderAwareRequestFilter:
2 class="org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter"/>
这个过滤器使用装饰模式(Decorate Model), 装饰的HttpServletRequest对象。其Wapper是ServletRequest包装类 HttpServletRequestWrapper的子类(如SavedRequestAwareWrapper或 SecurityContextHolderAwareRequestWrapper),附上获取用户权限信息,request参数,headers 和 cookies 的方法。
rememberMeProcessingFilter过滤器配置:
<bean id="rememberMeProcessingFilter"
class="org.springframework.security.ui.rememberme.RememberMeProcessingFilter"
p:authenticationManager-ref="authenticationManager"
p:rememberMeServices-ref="rememberMeServices"/>
当SecurityContextHolder中不存在Authentication用户受权信息时,rememberMeProcessingFilter就会调用rememberMeServices 的autoLogin()方法从cookie中获取用户信息自动登陆。
anonymousProcessingFilter过滤器配置:
2 class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter"
3 p:key="springsecurity"
4 p:userAttribute="anonymousUser,ROLE_ANONYMOUS"/>
若是不存在任何受权信息时,自动添加匿名用户身份至SecurityContextHolder中,就是这里配置的userAttribute,系统为用户分配一个ROLE_ANONYMOUS权限。
exceptionTranslationFilter(异常处理过滤器),该过滤器用来处理在系统认证受权过程当中抛出的异常,主要是处理AccessDeniedException和AuthenticationException两个异常并根据配置跳转到不一样URL:
2 class="org.springframework.security.ui.ExceptionTranslationFilter"
3 p:accessDeniedHandler-ref="accessDeniedHandler"
4 p:authenticationEntryPoint-ref="authenticationEntryPoint"/>
5 <!-- 处理AccessDeniedException -->
6 <bean id="accessDeniedHandler"
7 class="org.springframework.security.ui.AccessDeniedHandlerImpl"
8 p:errorPage="/accessDenied.jsp"/>
9 <bean id="authenticationEntryPoint"
10 class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint"
11 p:loginFormUrl="/login.jsp"
12 p:forceHttps="false"/>
accessDeniedHandler用于处理AccessDeniedException异常,当用户没有权限访问当前请求的资源时抛出此异常,并跳转自这里配置的/accessDenied.jsp页面。
authenticationEntryPoint(认证入口点),这里定义了用户登陆的页面。系统为咱们提供了3个认证入口点的实现:
认 证 入 口 点 |
做 用 |
BasicProcessingFilterEntryPoint |
经过向浏览器发送一个HTTP 401(未受权)消息,由浏览器弹出登陆对话框,提示用户登陆 |
AuthenticationProcessingFilterEntryPoint |
将用户重定向到一个基于HTML表单的登陆页面 |
CasProcessingFilterEntryPoint |
将用户重定向至一个Yale CAS登陆页面 |
这里咱们使用AuthenticationProcessingFilterEntryPoint认证入口点,提供给用户一个友好的登陆界面,只是为了给用户更好的体验。
filterSecurityInterceptor(过滤器安全拦截器), 该过滤器首先调用认证管理器来判断用户是否已被成功验证,若是没有被验证则重定向到登陆界面。不然,从Authentication获取用户的权限信息, 而后从objectDefinitionSource中获取URL所对应的权限,最后调用accessDecisionManager(访问决策管理器) 来判断用户当前拥有的权限是否与当前受保护的URL资源对应的权限匹配,若是匹配就能够访问该URL资源,不然将抛出AccessDeniedException异常并返 回客户端浏览器一个403错误(若是用户定义了accessDenied页面则会被重定向到该页,见:异常处理过滤器 exceptionTranslationFilter中配置的accessDeniedHandler Bean),访问决策管理的的工做机制将在随后更详细介绍,这里先给出过滤器安全拦截器的配置以下:
2
3 class="org.springframework.security.intercept.web.FilterSecurityInterceptor"
4
5 p:authenticationManager-ref="authenticationManager"
6
7 p:accessDecisionManager-ref="accessDecisionManager">
8 <property name="objectDefinitionSource">
9 <value><![CDATA[
10
11 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
12 PATTERN_TYPE_APACHE_ANT
13 /admins/**=ROLE_SUPERVISOR
14 /user/**=ROLE_USER,IS_AUTHENTICATED_REMEMBERED
15 /default.jsp=ROLE_USER,IS_AUTHENTICATED_REMEMBERED
16 /**=IS_AUTHENTICATED_ANONYMOUSLY
17 ]]></value>
18 </property>
19 </bean>
从配置能够看出来,过滤 器安全拦截器用到了咱们前面配置的认证管理器,过滤器安全拦截器使用authenticationManager并调用它的providers(提供者列 表)来对用户的身份进行验证并获取用户拥有的权限。若是用户被成功认证,过滤器安全拦截器将会使用accessDecisionManager(访问决策管理器)来判断已认证的用户是否有权限访问受保护的资源,这些受保护的资源由objectDefinitionSource属性定义。
访问决策管理器(accessDecisionManager):
2 class="org.springframework.security.vote.AffirmativeBased"
3 p:allowIfAllAbstainDecisions="false">
4 <property name="decisionVoters">
5 <list>
6 <bean class="org.springframework.security.vote.RoleVoter"/>
7 <bean class="org.springframework.security.vote.AuthenticatedVoter"/>
8 </list>
9 </property>
10 </bean>
身份验证只是Spring Security安全机制的第一步,访问决策管理器验证用户是否有权限访问相应的资源(filterSecurityInterceptor中objectDefinitionSource属性定义的访问URL须要的属性信息)。
org.springframework.security.AccessDecisionManager接口定义了用于验证用户是否有权限访问受保护资源的decide方法,另外一个supports方法根据受保护资源的配置属性(即访问这些资源所需的权限)来判断该访问决策管理器是否能作出针对该资源的访问决策。decide方法最终决定用户有无访问权限,若是没有则抛出AccessDeniedException异常(面前也提到过,你应该在回过头去看看)。
与认证管理器相似,访问决策管理器也不是由本身来实现访问控制的,而是经过一组投票者来投票决定(经过调用投票者的vote方法),访问决策管理器统计投票结果并最终完成决策工做。下表列出了系统提供的3个访问决策管理器的实现:
访问决策管理器 |
如 何 决 策 |
AffirmativeBased |
当至少有一个投票者投允许访问票时允许访问 |
ConsensusBased |
当全部投票者都投允许访问票时允许访问 |
UnanimousBased |
当没有投票者投拒绝访问票时允许访问 |
decisionVoters属性为访问决策管理器定义了一组进行投票工做的投票者,那么这些投票者是如何进行投票的呢?这就须要提org.springframework.security.vote.AccessDecisionVoter接口,全部的投票者都实现了这个接口并实现了其中的vote方法。该接口中还定义了3个int类型的常量:
int ACCESS_GRANTED = 1;(投同意票)
int ACCESS_ABSTAIN = 0;(投弃权票)
int ACCESS_DENIED = -1; (投反对票)
每一个决策投票者都返回这3个常量中一个,这取决与用户是否有权限访问当前请求的资源,访问决策管理器再对这些投票结果进行统计。认证投票者的配置如上面所示。
loggerListener是一个可选项,它和咱们前面配置的Bean或者过滤器没有关系,只是监听系统的一些事件(
实现了ApplicationListener监听接口),被它监听的事件包括AuthenticationCredentialsNotFoundEvent事 件,AuthorizationFailureEvent事件,AuthorizedEvent事件,PublicInvocationEvent事件,相信你从他们 的名字就能看出来是一些什么样的事件,除非你的e文比我还差劲。loggerListener配置以下:
到此,本例所涉及到的全部配置都介绍完了,在下一篇中会介绍方法安全拦截器,以及如何使用它来保护咱们的方法调用,以及前面提到过的会在下一篇中介绍的,这里不在一一列出。
接下来就是JSP页面了,首先是login.jsp:
2 登陆失败,请重试。错误缘由:<br/>
3 <font color="red">
4 <c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION}">
5 <c:out value="${SPRING_SECURITY_LAST_EXCEPTION}"></c:out>
6 </c:if>
7 </font>
8 </c:if>
9 <form action="<c:url value="/j_spring_security_check"/>" method="post">
10 <table>
11 <tr>
12 <td><label for="username">username:</label></td>
13 <td><input type="text" id="username" name="j_username"
value="<c:out value="${SPRING_SECURITY_LAST_USERNAME}"/>"/></td>
14 </tr>
15 <tr>
16 <td><label for="password">password:</label></td>
17 <td><input type="password" id="password" name="j_password" value=""/></td>
18 </tr>
19 <tr><td></td>
20 <td><input type="checkbox" name="_spring_security_remember_me">两周内记住我</td>
21 </tr> 22 <tr><td colspan="2"><input type="submit" value="提交"/> 23 <input type="reset" value="重置"/></td></tr> 24 </table> 25 </form>
若是你有看源代码,上面的某些参数,以及本文全部说起的东西你都不该该感到陌生。其它页面也不在列出了,还有就是如何让它运行起来,这些我相信你都能本身搞定。
附件1:springsecurity.rar(不包括JAR包)
补上使用命名空间配置实现的代码,命名空间的详细资料请参考Spring Security中文参考文档,翻译得很好,这里就不在累述了,配置文件中也有比较详细的注释。另外例子中还包括了自定义UserDetailService的实现已经如何Ehcache缓存用户信息,详细的信息将在下一篇中讲述。
附件2:springsecurity-namespace.rar(包括部分JAR包)