《Spring Security3》第二章翻译

来源:互联网 发布:linux ls 时间格式 编辑:程序博客网 时间:2024/06/05 00:17

第二章  Spring Security起步

在本章中,我们将要学习SpringSecurity背后的核心理念,包括重要的术语和产品架构。我们将会关注配置Spring Security的一些方式以及对应用的作用。

最重要的是为了解决工作中的问题,我们要开始使得JBCP Pets的在线商店系统变得安全。我们将会通过分析和理解认证如何保护在线商店的适当区域来解决在第一章:一个不安全应用的剖析中审计人员发现的第一个问题,即缺少URL保护和统一的认证造成的权限扩散

在本章的内容中,我们将会涉及:

l了解应用中安全的重要概念;

l使用Spring Security的快速配置功能,为JBCPPets在线商店实现基本层次的安全;

l理解SpringSecurity的全貌;

l探讨认证和授权的标准配置和选项;

l在SpringSecurity访问控制中使用Spring的表达式语言(Spring Expression Language)

 


 

安全的核心概念

         由于安全审计结果的启示作用,你研究了Spring Security并确定它能够提供一个坚实的基础,以此可以构建一个安全的系统来解决在安全审计JBCP Pet在线商店中发现的问题,而那个系统是基于Spring Web MVC开发的。

         为了SpringSecurity的使用更高效,在开始评估和提高我们应用的安全状况之前,先了解一些关键的概念和术语是很重要的。

认证

         正如我们在第一章所讨论的那样,认证是鉴别我们应用中的用户是他们所声明的那个人。你可能在在线或线下的日常生活中,遇到不同场景的认证:

l  凭据为基础的认证:当你登录e-mail账号时,你可能提供你的用户名和密码。E-mail的提供商会将你的用户名与数据中的记录进行匹配,并验证你提供的密码与对应的记录是不是匹配。这些凭证(用户名和密码,译者注)就是e-mail系统用来鉴别你是一个合法用户的。首先,我们将首先使用这种类型的认证来保护我们JBCP Pet在线商店的敏感区域。技术上来说,e-mail系统能够检查凭证信息不一定非要使用数据库而是各种方式,如一个企业级的目录服务器如Microsoft Active Directory。一些这种类型的集成方式将在本书的第二部分讲解。

l  两要素认证:当你想从自动柜员机取钱的时候,你在被允许取钱和做其他业务前,你必须先插卡并输入你的密码。这种方式的认证与用户名和密码的认证方式很类似,与之不同的是用户名信息被编码到卡的磁条上了。联合使用物理磁卡和用户输入密码能是银行确认你可能有使用这个账号的权限。联合使用密码和物理设备(你的ATM卡)是一种普遍存在的两要素认证形式。专业来看,在安全领域,这种类型的设备在安全性要求高的系统中很常见,尤其是处理财务或个人识别信息时。硬件设备如RSA的SecurId联合使用了基于时间的硬件和服务端的认证软件,使得这样的环境极难被破坏。

l  硬件认证:早上当你启动汽车时,你插入钥匙并打火。尽管和其他的两个例子很类似,但是你的钥匙和打火装置的匹配是一种硬件认证的方式。

 

其实会有很多种的认证方式来解决硬件和软件的安全问题,它们各自也有其优缺点。我们将会在本书的后面章节中介绍它们中的一些,因为它们适用于Spring Security。事实上,本书的后半部分基本上都是原来介绍很多通用的认证方式用Spring Security的实现。

SpringSecurity扩展了java标准概念中的已认证安全实体(对应单词principal)(java.security.Principal),它被用来唯一标识一个认证过的实体。尽管一个典型的安全实体通常一对一的指向了系统中的一个用户,但它也可能对应系统的各种客户端,如web service的客户端、自动运行的feed聚合器(automated batch feed)等等。在大多数场景下,在你使用Spring Security的过程中,一个安全实体(Principal)只是简单地代表一个用户(user),所以我当我们说一个安全实体的时候,你可以将其等同于说用户。

授权

         授权通常涉及到两个不同的方面,他们共同描述对安全系统的可访问性。

        

         第一个是已经认证的安全实体与一个或多个权限(authorities)的匹配关系(通常称为角色)。例如,一个非正式的用户访问你的网站将被视为只有访问的权限而一个网站的管理员将会被分配管理的权限。

         第二个是分配权限检查给系统中要进行安全保护的资源。通常这将会在系统的开发过程中进行,有可能会通过代码进行明确的声明也可能通过参数进行设置。例如,在我们应用中管理宠物商店详细目录的界面只能对具有管理权限的用户开放。

         【要进行安全保护的资源可以是系统的任何内容,它们会根据用户的权限进行有选择的可访问控制。web应用中的受保护资源可以是单个的页面、网站的一个完整部分或者一部分界面。相反的,受保护的业务资源可能会是业务对象的一个方法调用或者单个的业务对象。】

        

你可能想象的出对一个安全实体的权限检查过程,查找它的用户账号并确定它是不是真的为一个管理员。如果权限检查确定这个试图访问受保护区区域的安全实体实际上是管理员,那么这个请求将会成功,否则,这个安全实体的请求将会因为它缺少足够的权限而被拒绝。

我们更近距离的看一个特定的受保护资源——产品目录的编辑界面。目录的编辑界面需要管理员才能访问(毕竟,我们不希望普通的用户能够调整我们的目录层次),因此当一个安全实体访问它的时候会要求特定等级的权限。

当我们思考一个网站的管理员试图访问受保护的资源时,权限控制决定是如何做出的时候,我们猜想对受保护资源的权限的检查过程可以用集合理论很简明的进行表述。我们将会使用维恩图来展现对管理用户的这个决策过程:


 

<!--[endif]-->

对这个页面来说,在用户权限(普通用户和管理员)和需要权限(管理员)之间有一个交集,所以在交集中的用户将能够进行访问。

可以与没有授权的访问者进行对比:


 

权限集合没有交集,没有公共的元素。所以,用户将会被拒绝访问这个界面。至此,我们已经介绍了对资源授权的简单原理。

 

实际上,会有真正的代码来决定用户是允许还是被拒绝访问受保护的资源。下面的图片在整体上描述了这个过程,正如Spring Security所使用的那样:


 

 

我们可以看到,有一个名为访问决策管理器(accessdecision manager)的组件来负责决定一个安全实体是不是有适当的访问权限,判断基于安全实体具备的权限与被请求资源所要求资源的匹配情况。

安全访问控制器对访问是否被允许的判断过程可能会很简单,就像查看安全实体所拥有的权限集合与被访问资源所要求的资源集合是不是有交集。

让我们在JBCP Pets应用中简单使用SpringSecurity,并将会更详细的阐述认证和授权

三步之内使我们的应用变得安全

         尽管SpringSecurity的配置可能会很难,但是它的作者是相当为我们着想的,因为他们为我们提供了一种简单的机制来使用它很多的功能并可以此作为起点。以这个为起点,额外的配置能够实现应用的分层次详细的安全控制。

         我们将从我们不安全的在线商店开始,并且使用三步操作将它变成一个拥有基本用户名和密码安全认证的站点。这个认证仅仅是为了阐述使用Spring Security使我们应用变得安全的步骤,所以你可能会发现这样的方式会有明显不足,这将会引领我们在以后的配置中不断进行改良。

实现Spring Security的XML配置文件

         起点配置的第一步是创建一个XML的配置文件,用来描述所有需要的Spring Security组件,这些组件将会控制标准的web请求。

         在WEB-INFO目录下建立一个名为dogstore-security.xml的XML文件。文件的内容如下所示:

Xml代码  收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans:beans xmlns="http://www.springframework.org/schema/security"  
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.        xmlns:beans="http://www.springframework.org/schema/beans"  
  5.        xsi:schemaLocation="  
  6.        http://www.springframework.org/schema/beans  
  7.        http://www.springframework.org/schema/beans/spring-beans.xsd  
  8.        http://www.springframework.org/schema/security  
  9.        http://www.springframework.org/schema/security/   
  10.               spring-security-3.0.xsd">  
  11.   <http auto-config="true">  
  12.     <intercept-url pattern="/*" access="ROLE_USER"/>  
  13.   </http>  
  14.   <authentication-manager alias="authenticationManager">  
  15.     <authentication-provider>  
  16.       <user-service>  
  17.         <user authorities="ROLE_USER" name="guest" password="guest"/>  
  18.       </user-service>  
  19.     </authentication-provider>  
  20.   </authentication-manager>   
  21. </beans:beans>  
 这是我们应用中获得最小安全配置的唯一一个Spring Security配置文件。这个配置文件的格式使用了Spring Security特定的XML语法,一般称之为security命名空间。它在XML的命名空间中声明(http://www.springframework.org/schema/security)并与XML配置元素关联。我们将在在第六章:高级配置与扩展中讨论一种替代的配置方式,即使用传统的Spring Bean设置方式。

【讨厌Spring XML配置的用户可能会失望了,因为Spring Security没有像Spring那样提供可替代的注解机制。仔细想一下也是可以理解的,因为Spring Security关注的是整个系统而不是单个对象或类。但未来,我们可能能够在Spring MVC的控制器上使用安全注解,而不是在一个配置文件中指明URL模式!】

尽管在Spring Security中注解并不普遍,但正如你所预料的那样,对类或方法进行的配置是可以通过注解来完成的。我们将在第五章:精确的访问控制中介绍。

添加Spring DelegatingFilterProxy到web.xml文件

         SpringSecurity对我们应用的影响是通过一系列的ServletRequest过滤器实现的(我们将会在介绍Spring Security架构的时候进行阐述)。可以把这些过滤器想成位于每个到我们应用的请求周围的具有安全功能的三明治。(这个比喻够新鲜,不过Spring Security的核心确实就是一系列介于真正请求之间的过滤器,译者注)。

         SpringSecurity使用了o.s.web.filter.DelegatingFilterProxy这个servlet过滤器来包装所有的应用请求,从而保证它们是安全的。

【DelegatingFilterProxy实际上是Spring框架提供的,而不是安全特有的。这个过滤器一般在Spring构建的web应用工程中使用,并将依赖于servlet过滤器的Spring Bean与Servle过滤器的生命周期结合起来。】

         通过在web.xml部署描述文件中添加如下的代码,就可以配置这样一个过滤器。这段代码位于Spring MVC的<servlet-mapping>之后:

Xml代码  收藏代码
  1. <filter>  
  2.   <filter-name>springSecurityFilterChain</filter-name>  
  3.   <filterclass>  
  4.     org.springframework.web.filter.DelegatingFilterProxy  
  5.   </filter-class>  
  6. </filter>  
  7. <filter-mapping>  
  8.   <filter-name>springSecurityFilterChain</filter-name>  
  9.   <url-pattern>/*</url-pattern>  
  10. </filter-mapping>  
 我们所做的是使用一个ServletRequest过滤器并将它配置成处理匹配给定URL模式(/*)的请求。因为我们配置的这个通配符模式匹配所有的URL,所以这个过滤器将会应用于每个请求。

         如果你有心的话,可能会发现这与我们的Spring Security配置文件即dogstore-security.xml没有任何的关系。为了实现这个,我们需要添加一个XML配置文件的应用到web应用的部署描述文件web.xml中。

添加Spring Security XML配置文件的应用到web.xml

         取决于你如何配置你的Springweb应用,不知你是否已经在web.xml中有了对XML 配置文件的明确引用。Spring web的ContextLoaderListener的默认行为是寻找与你的Spring web servlet同名的XML配置文件。让我们看一下如何添加这个新的Spring Security XML配置文件到已经存在的JBCP Pet商店站点中。

         首先,我们需要看一下这个应用是否使用了Spring MVC的自动查找XML配置文件的功能。我们看一下在web.xml中servlet的名字,以<servlet-name>原始进行标识:

Xml代码  收藏代码
  1. <servlet>  
  2.   <servlet-name>dogstore</servlet-name>  
  3.   <servletclass>  
  4.     org.springframework.web.servlet.DispatcherServlet  
  5.   </servlet-class>  
  6.   <load-on-startup>1</load-on-startup>  
  7. </servlet>  
 Servlet的名字是(<servlet-name>)是dogstore,所以Spring的约定胜于配置(Convention

over Configuration,CoC)将会在WEB-INF目录下寻找名为dogstore-servelt.xml的配置文件。我们没有覆盖这种默认行为,你能在WEB-INF目录下找到这个文件,它包含了一些Spring MVC相关的配置。

【很多Spring Web Flow和Spring MVC的用户并不明白这些CoC规则是如何生效的以及Spring的代码中在何处声明了这些规则。o.s.web.context.support.XmlWebApplicationContext和它的父类是了解这些的一个很好的起点。JavaDoc在讲解基于Spring MVC框架的web工程的一些参数配置方面也做得不错。】

         也可以声明额外的SpringApplicationContext配置文件,它将会先于Spring MVC servle关联的配置文件加载。这通过Spring的o.s.web.context.ContextLoaderListener创建一个独立于Spring MVCApplicationContext的另一个ApplicationContext来实现。这是通常的非Spring MVC beans配置的方式,也为Spring Security相关的配置提供了一个好地方。

         在web应用的部署描述文件中,用来配置ContextLoaderListener的XML文件地址是在<context-param>元素中给出的:

Xml代码  收藏代码
  1. <context-param>  
  2.   <param-name>contextConfigLocation</param-name>  
  3.   <param-value>  
  4.     /WEB-INF/dogstore-base.xml  
  5.   </param-value>  
  6. </context-param>  

 dogstore-base.xml文件中包含了一些标准的Springbean的配置,如数据源、服务层bean等。现在,我们能够添加一个包含Spring Security的XML配置文件了,下面是更新后的<context-param>元素:

Xml代码  收藏代码
  1. <context-param>  
  2.   <param-name>contextConfigLocation</param-name>  
  3.   <param-value>  
  4.     /WEB-INF/dogstore-security.xml  
  5.     /WEB-INF/dogstore-base.xml  
  6.   </param-value>  
  7. </context-param>  

 在我们添加完新的SpringSecurity配置文件到web部署文件并重启web工程后,尝试访问应用的首页地址:http://localhost:8080/JBCPPets/home.do,你将会看到如下的界面:




 漂亮!我们已经使用SpringSecurity在三步之内实现了一个简单的安全层。在这里,你可以使用guest作为用户名和密码进行登录。接着你将能够看到JBCP Pets应用的一个简单首页。

为了简便起见,本章的源码只包含了全部JBCPPets整个应用的很小一部分(只有一个页面)。我们这么做是为了更简洁,同时也能让构建和部署应用时不必考虑我们将在后续章节中涉及到的附加功能。这也提供了一个很好的起点让你快速的体验参数的配置并重新部署以查看它们是否生效。

注意这些不足之处

         到此为止,思考一下我们所做的事情。你可能已经意识到在产品化之前应用存在很多很明显的问题,这需要很多后续的工作和对Spring Security产品的了解。尝试列举一下在将这个实现安全功能的站点上线前还需要什么样的完善。

         实现SpringSecurity起点级别的配置是相当迅速的,它已经提供了一个登录界面,用户名密码认证以及自动拦截我们在线商店的URL。但是在自动配置给我们提供的功能与我们的最终目标之间有很大的差距:

<!--[if !supportLists]-->l  <!--[endif]-->我们将用户名、密码以及角色信息硬编码到XML配置文件中。你是否还记得我们添加的这部分XML内容:

Xml代码  收藏代码
  1.        <authentication-manager alias="authenticationManager">  
  2.   <authentication-provider>  
  3.     <user-service>  
  4.       <user authorities="ROLE_USER" name="guest"  password="guest"/>  
  5.     </user-service>  
  6.   </authentication-provider>  
  7. </authentication-manager>  
 你可以看到用户名和密码在这个文件中。你不可能愿意为每一个系统的用户都在这个XML文件中添加一个声明。要解决这个问题,你需要使用一个基于数据库的认证提供者(authentication provider)来替代它(我们将第四章:凭证安全存储完成它)。

<!--[if !supportLists]-->l  <!--[endif]-->我们对所有的页面都进行了安全控制,包括一些潜在客户可能想匿名访问的界面。我们需要完善系统的角色以适应匿名的、认证过的以及管理权限的用户(这也将在第四章中讨论)。

<!--[if !supportLists]-->l  <!--[endif]-->登录界面非常应用,但是它太基础了而且与我们JBCP商店风格一点也不一致。我们需要添加一个登录的form界面,并与我们应用的外观和风格一致(我们将在下一章解决这个问题)。

常见问题

         很多用户在初次使用SpringSecurity时会遇到一些问题。我们列出了几个常见的问题和建议。我们希望你能够一直跟随着本书的讲解,运行我们示例代码。

<!--[if !supportLists]-->l  <!--[endif]-->确保你的应用在添加SpringSecurity之前是可以编译和部署的。必要的时候看一些你所使用的servlet容器的入门级例子和文档。

<!--[if !supportLists]-->l  <!--[endif]-->通常使用一个IDE如Eclipse会极大地简化你使用的servlet容器。不仅能够保证部署准确无误,它所提供的控制台日志也很易读可用来检查错误。你还可以在关键的位置添加断点,运行的时候会触发从而简化分析错误的过程。

<!--[if !supportLists]-->l  <!--[endif]-->如果你的XML配置文件不正确,你会得到这样的提示信息(或类似的):org.xml.sax.SAXParseException: cvc-elt.1: Cannot find thedeclaration of element 'beans'。为实现Spring Security的正确配置需要各种各样的XML命名空间引用,这可能会使很多用户感到迷惑。重新检查一下这个例子,仔细查看一下schame声明的部分,并使用XML校验器来保证你没有不合法的XML。

<!--[if !supportLists]-->l  <!--[endif]-->确保你所使用的Spring和SpringSecurity版本匹配,并确保没有你的应用中不存在没用的Spring jar包。

关注这个系列的同学们有福了,我把前两章翻译的doc文档上传了,欢迎传播。有谬误之处,请不吝指正。

 

 

使用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访问控制声明能够提供一种灵活的配置访问控制决策的方式。

 

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

 

l在总体上了解我们要进行安全保护的系统;

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

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

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

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

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