spring secrity(二)

前面介绍了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被创建的时候通过调用ApplicationContextpublishEvent(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 异常(面前也提到过,你应该在回过头去看看)。
与认证管理器类似,访问决策管理器也不是由自己来实现访问控制的,而是通过一组投票者来投票决定(通过调用投票者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配置如下:
1  < bean  id ="loggerListener"  class ="org.springframework.security.event.authentication.LoggerListener" />
到此,本例所涉及到的所有配置都介绍完了,在下一篇中会介绍方法安全拦截器,以及如何使用它来保护我们的方法调用,以及前面提到过的会在下一篇中介绍的,这里不在一一列出。
接下来就是JSP页面了,首先是login.jsp:
 1  < c:if  test ="${not empty param.login_error}" >
 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包)

你可能感兴趣的:(职场,休闲)