java

位置:IT落伍者 >> java >> 浏览文章

Spring Security学习总结一


发布日期:2021年06月13日
 
Spring Security学习总结一

在认识Spring Security之前所有的权限验证逻辑都混杂在业务逻辑中用户的每个操作以前可能都需要对用户是否有进行该项操作的权限进行判断来达到认证授权的目的类似这样的权限验证逻辑代码被分散在系统的许多地方难以维护AOP(Aspect Oriented Programming)和Spring Security为我们的应用程序很好的解决了此类问题正如系统日志事务管理等这些系统级的服务一样我们应该将它作为系统一个单独的切面进行管理以达到业务逻辑与系统级的服务真正分离的目的Spring Security将系统的安全逻辑从业务中分离出来

本文代码运行环境

JDK

springframework

springsecurity

JavaEE

Web容器

Apache Tomcat

IDE工具

Eclipse+MyEclipse

操作系统

Linux(Fedora

这只是我个人的学习总结而已还请高手们指出本文的不足之处

一 Spring Security 简介

这里提到的Spring Security也就是被大家广为熟悉的Acegi Security年底Acegi Security正式成为Spring Portfolio项目并更名为Spring SecuritySpring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架它提供了一组可以在Spring应用上下文中配置的Bean充分利用了Spring IoC(依赖注入也称控制反转)和AOP(面向切面编程)功能为应用系统提供声明式的安全访问控制功能减少了为企业系统安全控制编写大量重复代码的工作

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

Ø 认证授权机制

Ø Web资源访问控制

Ø 业务方法调用访问控制

Ø 领域对象访问控制Access Control List(ACL)

Ø 单点登录(Central Authentication Service)

Ø X认证

Ø 信道安全(Channel Security)管理等功能

当保护Web资源时Spring Security使用Servlet 过滤器来拦截Http请求进行身份验证并强制安全性以确保WEB资源被安全的访问如下图是Spring Security的主要组件图(摘自《Spring in Action》)

Spring Security的基本组件

无论是保护WEB资源还是保护业务方法或者领域对象Spring Security都的通过上图中的组件来完成的本文主要阐述如何使用Spring Security对WEB应用程序的资源进行安全访问控制并通过一个简单的实例来对Spring Security提供的各种过滤器的功能和配置方法进行描述

二 保护Web资源

Spring Security提供了很多的过滤器它们拦截Servlet请求并将这些请求转交给认证处理过滤器和访问决策过滤器进行处理并强制安全性认证用户身份和用户权限以达到保护Web资源的目的对于Web资源我们大约可以只用个过滤器来保护我们的应用系统下表列出了这些安全过滤器的名称作用以及它们在系统中的执行顺序

通道处理过滤器

确保请求是在安全通道(HTTP和HTTPS)之上传输的

认证处理过滤器

接受认证请求并将它们转交给认证管理器进行身份验证

CAS处理过滤器

接受CAS服务票据验证Yale CAS(单点登录)是否已经对用户进行了认证

HTTP基本授权过滤器

处理使用HTTP基本认证的身份验证请求

集成过滤器

处理认证信息在请求间的存储(比如在HTTP会话中)

安全强制过滤器

确保用户己经认证并且满足访问一个受保护Web资源的权限需求

接下来通过一个实例来说明它们的具体使用方法和如何在Spring中进行配置

建立Spring Security项目

首先在MyEclipse中创建一个Web Project并使用MyEclipse工具导入Spring项目的依赖JAR包并生成默认的这里暂时不会用到这个文件本文只是通过一个简单的实例来说明如何配置使用Spring Security不会涉及到数据库而是使用一个用户属性(usersproperties)文件来保存用户信息(包括用户名密码及相应的权限)但在实际的项目中我们很少会这样做而是应该把用户信息存在数据库中下一篇文章中将会详细介绍并用到这个文件来配置Hibernate这里我们保留它

现在还需要为项目导入Spring Security的JAR包它没有包括在Spring Framework中你可以从下载并将springsecuritycorejar(这是核心代码库)和springsecuritycoretigerjar(和annotation有关的比如使用注解对方法进行安全访问控制在下一篇中会用到)拷贝到项目的lib目录下其中也包括两个实例(tutorial和contacts)并且两个实例中都包括了如何使用Spring 的命名空间来配置Spring Security无论你对Spring 命名空间的使用是否了解它将使我们的配置文件大大缩短简化开发提高生产效率到此我们的Spring Security项目就建好了项目目录结构如下图所示

项目目录结构

配置webxml

Spring Security使用一组过滤器链来对用户进行身份验证和授权首先在webxml文件中添加FilterToBeanProxy过滤器配置

<filter>

  <filtername>springSecurityFilterChain</filtername>

  <filterclass>

   orgspringframeworksecurityutilFilterToBeanProxy

  </filterclass>

  <initparam>

    <paramname>targetClass</paramname>

  <paramvalue>

     orgspringframeworksecurityutilFilterChainProxy

  </paramvalue>

  </initparam>

</filter>

orgspringframeworksecurityutilFilterToBeanProxy实现了Filter接口它通过调用WebapplicationContextUtils类的getWebApplicationnContext(servletContext)方法来获取Spring的应用上下文句柄并通过getBean(beanName)方法来获取Spring受管Bean的对象即这里targetClass参数配置的Bean并通过调用FilterChain Proxy的init()方法来启动Spring Security过滤器链进行各种身份验证和授权服务(FilterChainProxy类也是实现了Filter接口)从而将过滤功能委托给Spring的FilterChainProxy受管Bean(它维护着一个处理验证和授权的过滤器列表列表中的过滤器按照一定的顺序执行并完成认证过程)这样即简化了webxml文件的配置又能充分利用 Spring的IoC功能来完成这些过滤器执行所需要的其它资源的注入

当用户发出请求过滤器需要根据webxml配置的请求映射地址来拦截用户请求这时Spring Security开始工作它会验证你的身份以及当前请求的资源是否与你拥有的权限相符从而达到保护Web资源的功能下面是本例所要过滤的用户请求地址

<filtermapping>

<filtername>springSecurityFilterChain</filtername>

<urlpattern>/j_spring_security_check</urlpattern>

</filtermapping>

<filtermapping>

<filtername>springSecurityFilterChain</filtername>

<urlpattern>/*</urlpattern>

</filtermapping>

提示

/j_spring_security_check是Spring Security默认的进行表单验证的过滤地址你也可以修改为别的名称但是需要和

applicationContextsecurityxml中相对应当然还会涉及到其它一些默认值(可能是一个成员变量也可能是别的请

求地址)在下文我们将看到建议你在阅读此文的同时应该参照Spring Security项目的源代码便于你更好的理解

配置applicationContextsecurityxml

FilterChainProxy过滤器链

FilterChainProxy会按顺序来调用一组filter使这些filter即能完成验证授权的本质工作又能享用Spring Ioc的功能来方便的得到其它依赖的资源FilterChainProxy配置如下

<beanid=filterChainProxy

  class=orgspringframeworksecurityutilFilterChainProxy>

<propertyname=filterInvocationDefinitionSource>

<value><![CDATA[

    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

PATTERN_TYPE_APACHE_ANT

    /**=httpSessionContextIntegrationFilterlogoutFilter

authenticationProcessingFiltersecurityContextHolderAwareRequestFilter

rememberMeProcessingFilteranonymousProcessingFilterexceptionTranslationFilter

filterSecurityInterceptor

]]></value>

</property>

</bean>

CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON 定义URL在匹配之前必须先转为小写PATTERN_TYPE_APACHE_ANT 定义了使用Apache ant的匹配模式/**定义的将等号后面的过滤器应用在那些URL上这里使用全部URL过滤每个过滤器之间都适用逗号分隔它们按照一定的顺序排列

提示

特别需要注意的是即使你配置了系统提供的所有过滤器这个过滤器链会很长但是千万不要使用换行否则它们不会正常工作

容器甚至不能正常启动

下面根据FilterChainProxy的配置来介绍各个过滤器的配置各个过滤器的执行顺序如以上配置

首先是通道处理过滤器如果你需要使用HTTPS这里我们就使用HTTP进行传输所以不需要配置通道处理过滤器然后是集成过滤器配置如下

<beanid=httpSessionContextIntegrationFilter

class=orgspringfrntextHttpSessionContextIntegrationFilter/>

httpSessionContextIntegrationFilter是集成过滤器的一个实现在用户的一个请求过程中用户的认证信息通过SecurityContextHolder(使用ThreadLoacl实现)进行传递的所有的过滤器都是通过SecurityContextHolder来获取用户的认证信息从而在一次请求中所有过滤器都能共享Authentication(认证)减少了HttpRequest参数的传送下面的代码是从安全上下文的获取Authentication对象的方法

SecurityContextcontext=SecurityContextHoldergetContext()

Authenticationauthentication=contextgetAuthentication();

但是ThreadLoacl不能跨越多个请求存在所以集成过滤器在请求开始时从Http会话中取出用户认证信息并创建一个SecurityContextHolder将Authentication对象保存在其中在请求结束之后在从SecurityContextHolder中获取Authentication对象并将其放回Http会话中共下次请求使用从而达到了跨越多个请求的目的集成过滤器还有其它的实现可以参考相关文档

提示

集成过滤器必须在其它过滤器之前被使用

logoutFilter(退出过滤器) 退出登录操作

<beanid=logoutFilter

class=orgspringframeworksecurityuilogoutLogoutFilter>

<constructorargvalue=/indexjsp/>

<constructorarg>

<list>

<!实现了LogoutHandler接口(logout方法)>

<refbean=rememberMeServices/>

<beanclass=orgspringframeworksecurityuilogoutSecurityContextLogoutHandler/>

</list>

</constructorarg>

</bean>

LogoutFilter的构造函数需要两个参数第一个是退出系统后系统跳转到的URL第二个是一个LogoutHandler类型的数组这个数组里的对象都实现了LogoutHandler接口并实现了它的logout方法用户在发送退出请求后会一次执行LogoutHandler数组的对象并调用它们的 logout方法进行一些后续的清理操作主要是从SecurityContextHolder对象中清楚所有用户的认证信息(Authentication对象)将用户的会话对象设为无效这些都时由SecurityContextLogoutHandler来完成LogoutFilter还会清除Cookie记录它由另外一个Bean来完成(RememberMeServices)

<ref bean=rememberMeServices/>标记指向了我们另外配置的一个Bean

<beanid=rememberMeServices

  class=orgspringframeworksecurityuiremembermeTokenBasedRememberMeServices

p:key=springsecurity

  p:userDetailsServiceref=userDetailsService/>

TokenBasedRememberMeServices继承自系统的AbstractRememberMeServices抽象类(实现了RememberMeServices和 LogoutHandler两个接口) RememberMeServices接口的loginSuccess方法负责在用户成功登录之后将用户的认证信息存入Cookie中这个类在后续的过滤器执行过程中也会被用到

另一个userDetailsService属性也是指向了我们配置的Bean 它负责从数据库中读取用户的信息这个类的详细配置将在后面的部分详细介绍这里只是简单的认识一下

过滤器链的下个配置的过滤器是authenticationProcessingFilter(认证过程过滤器)我们使用它来处理表单认证当接受到与filterProcessesUrl所定义相同的请求时它开始工作

<beanid=authenticationProcessingFilter

class=orgspringframeworksecurityuiwebappAuthenticationProcessingFilter

p:authenticationManagerref=authenticationManager

p:authenticationFailureUrl=/loginjsp?login_error=

  p:defaultTargetUrl=/defaultjsp

p:filterProcessesUrl=/j_spring_security_check

  p:rememberMeServicesref=rememberMeServices/>

下面列出了认证过程过滤器配置中各个属性的功能

authenticationManager 认证管理器

authenticationFailureUrl 定义登录失败时转向的页面

defaultTargetUrl 定义登录成功时转向的页面

filterProcessesUrl 定义登录请求的地址(在webxml中配置过)

rememberMeServices 在验证成功后添加cookie信息

这里也用到了rememberMeServices如果用户认证成功将调用RememberMeServices的loginSuccess方法将用户认证信息写入Cookie中这里也可以看到使用IoC的好处

决定用户是否有权限访问受保护资源的第一步就是要确定用户的身份最常用的方式就是用户提供一个用户名和密码以确认用户的身份是否合法这一步就是由认证过程过滤器调用authenticationManager(认证管理器)来完成的orgspringframeworksecurityAuthenticationManager接口定义了一个authenticate方法它使用Authentication作为入口参数(只包含用户名和密码)并在验证成功后返回一个完整的Authentication对象(包含用户的权限信息GrantedAuthority数组对象)authenticationProcessingFilter(认证过程过滤器)会将这个完整的Authentication对象存入SecurityContext中如果认证失败会抛出一个AuthenticationException并跳转到authenticationFailureUrl 定义的URL认证管理其配置如下

<beanid=authenticationManager

class=orgspringframeworksecurityprovidersProviderManager

p:sessionControllerref=concurrentSessionController>

<propertyname=providers>

<list>

<refbean=daoAuthenticationProvider/>

<bean

class=orgspringframeworksecurityprovidersanonymousAnonymousAuthenticationProvider

p:key=springsecurity/>

<bean

class=orgspringframeworksecurityprovidersremembermeRememberMeAuthenticationProvider

p:key=springsecurity/>

</list>

</property>

</bean>

正如在配置中看到的一样系统使用orgspringframeworksecurityprovidersProviderManager(提供者管理器)类作为认证管理器的一个实现事实上这个类是继承自实现了AuthenticationManager接口的 AbstractAuthenticationManager类需要注意的是ProviderManager(提供者管理器)自己并不实现身份验证而是把这项工作交给了多个认证提供者(提供者集合)或者说的多个认证来源

提示

Spring Security为我们提供的所有认证提供者实现都是orgspringframeworksecurityproviders AuthenticationProvider

接口的实现类它们都实现了此接口的authenticate方法如果你正在看源代码会发现这个authenticate方法事实上和Authe

nticationManager(认证管理器)接口的authenticate方法完全一样

providers属性定义了提供者管理器的集合ProviderManager(提供者管理器)逐一遍历这个认证提供者的集合并调用提供者的authenticate方法如果一个提供者认证失败会尝试另外一个提供者直到某一个认证提供者能够成功的验证该用户的身份以保证获取不同来源的身份认证下面表格列出了系统提供的一些认证提供者

DaoAuthenticationProvider

从数据库中读取用户信息验证身份

AnonymousAuthenticationProvider

匿名用户身份认证

RememberMeAuthenticationProvider

已存cookie中的用户信息身份认证

AuthByAdapterProvider

使用容器的适配器验证身份

CasAuthenticationProvider

根据Yale中心认证服务验证身份 用于实现单点登陆

JaasAuthenticationProvider

从JASS登陆配置中获取用户信息验证身份

RemoteAuthenticationProvider

根据远程服务验证用户身份

RunAsImplAuthenticationProvider

对身份已被管理器替换的用户进行验证

XAuthenticationProvider

从X认证中获取用户信息验证身份

TestingAuthenticationProvider

单元测试时使用

从上面的表中可以看出系统为我们提供了不同的认证提供者每个认证提供者会对自己指定的证明信息进行认证如DaoAuthenticationProvider仅对UsernamePasswordAuthenticationToken这个证明信息进行认证

在实际项目中用户的身份和权限信息可能存储在不同的安全系统中(如数据库LDAP服务器CA中心)

作为程序员我们可以根据需要选择不同的AuthenticationProvider(认证提供者)来对自己的系统提供认证服务

这里我们着重介绍DaoAuthenticationProvider它从数据库中读取用户信息验证身份配置如下

<beanid=daoAuthenticationProvider

  class=orgspringframeworksecurityprovidersdaoDaoAuthenticationProvider

p:passwordEncoderref=passwordEncoder

  p:userDetailsServiceref=userDetailsService/>

<beanid=passwordEncoder

  class=orgspringframeworksecurityprovidersencodingMdPasswordEncoder/>

还记得前面配置的RememberMeServices吗?它也有一个和DaoAuthenticationProvider同样的属性userDetailsService这是系统提供的一个接口(orgspringframeworksecurityuserdetailsUserDetailsService)在这里我们把它单独提出来进行介绍

首先我们需要了解Spring Security为我们提供的另外一个重要的组件orgspringframeworksecurityuserdetails 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从而可以获取用户名密码以及权限等信息代码如下

UserDetailsdetails=(UserDetails)authenticationgetPrincipal();

GrantedAuthority[]authority=detailsgetAuthorities();

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

admin=adminROLE_SUPERVISOR

user=userROLE_USER

user=userROLE_USER

user=userdisabledROLE_USER

配置userDetailsService

<beanid=userDetailsService

class=orgspringframeworksecumoryInMemoryDaoImpl>

<propertyname=userProperties>

<beanclass=orgspringframewonfigPropertiesFactoryBean

   p:location=/WEBINF/usersproperties/>

</property>

</bean>

InMemoryDaoImpl类是UserDetailsService接口的一个实现它从属性文件里读取用户信息Spring Security使用一个属性编辑器将用户信息为我们组织成一个orgspringframeworksecumoryUserMap类的对象我们也可以直接为它提供一个用户权限信息的列表详见applicationContextsecurityxml配置文件

UserMap字符串的每一行都用键值对的形式表示前面是用户名然后是等号后面是赋予该用户的密码/权限等信息它们使用逗号隔开比如

admin=adminROLE_SUPERVISOR

定义了一个名为admin的用户登录密码为admin该用户拥有ROLE_SUPERVISOR权限再如usersproperties文件中配置的名为user的用户登录密码为user该用户拥有ROLE_USER权限disabled定义该用户不可用为被激活(UserDetails 的isEnabled方法)

即使是系统的开发者或者说是最终用户都不应该看到系统中有明文的密码所以Spring Security考虑的还是很周到的为我们提供的密码加密的功能正如你在Dao认证提供者(DaoAuthenticationProvider)中看到的passwordEncoder属性配置的就是一个密码加密程序(密码编码器)这里我们使用MD加密可以看配置文件中的scott用户你还能看出他的密码是什么吗?当然这里只是演示功能其它用户还是没有改变你可以自己试试系统为我们提供了一些常用的密码编码器(这些编码器都位于orgspringframeworksecu rityprovidersencoding包下)

PlaintextPasswordEncoder(默认)——不对密码进行编码直接返回未经改变的密码

MdPasswordEncoder ——对密码进行消息摘要(MD)编码

MdPasswordEncoder ——对密码进行消息摘要(MD)编码

ShaPasswordEncoder ——对密码进行安全哈希算法(SHA)编码

你可以根据需要选择合适的密码编码器你也可以设置编码器的种子源(salt source)一个种子源为编码提供种子(salt)或者称编码的密钥这里不再赘述

这里附加介绍了不少东西希望你还没有忘记在AuthenticationManager(认证管理器)中还配置了一个名为sessionController的Bean这个Bean可以阻止用户在进行了一次成功登录以后在进行一次成功的登录在 applicationContextsecurityxml配置文件添加sessionController的配置

<beanid=concurrentSessionController

class=orgspringfrncurrentConcurrentSessionControllerImpl

p:maximumSessions=

p:exceptionIfMaximumExceeded=true

p:sessionRegistryref=sessionRegistry/>

<beanid=sessionRegistry

class=orgspringfrncurrentSessionRegistryImpl/>

maximumSessions属性配置了只允许同一个用户登录系统一次exceptionIfMaximumExceeded属性配置了在进行第二次登录是是否让第一次登录失效这里设置为true不允许第二次登录要让此功能生效我们还需要在webxml文件中添加一个监听器以让Spring Security能获取Session的生命周期事件配置如下

<listener>

<listenerclass>

  orgspringframeworksecurityuisessionHttpSessionEventPublisher

</listenerclass>

</listener>

HttpSessionEventPublisher类实现javaxservlethttpHttpSessionListener接口在Session被创建的时候通过调用ApplicationContext的publishEvent(ApplicationEvent event)发布HttpSessionCreatedEvent类型的事件HttpSessionCreatedEvent类继承自orgntextApplicationEvent类的子类 HttpSessionApplicationEvent抽象类

concurrentSessionController使用sessionRegistry来完成对发布的Session的生命周期事件的处理orgspringfrncurrentSessionRegistryImpl(实现了SessionRegistry接口) SessionRegistryImpl类还实现了Spring Framework 的事件监听orgntextApplication Listener接口并实现了该接口定义的onApplicationEvent(ApplicationEvent event)方法用于处理Applic ationEvent类型的事件如果你了解Spring Framework的事件处理那么这里你应该可以很好的理解

认证管理器到此介绍完毕了认证过程过滤器也介绍完了接下来我们继续介绍过滤器链的下一个过滤器securityContextHolderAwareRequestFilter

<beanid=securityContextHolderAwareRequestFilter

  class=orgspringframeworksecuritywrapperSecurityContextHolderAwareRequestFilter/>

这个过滤器使用装饰模式(Decorate Model)装饰的HttpServletRequest对象其Wapper是ServletRequest包装类HttpServletRequestWrapper的子类(如SavedRequestAwareWrapper或SecurityContextHolderAwareRequestWrapper)附上获取用户权限信息request参数headers 和 cookies 的方法

rememberMeProcessingFilter过滤器配置

<bean id=rememberMeProcessingFilter

class=orgspringframeworksecurityuiremembermeRememberMeProcessingFilter

pauthenticationManagerref=authenticationManager

prememberMeServicesref=rememberMeServices/>

当SecurityContextHolder中不存在Authentication用户授权信息时rememberMeProcessingFilter就会调用rememberMeServices 的autoLogin()方法从cookie中获取用户信息自动登录

anonymousProcessingFilter过滤器配置

<beanid=anonymousProcessingFilter

  class=orgspringframeworksecurityprovidersanonymousAnonymousProcessingFilter

p:key=springsecurity

  p:userAttribute=anonymousUserROLE_ANONYMOUS/>

如果不存在任何授权信息时自动添加匿名用户身份至SecurityContextHolder中就是这里配置的userAttribute系统为用户分配一个ROLE_ANONYMOUS权限

exceptionTranslationFilter(异常处理过滤器)该过滤器用来处理在系统认证授权过程中抛出的异常主要是处理AccessDeniedException和AuthenticationException两个异常并根据配置跳转到不同URL

<beanid=exceptionTranslationFilter

  class=orgspringframeworksecurityuiExceptionTranslationFilter

  p:accessDeniedHandlerref=accessDeniedHandler

p:authenticationEntryPointref=authenticationEntryPoint/>

<!处理AccessDeniedException>

<beanid=accessDeniedHandler

  class=orgspringframeworksecurityuiAccessDeniedHandlerImpl

p:errorPage=/accessDeniedjsp/>

<beanid=authenticationEntryPoint

   class=orgspringframeworksecurityuiwebappAuthenticationProcessingFilterEntryPoint

  p:loginFormUrl=/loginjsp

  p:forceHttps=false/>

accessDeniedHandler用于处理AccessDeniedException异常当用户没有权限访问当前请求的资源时抛出此异常并跳转自这里配置的/accessDeniedjsp页面

authenticationEntryPoint(认证入口点)这里定义了用户登录的页面系统为我们提供了个认证入口点的实现

认 证 入 口 点

作 用

BasicProcessingFilterEntryPoint

通过向浏览器发送一个HTTP (未授权)消息由浏览器弹出登录对话框提示用户登录

AuthenticationProcessingFilterEntryPoint

将用户重定向到一个基于HTML表单的登录页面

CasProcessingFilterEntryPoint

将用户重定向至一个Yale CAS登录页面

这里我们使用AuthenticationProcessingFilterEntryPoint认证入口点提供给用户一个友好的登录界面只是为了给用户更好的体验

filterSecurityInterceptor(过滤器安全拦截器)该过滤器首先调用认证管理器来判断用户是否已被成功验证如果没有被验证则重定向到登录界面否则从Authentication获取用户的权限信息然后从objectDefinitionSource中获取URL所对应的权限最后调用accessDecisionManager(访问决策管理器)来判断用户当前拥有的权限是否与当前受保护的URL资源对应的权限匹配如果匹配就可以访问该URL资源否则将抛出AccessDeniedException异常并返回客户端浏览器一个错误(如果用户定义了accessDenied页面则会被重定向到该页异常处理过滤器exceptionTranslationFilter中配置的accessDeniedHandler Bean)访问决策管理的的工作机制将在随后更详细介绍这里先给出过滤器安全拦截器的配置如下

<beanid=filterSecurityInterceptor

  class=orgspringframeworksecurityinterceptwebFilterSecurityInterceptor

  p:authenticationManagerref=authenticationManager

  p:accessDecisionManagerref=accessDecisionManager>

<propertyname=objectDefinitionSource>

<value><![CDATA[

CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

PATTERN_TYPE_APACHE_ANT

/admins/**=ROLE_SUPERVISOR

     /user/**=ROLE_USERIS_AUTHENTICATED_REMEMBERED

    /defaultjsp=ROLE_USERIS_AUTHENTICATED_REMEMBERED

  /**=IS_AUTHENTICATED_ANONYMOUSLY

  ]]></value>

  </property>

</bean>

从配置可以看出来过滤器安全拦截器用到了我们前面配置的认证管理器过滤器安全拦截器使用authenticationManager并调用它的providers(提供者列表)来对用户的身份进行验证并获取用户拥有的权限如果用户被成功认证过滤器安全拦截器将会使用accessDecisionManager(访问决策管理器)来判断已认证的用户是否有权限访问受保护的资源这些受保护的资源由objectDefinitionSource属性定义

访问决策管理器(accessDecisionManager)

<beanid=accessDecisionManager

  class=orgspringframeworksecurityvoteAffirmativeBased

p:allowIfAllAbstainDecisions=false>

<propertyname=decisionVoters>

<list>

<beanclass=orgspringframeworksecurityvoteRoleVoter/>

<beanclass=orgspringframeworksecurityvoteAuthenticatedVoter/>

</list>

</property>

</bean>

身份验证只是Spring Security安全机制的第一步访问决策管理器验证用户是否有权限访问相应的资源(filterSecurityInterceptor中objectDefinitionSource属性定义的访问URL需要的属性信息)

orgspringframeworksecurityAccessDecisionManager接口定义了用于验证用户是否有权限访问受保护资源的decide方法另一个supports方法根据受保护资源的配置属性(即访问这些资源所需的权限)来判断该访问决策管理器是否能做出针对该资源的访问决策decide方法最终决定用户有无访问权限如果没有则抛出AccessDeniedException异常(面前也提到过你应该在回过头去看看)

与认证管理器类似访问决策管理器也不是由自己来实现访问控制的而是通过一组投票者来投票决定(通过调用投票者的vote方法)访问决策管理器统计投票结果并最终完成决策工作下表列出了系统提供的个访问决策管理器的实现

访问决策管理器

如 何 决 策

AffirmativeBased

当至少有一个投票者投允许访问票时允许访问

ConsensusBased

当所有投票者都投允许访问票时允许访问

UnanimousBased

当没有投票者投拒绝访问票时允许访问

decisionVoters属性为访问决策管理器定义了一组进行投票工作的投票者那么这些投票者是如何进行投票的呢?这就需要提orgspringframeworksecurityvoteAccessDecisionVoter接口所有的投票者都实现了这个接口并实现了其中的vote方法该接口中还定义了个int类型的常量

int ACCESS_GRANTED = (投赞成票)

int ACCESS_ABSTAIN = (投弃权票)

int ACCESS_DENIED = (投反对票)

每个决策投票者都返回这个常量中一个这取决与用户是否有权限访问当前请求的资源访问决策管理器再对这些投票结果进行统计认证投票者的配置如上面所示

loggerListener是一个可选项它和我们前面配置的Bean或者过滤器没有关系只是监听系统的一些事件(实现了ApplicationListener监听接口)被它监听的事件包括AuthenticationCredentialsNotFoundEvent事件AuthorizationFailureEvent事件AuthorizedEvent事件PublicInvocationEvent事件相信你从他们的名字就能看出来是一些什么样的事件除非你的e文比我还差劲loggerListener配置如下

<beanid=loggerListenerclass=orgspringframeworksecurityeventauthenticationLoggerListener/>

到此本例所涉及到的所有配置都介绍完了在下一篇中会介绍方法安全拦截器以及如何使用它来保护我们的方法调用以及前面提到过的会在下一篇中介绍的这里不在一一列出

接下来就是JSP页面了首先是loginjsp

<c:iftest=${notemptyparamlogin_error}>

登录失败请重试错误原因:<br/>

<fontcolor=red>

<c:iftest=${notemptySPRING_SECURITY_LAST_EXCEPTION}>

<c:outvalue=${SPRING_SECURITY_LAST_EXCEPTION}></c:out>

</c:if>

</font>

</c:if>

<formaction=<c:urlvalue=/j_spring_security_check/>method=post>

<table>

<tr>

<td><labelfor=username>username:</label></td>

<td><inputtype=textid=usernamename=j_username

     value=<c:outvalue=${SPRING_SECURITY_LAST_USERNAME}/>/></td>

</tr>

<tr>

<td><labelfor=password>password:</label></td>

<td><inputtype=passwordid=passwordname=j_passwordvalue=/></td>

</tr>

<tr><td></td>

<td><inputtype=checkboxname=_spring_security_remember_me>两周内记住我</td>

</tr>

<tr><tdcolspan=><inputtype=submitvalue=提交/>

<inputtype=resetvalue=重置/></td></tr>

</table>

</form>

如果你有看源代码上面的某些参数以及本文所有提及的东西你都不应该感到陌生其它页面也不在列出了还有就是如何让它运行起来这些我相信你都能自己搞定

附件linux/springsecurityrar>springsecurityrar(不包括JAR包)

补上使用命名空间配置实现的代码命名空间的详细资料请参考Spring Security中文参考文档翻译得很好这里就不在累述了配置文件中也有比较详细的注释另外例子中还包括了自定义UserDetailService的实现已经如何Ehcache缓存用户信息详细的信息将在下一篇中讲述

附件linux/springsecuritynamespacerar>springsecuritynamespacerar(包括部分JAR包)

               

上一篇:Spring-OSGI 1.0 M3 中文手册

下一篇:Ant实战之Jar文件数字签名的制作