Spring Security学习总结

提示:
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,它从数据库中读取用户信息验证身份,配置如下:

1  < bean  id
="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
,从而可以获取用户名,密码以及权限等信息。代码如下:

1  UserDetails details 

(UserDetails)authentication.getPrincipal();
2  
3  GrantedAuthority[] authority 
=  details.getAuthorities();
前面介绍了
DaoAuthenticationProvider,它可以从数据库中读取用户信息,同样也可以从一个用户属性文件中读取,下一篇文章中我们在介绍如何从数据库中读取用户信息,当然还会涉及到更深入的东西,比如根据自己系统的需要自定义
UserDetails 和UserDetailsService,这个只是让你对整个系统有个简单的了解,所以我们使用 用户属性文件
(users.properties )来存储用户信息:

1  admin = admin,ROLE_SUPERVISOR
2  
3  user1 = user1,ROLE_USER
4  
5  user2 = user2,ROLE_USER
6  
7  user3 = user3,disabled,ROLE_USER
    配置
userDetailsService :

1  < bean  id
="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
字符串的每一行都用键值对的形式表示,前面是用户名,然后是等号,后面是赋予该用户的密码/权限等信息,它们使用逗号隔开。比如:

1  admin = admin,ROLE_SUPERVISOR
定义了一个名为 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 的配置:

1  < bean  id
="concurrentSessionController"
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的生命周期事件,配置如下:

1  < listener
>
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 :

1  < bean  id
="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 过滤器配置:

1  < bean  id
="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:

1  < bean  id
="exceptionTranslationFilter"
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),访问决策管理的的工作机制将在随后更详细介绍,这里先给出过滤器安全拦截器的配置如下:

1  <
bean  id ="filterSecurityInterceptor"
 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) :

1  <
bean  id ="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 异常(面前也提到过,你应该在回过头去看看)。

时间: 2024-08-07 21:01:20

Spring&nbsp;Security学习总结的相关文章

[转]Spring Security学习总结一

[总结-含源码]Spring Security学习总结一(补命名空间配置) Posted on 2008-08-20 10:25 tangtb 阅读(43111) 评论(27)  编辑  收藏 所属分类: Spring .Spring Security Spring Security学习总结一 在认识Spring Security之前,所有的权限验证逻辑都混杂在业务逻辑中,用户的每个操作以前可能都需要对用户是否有进行该项 操作的权限进行判断,来达到认证授权的目的.类似这样的权限验证逻辑代码被分散

[转]Spring Security学习总结二

原文链接: http://www.blogjava.net/redhatlinux/archive/2008/08/20/223148.html http://www.blogjava.net/redhatlinux/archive/2008/09/01/226010.html [总结-含源码]Spring Security学习总结二 Posted on 2008-09-01 10:08 tangtb 阅读(9518) 评论(12)  编辑  收藏 所属分类: Spring .Spring Se

Spring 3.0 学习-DI 依赖注入_创建Spring 配置-使用一个或多个XML 文件作为配置文件,使用自动注入(byName),在代码中使用注解代替自动注入,使用自动扫描代替xml中bea

文章大纲 在xml中声明bean和注入bean 在xml中声明bean和自动注入bean 自动扫描bean和自动注入bean 对自动扫描bean增加约束条件 首次接触spring请参考 Spring 3.0 学习-环境搭建和三种形式访问 1.典型的Spring XML 配置文件表头 <?xml version="1.0" encoding="UTF-8"?><!-- 一般化的Spring XML 配置 --> <beans xmlns=

[Spring Data MongoDB]学习笔记--建立数据库的连接

1. 有了上一篇的Mongo后,连接数据库我们还需要更多的信息,比如数据库名字,用户名和密码等. 我们可以继续来配置MongoDbFactory的实例. public interface MongoDbFactory { DB getDb() throws DataAccessException; DB getDb(String dbName) throws DataAccessException; } 然后我们可以继续用MongoDbFactory来创建MongoTemplate的实例. pu

Spring Data Redis学习

本文是从为知笔记上复制过来的,懒得调整格式了,为知笔记版本是带格式的.点这里 为知笔记版本 Spring Data Redis 学习 Version 1.8.4.Release 前言 1.新功能 1.1.Spring Data Redis 1.8 新特性 1.2.Spring Data Redis 1.7 新特性 1.3.Spring Data Redis 1.6 新特性 1.4.Spring Data Redis 1.5 新特性 介绍 2.为什么选择Spring Data Redis? 3.要

Spring源码学习笔记(6)

Spring源码学习笔记(六) 前言-- 最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门. 上一篇中我们梳理到 Spring 加载 XML 配置文件, 完成 XML 的解析工作,接下来我们将进入 Spring 加载 bean 的逻辑. 我们使用 Spring 获取 XML

Spring源码学习笔记(3)

Spring源码学习笔记(三) 前言----     最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门. DispatcherServlet 实现核心功能 和普通的 Servelt 类一样, DispatcherServlet 中的 doGet() 和 doPost() 方法

[Spring Data MongoDB]学习笔记--牛逼的MongoTemplate

MongoTemplate是数据库和代码之间的接口,对数据库的操作都在它里面. 注:MongoTemplate是线程安全的. MongoTemplate实现了interface MongoOperations,一般推荐使用MongoOperations来进行相关的操作. MongoOperations mongoOps = new MongoTemplate(new SimpleMongoDbFactory(new Mongo(), "database")); MongoDB docu

[Spring Data MongoDB]学习笔记--注册一个Mongo实例

1. 通过Java based bean metadata @Configuration public class AppConfig { public @Bean Mongo mongo() throws UnknownHostExceptioin { return new Mongo("localhost"); } } 上面的方式包含异常处理,这并不是我们想要的. 所以,应该尽量用下面这种方式MongoFactoryBean,或者后面的xml方式. @Configuration p