安全的复杂之处:安全web请求的架构

         借助于Spring Security的强大基础配置功能以及内置的认证功能,咱们在前面讲述的三步配置是很快就能完成的;它们的使用是经过添加auto-config属性和http元素实现的。

         但不幸的是,应用实现的考量、架构的限制以及基础设施集成的要求可能使你的Spring Security实现远较这个简单的配置所提供的复杂。不少用户一使用比基本配置复杂的功能就会遇到麻烦,那是由于他们不了解这个产品的架构以及各个元素是如何协同工做以实现一个总体的。 java

         理解web请求的总体流程以及它们是如何穿越实现功能的拦截器链,对咱们成功了解Spring Security的高级话题相当重要。记住认证和受权的基本概念,由于它们贯穿咱们要保护的系统架构的始终。 web


请求是怎样被处理的?

         Spring Security的架构在很大程度上依赖代理(delegates)和servlet过滤器,来实现环绕在web应用请求先后的功能层。 spring

         Servlet过滤器(Servlet Filter,实现javax.servlet.Filter接口的类)被用来拦截用户请求来进行请求以前或以后的处理,或者干脆重定向这个请求,这取决于servlet过滤器的功能。在JBCP Pets在线商店中,最终的目标servlet是Spring MVC 分发servlet,可是在理论上它多是任何一个web servlet。下面的图描述了一个servlet过滤器是如何封装一个用户请求的: 数据库



 Spring Security在XML配置文件中的自动配置属性,创建了十个servlet过滤器,它们经过使用Java EE的servlet过滤器链按顺序组合起来。Filter chain是Java EE Servlet API的一个概念,经过接口javax.servlet.FilterChain进行定义,它容许在web应用中的一系列的servlet过滤器可以应用于任何给定的请求。 express

         与生活中金属制定的链相似,每个servelt过滤器表明链上的一环,它会进行方法的调用以处理用户的请求。请求穿过整个过滤器链,按顺序调用每一个过滤器。 api



 正如你能从链这个词汇中推断出的那样,servlet请求按照必定的顺序从一个过滤器到下一个穿过整个过滤器链,最终到达目标servlet。与之相对的是,当servelt处理完请求并返回一个response时,过滤器链按照相反的顺序再次穿过全部的过滤器。 安全

         Spring Security使用了过滤器链的概念并实现了本身抽象,提供了VirtualFilterChain,它能够根据Spring Security XML配置文件中设置的URL模式动态的建立过滤器链(能够将它与标准的Java EE过滤器链进行对比,后者须要在web应用的部署描述文件中进行设置)。 网络

【Servlet过滤器除了可以如它的名字所描述的那样进行过滤功能(或阻止请求)之外,还能够用于不少其余的目的。实际上,不少的servlet过滤器的功能相似于在web运行的环境中对请求进行AOP式的代理拦截,由于它们能够容许一些功能在任何发往servelt容器的请求处理以前或以后执行。过滤器能实现的多功能在Spring Security中页获得了体现,由于不少过滤器实际上并不直接影响用户的请求。】 session

 

自动配置的选项为你创建了十个Spring Security的过滤器。理解这些过滤器的默认行为以及它们在哪里以及如何配置的,对使用Spring Security的高级功能相当重要。 架构

 

这些过滤器以及它们使用的顺序,在下面的表格中进行了描述。大多数这些过滤器在咱们完善JBCP Pets在线商店的过程当中都会被再次提到,因此若是你如今不明白它们的确切功能也没必要担忧。

过滤器名称

描述

o.s.s.web.context.SecurityContextPersistenceFilter

负责从SecurityContextRepository获取或存储SecurityContext。SecurityContext表明了用户安全和认证过的session。

o.s.s.web.authentication.logout.LogoutFilter

监控一个实际为退出功能的URL(默认为/j_spring_security_logout),而且在匹配的时候完成用户的退出功能。

o.s.s.web.authentication.UsernamePasswordAuthenticationFilter

监控一个使用用户名和密码基于form认证的URL(默认为/j_spring_security_check),并在URL匹配的状况下尝试认证该用户。

o.s.s.web.authentication.ui.DefaultLoginPageGeneratingFilter

监控一个要进行基于forn或OpenID认证的URL(默认为/spring_security_login),并生成展示登陆form的HTML

o.s.s.web.authentication.www.BasicAuthenticationFilter

监控HTTP 基础认证的头信息并进行处理

o.s.s.web.savedrequest.

RequestCacheAwareFilter

用于用户登陆成功后,从新恢复由于登陆被打断的请求。

o.s.s.web.servletapi.

SecurityContextHolderAwareRequest

Filter

用一个扩展了HttpServletRequestWrapper的子类(o.s.s.web. servletapi.SecurityContextHolderAwareRequestWrapper)包装HttpServletRequest。它为请求处理器提供了额外的上下文信息。

o.s.s.web.authentication.

AnonymousAuthenticationFilter

若是用户到这一步尚未通过认证,将会为这个请求关联一个认证的token,标识此用户是匿名的。

o.s.s.web.session.

SessionManagementFilter

根据认证的安全实体信息跟踪session,保证全部关联一个安全实体的session都能被跟踪到。

o.s.s.web.access.

ExceptionTranslationFilter

解决在处理一个请求时产生的指定异常

o.s.s.web.access.intercept.

FilterSecurityInterceptor

简化受权和访问控制决定,委托一个AccessDecisionManager完成受权的判断

Spring Security拥有总共大约25个过滤器,它们可以根据你的须要进行适当的应用以改变用户请求的行为。固然,若是须要的话,你也能够添加你本身实现了javax.servlet.Filter接口的过滤器。

请记住,若是你在XML配置文件中使用了auto-config属性,以上表格中列出的过滤器自动添加的。经过使用一些额外的配置指令,以上列表中的过滤器可以精确的控制是否被包含,在后续的章节章将会进行介绍。

 

你可能会彻底从头作起来配置过滤器链。尽管这会很单调乏味,由于有不少的依赖关系要配置,可是它为配置和应用场景的匹配方面提供了更高层次的灵活性。咱们将在第六章讲述在启动的过程当中所依赖的Spring Bean的声明。

你可能想知道DelegatingFilterProxy是怎样找到Spring Security配置的过滤器链的。让咱们回忆一下,在web.xml文件中,咱们须要给DelegatingFilterProxy一个过滤器的名字:

<filter>

  <filter-name>springSecurityFilterChain</filter-name>

  <filter-class>

    org.springframework.web.filter.DelegatingFilterProxy

  </filter-class>

</filter>

这个过滤器的名字并非随意配置的,实际上会跟根据这个名字把Spring Security织入到DelegatingFilterProxy。除非明确配置,不然DelegatingFilterProxy会在Spring WebApplicationContext中寻找同名的配置bean(名字是在filter-name中指明的)。更多配置DelegatingFilterProxy的细节能够在这个类对应的Javadoc中找到。

auto-config场景下,发生了什么事情?

在Spring Security 3中,使用auto-config会自动提供如下三个认证相关的功能:

<!--[if !supportLists]-->l  <!--[endif]-->HTTP基本认证

<!--[if !supportLists]-->l  <!--[endif]-->Form登陆认证

<!--[if !supportLists]-->l  <!--[endif]-->退出

值得注意的是,也可使用配置元素实现这三个功能,可以实现比使用auto-config提供的功能更精确。咱们将在随后的章节中看到它们的使用以提供更高级的功能。

【auto-config和之前不同了!在Spring Security3以前的版本中,auto-config属性提供了比如今更多的启动项。在Spring Security2中经过auto-config配置的功能,如今可使用security命名空间样式的配置很容易的实现。请参考13章:迁移至Spring Security 3来获取更多从Spring Security2迁移到3的详细信息。】

除了以上认证相关的功能,其它过滤器链的配置是经过使用<http>元素来实现的。

 

用户是怎样认证的?

在咱们的安全系统中,当一个用户在咱们的登陆form中提供凭证后,这些凭证信息必须与凭证存储中的数据进行校验以肯定下一步的行为。凭证的校验涉及到一系列的逻辑组件,它们封装了认证过程。

咱们将会深刻讲解咱们例子中的用户名和密码登陆form,与之对应的接口和实现都是特定于用户名和密码认证的。可是,请记住,总体的认证是相同的,无论你是使用基于form的登陆请求,或者使用一个外部的认证提供者如集中认证服务(CAS),抑或用户的凭证信息存在一个数据库或在一个LDAP目录中。在本书的第二部分,咱们将会看到在基于form登陆中学到的概念是如何应用到更高级的认证机制中。

涉及到认证功能的重要接口在下边的图标中有一个概览性的描述:



 站在一个较高层次上看,你能够看到有三个主要的组件负责这项重要的事情:

接口名

描述/角色

AbstractAuthenticationProcessingFilter

它在基于web的认证请求中使用。处理包含认证信息的请求,如认证信息多是form POST提交的、SSO信息或者其余用户提供的。建立一个部分完整的Authentication对象以在链中传递凭证信息。

AuthenticationManager

它用来校验用户的凭证信息,或者会抛出一个特定的异常(校验失败的状况)或者完整填充Authentication对象,将会包含了权限信息。

AuthenticationProvider

它为AuthenticationManager提供凭证校验。一些AuthenticationProvider的实现基于凭证信息的存储,如数据库,来断定凭证信息是否能够被承认。

有两个重要接口的实现是在认证链中被这些参与的类初始化的,它们用来封装一个认证过(或尚未认证过的)的用户的详细信息和权限。

o.s.s.core.Authentication是你之后要常常接触到的接口,由于它存储了用户的详细信息,包括惟一标识(如用户名)、凭证信息(如密码)以及本用户被授予的一个或多个权限(o.s.s.core.

GrantedAuthority)。开发人员一般会使用Authentication对象来获取用户的详细信息,或者使用自定义的认证明现以便在Authentication对象中增长应用依赖的额外信息。

如下列出了Authentication接口能够实现的方法:

方法签名

描述

Object getPrincipal()

返回安全实体的惟一标识(如,一个用户名)

Object getCredentials()

返回安全实体的凭证信息

List<GrantedAuthority>

getAuthorities()

获得安全实体的权限集合,根据认证信息的存储决定的。

Object getDetails()

返回一个跟认证提供者相关的安全实体细节信息

你可能会担忧的发现,Authentication接口有好几个方法的返回值是简单的java.lang.Object。这可能会致使在编译阶段很难知道调用Authentication对象的方法返回值是什么类型的对象。

须要注意的一点是AuthenticationProvider并非直接被AuthenticationManager接口使用或引用的。可是Spring Security只提供了AuthenticationManager的一个具体实现类,即o.s.s.authentication.ProviderManager,它会使用一个或更多以上描述的AuthenticationProvider实现类。由于AuthenticationProvider的使用很是广泛而且被很好的集成在ProviderManager中,因此理解它在最多见的基本配置下是如何工做的就很是重要了。

让咱们更仔细的看看在基于web用户名和密码认证的请求下,这些类的处理过程:

 

 



 
让咱们看一下在较高层次示意图中反映出的抽象工做流程,并将其细化到这个基于表单认证的具体实现。你能够看到UsernamePasswordAuthenticationFilter负责(经过代理从它的抽象父类中)建立UsernamePasswordAuthenticationToken对象(Authentication接口的一个实现),并部分填充这个对象依赖的信息,这些信息来自HttpServletRequet。可是它是从哪里获取用户名和密码的呢?

spring_security_login是什么?咱们怎么到达这个界面的?

 你可能已经发现,当你试图访问咱们JBCP Pets商店的主页时,你被重定向到http://localhost:8080/JBCPPets/spring_security_login



 URL的spring_security_login部分代表这是一个默认的登陆的页面而且是在DefaultLoginPageGeneratingFilter中命名的。咱们可使用配置属性来修改这个页面的名字从而使得它对于咱们应用来讲是惟一的。

【建议修改登陆页URL的默认值。修改后不只可以对应用或搜索引擎更友好,并且可以隐藏你使用Spring Security做为安全实现的事实。经过这种方式来掩盖Spring Security可以使得万一Spring Security被发现存在安全漏洞时,恶意黑客寻找你应用漏洞的难度。尽管经过这种方式的安全掩盖不会下降你应用的脆弱性,可是它确实可以使得一些传统的黑客工具很难肯定你的应用可以承担的住什么类型的攻击。须要注意的是,这里并非“spring”名称在URL中出现的惟一地方。咱们将在后面的章节详细阐述。】

让咱们看一下这个form的HTML源码(忽略布局信息),来看一下UsernamePasswordAuthenticationFilter指望获得的信息:

 

<form name='f' action='/JBCPPets/j_spring_security_check'  method='POST'>

  User:<input type='text' name='j_username' value=''>

  Password:<input type='password' name='j_password'/>

  <input name="submit" type="submit"/>

  <input name="reset" type="reset"/>

</form>

你能够看到用户名和密码对应的form文本域有独特的名字((j_username和j_password),而且form的action地址j_spring_security_check也并非咱们配置的。它们是怎么来的呢?

文本域的名字是UsernamePasswordAuthenticationFilter规定的,并借鉴了Java EE Servlet 2.x的规范(在SRV.12.5.3章节中),规范要求登陆的form使用特定的名字而且form的action要为特定的j_security_check值。这样的实际模式目标是容许基于Java EE servlet-based的应用可以与servlet容器的安全设施以标准的方式链接起来。

由于咱们的应用没有使用到servlet容器的安全组件,因此能够明确设置UsernamePasswordAuthenticationFilter以使得文本域有不一样的名字。这种特定的配置变化可能会比你想象的复杂。如今,咱们将要回顾一下UsernamePasswordAuthenticationFilter的生命周期,看一下它是如何进入咱们配置的(尽管咱们将会在第六章再次讲述这个配置)。

UsernamePasswordAuthenticationFilter是经过<http>配置指令的<form-login>子元素来进行配置的。正如在本章前面讲述的,咱们设置的auto-config元素将会在你没有明确添加的状况下包含了<form-login>功能。正如你可能猜想的那样,j_spring_security_check并不对应任何应用中的物理资源。它只是UsernamePasswordAuthenticationFilter监视的一个基于form登陆的URL。实际上,在Spring Security中有好几个这样的特殊的URL来实现特定的全局功能。你能在附录:参考资料中找到这些URL的一个列表。

 

用户的凭证信息是在哪里被校验的?

在咱们的简单的三步配置文件中,咱们使用了一个基于内存的凭证存储实现快速的部署和运行:

 

<authentication-manager alias="authenticationManager">

  <authentication-provider>

    <user-service>

      <user authorities="ROLE_USER" name="guest" password="guest"/>

    </user-service>

  </authentication-provider>

</authentication-manager>

咱们没有将AuthenticationProvider与任何具体的实现相关联,在这里咱们再次看到了security命名空间默认为咱们作了许多机械的配置工做。可是须要记住的是AuthenticationManager支持配置一个或多个AuthenticationProvider。<authentication-provider>声明默认谁实例化一个内置的实现,即o.s.s.authentication.dao.DaoAuthenticationProvider。<authentication-provider>声明会自动的将这个AuthenticationProvider对象织入到配置的AuthenticationManager中,固然在咱们这个场景中AuthenticationManager是自动配置的。

DaoAuthenticationProvider是AuthenticationProvider的简单封装实现并委托o.s.s.core.userdetails.UserDetailsService接口的实现类进行处理。UserDetailsService负责返回o.s.s.core.userdetails.UserDetails的一个实现类。

若是你查看UserDetails的Javadoc,你会发现它与咱们前面讨论的Authentication接口很是相似。尽管它们在方法名和功能上有些重叠的部分,可是请不要混淆,它们有着大相径庭的目的:

接口

目的

Authentication

它存储安全实体的标识、密码以及认证请求的上下文信息。它还包含用户认证后的信息(可能会包含一个UserDetails的实例)。一般不会被扩展,除非是为了支持某种特定类型的认证。

UserDetails

为了存储一个安全实体的概况信息,包含名字、e-mail、电话号码等。一般会被扩展以支持业务需求。

咱们对<user-service>子元素的声明将会触发对o.s.s.core.userdetails.memory.InMemoryDaoImpl的配置,它是UserDetailsService的一个实现。正如你可能指望的那样,这个实现将在安全XML文件中配置的用户信息放在一个内存的数据存储中。这个service的实现支持其它属性的设置,从而实现帐户的禁用和锁定。

让咱们更直观的看一下DaoAuthenticationProvider是如何交互的,从而AuthenticationManager提供认证支持:



 正如你可能想象的那样,认证是至关可配置化的。大多数的Spring Security例子要么使用基于内存的用户凭证存储要么使用JDBC(在数据库中)的用户凭证存储。咱们已经意识到修改JBCP Pets应用以实现数据库存储用户凭证是一个好主意,咱们将会在第四章来处理这个配置变化。

何时校验不经过?

Spring Security很好的使用应用级异常(expected exceptions)来表示处理各类的结果状况。你可能在使用Spring Security的平常工做中不会与这些异常打交道,可是了解它们以及它们为什么被抛出将会在调试问题或理解应用流程中很是有用。

全部认证相关的异常都继承自o.s.s.core.AuthenticationException基类。除了支持标准的异常功能,AuthenticationException包含两个域,可能在提供调试失败信息以及报告信息给用户方面颇有用处。

<!--[if !supportLists]-->l  <!--[endif]-->authentication:存储关联认证请求的Authentication实例;

<!--[if !supportLists]-->l  <!--[endif]-->extraInformation:根据特定的异常能够存储额外的信息。如UsernameNotFoundException在这个域上存储了用户名。

咱们在下面的表格中,列出了常见的异常。完整的认证异常列表能够在附录:参考资料中找到:

异常类

什么时候抛出

extraInformation内容

BadCredentialsException

如何没有提供用户名或者密码与认证存储中用户名对应的密码不匹配

UserDetails

LockedException

若是用户的帐号被发现锁定了

UserDetails

UsernameNotFoundException

若是用户名不存在或者用户没有被授予的GrantedAuthority

String(包含用户名)

这些以及其余的异常将会传递到过滤器链上,一般将会被request请求的过滤器捕获并处理,要么将用户重定向到一个合适的界面(登陆或访问拒绝),要么返回一个特殊的HTTP状态码,如HTTP 403(访问被拒绝)。


请求是怎样被受权的?

在Spring Security的默认过滤器链中,最后一个servelt过滤器是FilterSecurityInterceptor,它的做用是判断一个特定的请求是被容许仍是被拒绝。在FilterSecurityInterceptor被触发的时候,安全实体已经通过了认证,因此系统知道他们是合法的用户。(其实也有多是匿名的用户,译者注)。请记住的一点是,Authentication提供了一个方法((List<GrantedAuthority>

getAuthorities()),将会返回当前安全实体的一系列权限列表。受权的过程将使用这个方法提供的信息来决定一个特定的请求是否会被容许。

 

须要记住的是受权是一个二进制的决策——一个用户要么有要么没有访问一个受保护资源的权限。在受权中,没有模棱两可的情景。

在Spring Security中,良好的面向对象设计随处可见,在受权决策管理中也不例外。回忆一下咱们在本章前面的讨论,一个名为访问控制决策器(access decision manager)的组件负责做出受权决策。

在Spring Security中,o.s.s.access.AccessDecisionManager接口定义了两个简单而合理的方法,它们可以用于请求的决策判断流程:

<!--[if !supportLists]-->l  <!--[endif]-->supports:这个逻辑操做实际上包含两个方法,它们容许AccessDecisionManager的实现类判断是否支持当前的请求。

<!--[if !supportLists]-->l  <!--[endif]-->decide:基于请求的上下文和安全配置,容许AccessDecisionManager去核实访问是否被容许以及请求是否可以被接受。decide方法实际上没有返回值,经过抛出异常来代表对请求访问的拒绝。

 

与AuthenticationException及其子类在认证过程当中的使用很相似,特定类型的异常可以代表应用在受权决策中的不一样处理结果。o.s.s.access.AccessDeniedException是在受权领域里最多见的异常,所以值得过滤器链进行特殊的处理。咱们将在第六章中详细介绍它的高级配置。

AccessDecisionManager是可以经过标准的Spring bean绑定和引用实现彻底的自定义配置。AccessDecisionManager的默认实现提供了一个基于AccessDecisionVoter接口和投票集合的受权机制。

投票器(voter)是在受权过程当中的一个重要角色,它的做用是评估如下的内容:

<!--[if !supportLists]-->l  <!--[endif]-->要访问受保护资源的请求所对应上下文(如URL请求的IP地址);

<!--[if !supportLists]-->l  <!--[endif]-->用户的凭证信息(若是存在的话);

<!--[if !supportLists]-->l  <!--[endif]-->要试图访问的受保护资源;

<!--[if !supportLists]-->l  <!--[endif]-->系统的配置以及要访问资源自己的配置参数。

AccessDecisionManager还会负责传递要请求资源的访问声明信息(在代码中为ConfigAttribute接口的实现类)给投票器。在web URL的请求中,投票器将会获得资源的访问声明信息。若是看一下咱们配置文件中很是基础的拦截声明,咱们可以看到ROLE_USER被设置为访问配置并用于用户试图访问的资源:

 

<intercept-url pattern="/*" access="ROLE_USER"/>

投票器将会对用户是否可以访问指定的资源作出一个判断。Spring Security容许过滤器在三种决策结果中作出一种选择,它们的逻辑定义在o.s.s.access.AccessDecisionVoter接口中经过常量进行了定义。

决策类型

描述

Grant (ACCESS_GRANTED)

投票器容许对资源的访问

Deny (ACCESS_DENIED)

投票器拒绝对资源的访问

Abstain (ACCESS_ABSTAIN)

投票器对是否可以访问作了弃权处理(即没有作出决定)。可能在多种缘由下发生,如:

<!--[if !supportLists]-->l  <!--[endif]-->投票器没有确凿的判断信息;

<!--[if !supportLists]-->l  <!--[endif]-->投票器不能对这种类型的请求作出决策。

正如你从访问决策相关类和接口的设计中能够猜到的那样,Spring Security的这部分被精心设计,因此认证和访问控制的使用场景并不只仅限于web领域。咱们将会在:精确的访问控制中关于方法级别的安全时,再次讲解投票器和访问控制管理。

当将他们组合在一块儿,“对web请求的默认认证检查”的总体流程将以下图所示:



 咱们能够看到ConfigAttribute可以从配置声明(在DefaultFilterInvocationSecurityMetadataSource类中保存)中传递数据到投票器,投票器并不须要其余的类来理解ConfigAttribute的内容。这种分离可以为新类型的安全声明(例如咱们将要看到的方法安全声明)使用相同的访问决策模式提供基础。

配置access decision集合

实际上Spring Security容许经过security命名空间来配置AccessDecisionManager。<http>元素的access-decision-manager-ref属性来指明一个实现了AccessDecisionManager的Spring Bean。Spring Security提供了这个接口的三个实现类,都在o.s.s.access.vote包中:

类名

描述

AffirmativeBased

若是有任何一个投票器容许访问,请求将被马上容许,而无论以前可能有的拒绝决定。

ConsensusBased

多数票(容许或拒绝)决定了AccessDecisionManager的结果。平局的投票和空票(全是弃权的)的结果是可配置的。

UnanimousBased

全部的投票器必须全是容许的,不然访问将被拒绝。

 

配置使用UnanimousBased的访问决策管理器(access decision manager)

若是你想修改咱们的应用来使用UnanimousBased访问决策管理器,咱们须要修改两个地方。首先让咱们在<http>元素上添加access-decision-manager-ref属性:

 

<http auto-config="true"

      access-decision-manager-ref="unanimousBased" >

这是一个标准的Spring Bean的引用,因此这须要对应一个bean的id属性。接下来,咱们要定义这个bean(在dogstore-base.xml中),并与咱们引用的有相同的id:

 

<bean class="org.springframework.security.access.vote.UnanimousBased"

      id="unanimousBased">

  <property name="decisionVoters">

    <list>

      <ref bean="roleVoter"/>

      <ref bean="authenticatedVoter"/>

    </list>

  </property>

</bean>

<bean class="org.springframework.security.access.vote.RoleVoter"

id="roleVoter"/>

<bean class="org.springframework.security.access.vote.

AuthenticatedVoter" id="authenticatedVoter"/>

你可能象知道decisionVoters属性是什么。这个属性在咱们不声明AccessDecisionManager时,是自动配置的。默认的AccessDecisionManager要求咱们配置投票器的一个列表,它们将会在认证决策时用到。这里列出的两个投票器是security命名空间配置默认提供的。

遗憾的是,Spring Security没有为咱们提供太多的投票器,可是实现AccessDecisionVoter接口并在配置中添加咱们的实现并非一件困难的事情。咱们将在第六章看一个例子。

咱们引用的两个投票器介绍以下:

类名

描述

例子

o.s.s.access.

vote.RoleVoter

检查用户是否拥有声明角色的权限(GrantedAuthority)。access属性定义了GrantedAuthority的一个列表。预期会有ROLE_前缀,但这也是可配置的。

access="ROLE_USER,ROLE_ADMIN"

o.s.s.access.

vote.AuthenticatedVoter

支持特定类型的声明,容许使用通配符:

<!--[if !supportLists]-->l  <!--[endif]-->IS_AUTHENTICATED_FULLY——容许提供完整的用户名和密码的用户访问;

<!--[if !supportLists]-->l  <!--[endif]-->IS_AUTHENTICATED_REMEMBERED——若是用户是经过remember me功能认证的则容许访问;

<!--[if !supportLists]-->l  <!--[endif]-->IS_AUTHENTICATED_ANONYMOUSLY——容许匿名用户访问。

access=" IS_AUTHENTICATED_ANONYMOUSLY"

使用 Spring 表达式语言配置访问控制

基于角色标准投票机制的标准实现是使用 RoleVoter ,还有一种替代方法可用来定义语法复杂的投票规则即便用Spring 表达式语言( SpEL )。要实现这一功能的直接方式是在 <http> 配置元素上添加 use-expressions 属性:

 

<http auto-config="true"

      use-expressions="true">

添加后将要修改用来进行拦截器规则声明的 access 属性,改成 SpEL 表达式。 SpEL 容许使用特定的访问控制规则表达式语言。与简单的字符串如 ROLE_USER 不一样,配置文件能够指明表达式语言触发方法调用、引用系统属性、计算机值等等。

SpEL 的语法与其余的表达式语言很相似,如在 Tapestry 等框架中用到的 Object Graph Notation Language (OGNL) ,以及用于 JSP 和 JSF 的 Unified Expression Language 。它的语法面很广,已经超出了本书的覆盖范围,咱们将会经过几个例子为你构建表达式提供一些确切的帮助。

须要注意的重要一点是,若是你经过使用 use-expressions 属性启用了 SpEL 表达式访问控制,将会使得自动配置的 RoleVoter 实效,后者可以使用角色的声明,正如在前面的例子所见到的那样:

 

<intercept-url pattern="/*" access="ROLE_USER"/>

这意味着若是你仅仅想经过角色来过滤请求的话,访问控制声明必要要进行修改。幸运的的是,这已经被充分考虑过了,一个 SpEL 绑定的方法 hasRole 可以检查角色。若是咱们要使用表达式来重写例子的配置,它可能看起来以下所示:

 

<http auto-config="true" use-expressions="true">

  <intercept-url pattern="/*" access="hasRole('ROLE_USER')"/>

</http>

正如你可能预料的那样, SpEL 使用了一个不一样的 Voter 实现类,即o.s.s.web.access.expression.WebExpressionVoter ,它能理解怎样解析 SpEL 表达式。 WebExpressionVoter 借助于 o.s.s.web.access.expression.WebSecurityExpressionHandler 接口的一个实现类来达到这个目的。WebSecurityExpressionHandler 同时负责评估表达式的执行结果以及提供在表达式中应用的安全相关的方法。这个接口的默认实现对外暴露了 o.s.s.web.access.expression.WebSecurityExpressionRoot 类中定义的方法。

这些类的流程以及关系以下图所示:



 为实现 SpEL 访问控制表达式的方法和伪属性( pseudo-property )在类 WebSecurityExpessionRoot 及其父类的公共方法中进行了声明。

【伪属性( pseudo-property )是指没有传入参数并符合 JavaBeans 的 getters 命名格式的方法。这容许 SpEL 表达式可以省略方法的圆括号以及 is 或 get 的前缀。如 isAnonymous() 方法能够经过 anonymous 伪属性来访问。】

Spring Security 3 提供的 SpEL 方法和伪属性在如下的表格中进行了描述。要注意的是没有被标明“ web only ”的方法和属性能够在保护其余类型的资源中使用,如在保护方法调用时。示例表示的方法和属性是使用在 <intercept-url> 的 access 声明中。

方法

Web only?

描述

示例

hasIpAddress

(ipAddress)

Yes

用于匹配一个请求的 IP 地址或一个地址的网络掩码

access="hasIpAddress('

162.79.8.30')"

access="hasIpAddress('

162.0.0.0/224')"

hasRole(role)

No

用于匹配一个使用GrantedAuthority 的角色(相似于RoleVoter )

access="hasRole('ROLE

USER')"

hasAnyRole(role)

No

用于匹配一个使用GrantedAuthority 的角色列表。用户匹配其中的任何一个都可放行。

access="hasRole('ROLE_

USER','ROLE_ADMIN')"

除了以上表格中的方法,在 SpEL 表达式中还有一系列的方法能够做为属性。它们不须要圆括号或方法参数。

属性

Web only?

描述

例子

permitAll

No

任何用户都可访问

access="permitAll"

denyAll

NO

任何用户均不可访问

access="denyAll"

anonymous

NO

匿名用户可访问

access="anonymous"

authenticated

NO

检查用户是否定证过

access="authenticated"

rememberMe

No

检查用户是否经过remember me 功能认证的

access="rememberMe"

fullyAuthenticated

No

检查用户是否经过提供完整的凭证信息来认证的

access="fullyAuthenticated"

须要记住的是, voter 的实现类必须基于请求的上下文返回一个投票的结果(容许、拒绝或者弃权)。你可能会认为hasRole 会返回一个 Boolean 值,实际上正是如此。基于 SpEL 的访问控制声明必须是返回 Boolean 类型的表达式。返回值为 true 意味着投票器的结果是容许访问, false 的结果意味着投票器拒绝访问。

【若是一个表达式的值不是 Boolean 类型的,你将会获得以下的一个异常信息:org.springframework.expression.spel.SpelException:

EL1001E:Type conversion problem, cannot convert from

class java.lang.Integer to java.lang.Boolean 】

另外,表达式不能返回一个弃权类型的结果,除非访问控制声明不是一个合法 SpEL 表达式,在这种状况下投票器将会放弃投票。

若是你不在意这些细小的约束, SpEL 访问控制声明可以提供一种灵活的配置访问控制决策的方式。

 

在本章中,咱们提供了安全领域两个重要概念即认证和受权的介绍。

 

在整体上了解咱们要进行安全保护的系统;

使用 Spring Security 的自动配置在三步以内实现了咱们应用的安全配置;

了解了在 Spring Security 中 servlet 过滤器的使用及重要性;

了解了认证和受权过程当中重要的角色,包括一些重要类实现的详细介绍如 Authentication 和 UserDetails

体验了与访问控制规则有关的 SpEL 表达式的配置。

在接下来的一章中,咱们将经过添加一些加强用户体验的功能,把基于用户名和密码的认证提升一个新的水平。

相关文章
相关标签/搜索