域对象安全(ACL)
1.1。概观
复杂应用程序一般会发现须要定义访问权限,而不只仅是在Web请求或方法调用级别。相反,安全决策须要包括谁(认证),其中(MethodInvocation)和什么(SomeDomainObject)。换句话说,受权决策还须要考虑方法调用的实际域对象实例主题。javascript
想象你正在为宠物诊所设计一个应用程序。您的春季应用将有两个主要的用户群:宠物诊所的工做人员以及宠物诊所的客户。员工将能够访问全部的数据,而您的客户只能看到本身的客户记录。为了使其更有趣,您的客户能够容许其余用户查看他们的客户记录,例如他们的“小狗学前班”导师或当地“小马俱乐部”的总裁。使用Spring Security做为基础,您可使用几种方法:html
- 编写您的业务方法来强制执行安全性。您能够咨询客户域对象实例中的集合,以肯定哪些用户能够访问。经过使用SecurityContextHolder.getContext()。getAuthentication(),您将能够访问Authentication对象。
- 编写一个AccessDecisionVoter,以便从存储在Authentication对象中的GrantedAuthority []执行安全性。这将意味着您的AuthenticationManager须要使用自定义GrantedAuthority []来填充身份验证,表明主体能够访问的每一个Customer域对象实例。
- 编写一个AccessDecisionVoter来强制执行安全性,并直接打开目标客户域对象。这意味着您的选民须要访问容许其检索Customer对象的DAO。而后它将访问客户对象的批准用户的集合并作出适当的决定。
这些方法中的每一种都是彻底合法的。可是,首先将您的受权检查与您的业务代码相结合。其中的主要问题包括增长单元测试的难度,以及在其余地方重复使用客户受权逻辑将更加困难。从Authentication对象获取GrantedAuthority [] s也很好,但不会扩展到大量的Customer。若是用户能够访问5,000个客户(在这种状况下不太可能,可是想象一下,若是它是一个大型的小马俱乐部的受欢迎的兽医!),构建Authentication对象所需的内存消耗量和时间将是不合须要的。最终的方法,直接从外部代码打开客户,多是三个中最好的。它实现了关注点的分离,而且不会滥用内存或CPU周期,可是因为AccessDecisionVoter和最终业务方法自己都将执行对负责检索Customer对象的DAO的调用,所以仍然没有效率。每一个方法调用两次访问显然是不但愿的。另外,列出全部方法,您将须要从头开始编写本身的访问控制列表(ACL)持久性和业务逻辑。java
幸运的是,还有另外一种选择,下面咱们来讨论一下。web
1.2。关键概念
Spring Security的ACL服务在spring-security-acl-xxx.jar中发布。您将须要将此JAR添加到您的类路径中以使用Spring Security的域对象实例安全功能。ajax
Spring Security的域对象实例安全功能集中在访问控制列表(ACL)的概念上。系统中的每一个域对象实例都有本身的ACL,而且ACL记录谁能够和不能与该域对象一块儿使用的详细信息。考虑到这一点,Spring Security为您的应用程序提供三种与ACL相关的主要功能:正则表达式
- 一种有效地检索全部域对象的ACL条目的方法(并修改这些ACL)
- 在调用方法以前,确保给定主体的方法容许与对象一块儿工做
- 在调用方法以后,确保给定委托人的方式容许与对象(或其返回的东西)一块儿工做
如第一个要点所示,Spring Security ACL模块的主要功能之一是提供检索ACL的高性能方法。这种ACL存储库功能是很是重要的,由于系统中的每一个域对象实例可能具备多个访问控制条目,而且每一个ACL可能会以相似树状的结构从其余ACL继承(Spring支持即开即用安全,是很是经常使用的)。 Spring Security的ACL功能通过精心设计,能够提供ACL的高性能检索,以及可插拔缓存,死锁 - 最小化数据库更新,独立于ORM框架(咱们直接使用JDBC),适当封装和透明数据库更新。算法
给定的数据库是ACL模块操做的核心,让咱们来探索实现中默认使用的四个主表。下表按照典型的Spring Security ACL部署中的大小顺序显示,最后列出最多行的表:spring
- ACL_SID容许咱们惟一地标识系统中的任何主体或权限(“SID”表示“安全身份”)。惟一的列是ID,SID的文本表示,以及用于指示文本表示是指主体名称仍是GrantedAuthority的标志。所以,每一个惟一主体或GrantedAuthority都有一行。当在接收权限的上下文中使用时,SID一般称为“收件人”。
- ACL_CLASS容许咱们惟一地标识系统中的任何域对象类。惟一的列是ID和Java类名。所以,咱们但愿存储ACL权限的每一个惟一的类都有一行。
- ACL_OBJECT_IDENTITY存储系统中每一个惟一域对象实例的信息。列包括ID,ACL_CLASS表的外键,惟一标识符,所以咱们知道咱们提供哪些ACL_CLASS实例,父级为ACL_SID表的外键以表示域对象实例的全部者,以及是否容许ACL条目从任何父ACL继承。对于咱们存储ACL权限的每一个域对象实例,咱们都有一行。
- 最后,ACL_ENTRY存储分配给每一个收件人的各个权限。列包括ACL_OBJECT_IDENTITY的外键,收件人(即ACL_SID的外键),是否进行审计,以及表示授予或拒绝的实际权限的整数位掩码。对于接收到域对象使用权限的每一个收件人,咱们都有一行。
如最后一段所述,ACL系统使用整数位掩码。不用担忧,您不须要知道使用ACL系统的位移更精细的点,但足以说咱们能够打开或关闭32位。这些位中的每个表示许可,默认状况下,读取(位0),写入(位1),建立(位2),删除(位3)和管理(位4)。若是您但愿使用其余权限,您能够轻松实现本身的权限实例,而其他的ACL框架将在不了解扩展的状况下运行。数据库
重要的是要了解,系统中的域对象数量对咱们选择使用整数位掩码的事实绝对没有影响。虽然您有32位可用权限,您能够拥有数十亿个域对象实例(这将意味着ACL_OBJECT_IDENTITY中的数十亿行,极可能是ACL_ENTRY)。咱们提出这一点,由于咱们发现有时人们错误地认为他们须要一些每一个潜在的域对象,而不是这样。apache
如今咱们已经提供了ACL系统的基本概述,以及表格结构的外观,咱们来探讨关键接口。关键接口是:
- Acl:每一个域对象都有一个且只有一个Acl对象,它们内部持有AccessControlEntry,而且知道Acl的全部者。 Acl不直接引用域对象,而是引用ObjectIdentity。 Acl存储在ACL_OBJECT_IDENTITY表中。
- AccessControlEntry:Acl拥有多个AccessControlEntry,它们一般在框架中缩写为ACE。每一个ACE指的是“Permission”,Sid和Acl的特定元组。 ACE还能够授予或不授予并包含审核设置。 ACE存储在ACL_ENTRY表中。
- Permission:一个权限表明一个不可变的位掩码,为位掩码和输出信息提供了便利的功能。上面提供的基本权限(位0到4)包含在BasePermission类中。
- Sid:ACL模块须要引用principal和GrantedAuthority []。间接级别由Sid接口提供,Sid接口是“安全身份”的缩写。普通类包括PrincipalSid(用于表示Authentication对象中的主体)和GrantedAuthoritySid。安全身份信息存储在ACL_SID表中。
- ObjectIdentity:每一个域对象在ACL模块内部由ObjectIdentity表示。默认实现称为ObjectIdentityImpl。
- AclService:检索适用于给定ObjectIdentity的Acl。在包含的实现(JdbcAclService)中,检索操做被委派给LookupStrategy。 LookupStrategy提供了一个高度优化的策略,用于检索ACL信息,使用批量检索(BasicLookupStrategy),并支持利用物化视图,层次查询和相似以性能为中心的非ANSI SQL功能的自定义实现。
- MutableAclService:容许修改的Acl被显示为持久性。若是您不但愿使用此界面不是必需的。
请注意,咱们即时的AclService和相关数据库类都使用ANSI SQL。所以,这应与全部主要数据库配合使用。在撰写本文时,系统已经使用Hypersonic SQL,PostgreSQL,Microsoft SQL Server和Oracle成功测试。
两个示例随Spring Security一块儿发布,演示了ACL模块。第一个是联系人样本,另外一个是文档管理系统(DMS)示例。咱们建议您查看这些例子。
1.3。入门
要开始使用Spring Security的ACL功能,您须要将ACL信息存储在某个地方。这须要使用Spring实例化一个DataSource。而后将DataSource注入到JdbcMutableAclService和BasicLookupStrategy实例中。后者提供了高性能的ACL检索功能,前者提供了mutator功能。请参阅Spring Security附带的示例配置示例。您还须要使用上一节中列出的四个特定于ACL的表来填充数据库(请参阅相应SQL语句的ACL示例)。
一旦建立了所需的模式和实例化的JdbcMutableAclService,您将须要确保您的域模型支持与Spring Security ACL软件包的互操做性。但愿ObjectIdentityImpl证实是足够的,由于它提供了大量可使用的方式。大多数人将拥有包含公共Serializable getId()方法的域对象。若是返回类型很长,或者与long(例如int)兼容,则您将发现不须要进一步考虑ObjectIdentity问题。 ACL模块的许多部分依赖于长标识符。若是你不使用long(或int,byte等),那么颇有可能须要从新实现一些类。咱们不打算在Spring Security的ACL模块中支持非长标识符,由于长期以来已经与全部数据库序列(最多见的标识符数据类型)兼容,而且具备足够的长度来适应全部常见的使用场景。
如下代码片断显示了如何建立Acl或修改现有的“Acl”:
// Prepare the information we'd like in our access control entry (ACE) ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44)); Sid sid = new PrincipalSid("Samantha"); Permission p = BasePermission.ADMINISTRATION; // Create or update the relevant ACL MutableAcl acl = null; try { acl = (MutableAcl) aclService.readAclById(oi); } catch (NotFoundException nfe) { acl = aclService.createAcl(oi); } // Now grant some permissions via an access control entry (ACE) acl.insertAce(acl.getEntries().length, p, sid, true); aclService.updateAcl(acl);
在上面的示例中,咱们检索与“Foo”域对象相关联的ACL,标识符为44。而后咱们添加一个ACE,以便名为“Samantha”的主体能够“管理”该对象。代码片断相对不言自明,除了insertAce方法。 insertAce方法的第一个参数是肯定Acl中什么位置将插入新条目。在上面的示例中,咱们只是将新的ACE放在现有ACE的末尾。最后一个参数是一个布尔值,表示ACE是授予仍是拒绝。大多数时候它会被授予(true),可是若是它是拒绝(false),权限被有效地阻止。
Spring Security不提供任何特殊的集成来自动建立,更新或删除ACL做为DAO或存储库操做的一部分。相反,您须要为各个域对象编写如上所示的代码。值得考虑的是在服务层上使用AOP来自动将ACL信息与服务层操做集成在一块儿。过去咱们发现这是一个很是有效的方法。
一旦您使用上述技术在数据库中存储一些ACL信息,下一步是实际使用ACL信息做为受权决策逻辑的一部分。你在这里有不少选择。您能够编写本身的AccessDecisionVoter或AfterInvocationProvider,分别在方法调用以前或以后触发。这样的类将使用AclService来检索相关的ACL,而后调用Acl.isGranted(Permission []权限,Sid [] sids,boolean administrativeMode)来决定是否授予或拒绝权限。或者,您可使用咱们的AclEntryVoter,AclEntryAfterInvocationProvider或AclEntryAfterInvocationCollectionFilteringProvider类。全部这些类都提供了基于声明的方法来在运行时评估ACL信息,从而无需编写任何代码。请参考示例应用程序了解如何使用这些类。
2.认证前方案
在某些状况下,您但愿使用Spring Security进行受权,但在访问应用程序以前,用户已经被某些外部系统可靠地进行身份验证。咱们将这些状况称为“预认证”情景。示例包括X.509,Siteminder和由应用程序运行的Java EE容器进行身份验证。当使用预认证时,Spring Security必须
- 识别发出请求的用户。
- 获取用户的权限。
细节将取决于外部认证机制。在X.509的状况下,用户可能会经过其证书信息来标识用户,或者在Siteminder的状况下能够经过HTTP请求标头识别用户。若是依赖容器认证,则经过在传入的HTTP请求上调用getUserPrincipal()方法来识别用户。在某些状况下,外部机制可能为用户提供角色/权限信息,但在其余状况下,必须从单独的来源(如UserDetailsService)获取权限。
2.1。预认证框架类
因为大多数预认证机制遵循相同的模式,Spring Security具备一组类,它们为实现预认证的认证提供程序提供内部框架。这消除了重复,并容许以结构化的方式添加新的实现,而无需从头开始编写全部内容。若是您想使用像X.509认证这样的一些类,那么您就不须要了解这些类,由于它已经具备一个更简单易用的入门命名空间配置选项。若是您须要使用显式bean配置或正在计划编写本身的实现,那么了解提供的实现如何工做将是有用的。你将在org.springframework.security.web.authentication.preauth下找到类。咱们只是在这里提供一个大纲,因此你应该在适当的时候参考Javadoc和source。
2.1.1。 AbstractPreAuthenticatedProcessingFilter
该类将检查安全上下文的当前内容,若是为空,它将尝试从HTTP请求中提取用户信息并将其提交给AuthenticationManager。子类覆盖如下方法来获取此信息:
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request); protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
调用这些以后,过滤器将建立一个PreAuthenticatedAuthenticationToken,其中包含返回的数据并将其提交以进行身份验证。经过这里的“认证”,咱们真的只是意味着进一步处理可能加载用户的权限,但遵循标准的Spring Security身份验证架构。
像其余Spring Security认证过滤器同样,预认证过滤器具备authenticationDetailsSource属性,默认状况下将建立一个WebAuthenticationDetails对象,以将其余信息(如会话标识符和始发IP地址)存储在Authentication对象的详细信息属性中。在能够从预认证机制获取用户角色信息的状况下,数据也存储在此属性中,具体实现GrantedAuthoritiesContainer接口。这使得认证提供商可以读取外部分配给用户的权限。接下来咱们来看一个具体的例子。
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
若是过滤器配置了做为此类的实例的authenticationDetailsSource,则经过为预约义的“可映射角色”集合中的每个调用isUserInRole(String role)方法来获取权限信息。该类从配置的MappableAttributesRetriever获取这些。可能的实现包括对应用程序上下文中的列表进行硬编码,并从web.xml文件中的<security-role>信息读取角色信息。预认证示例应用程序使用后一种方法。
还有一个额外的阶段,角色(或属性)使用配置的Attributes2GrantedAuthoritiesMapper映射到Spring Security GrantedAuthority对象。默认值将只是添加一般的ROLE_前缀到名称,但它可让你彻底控制行为。
2.1.2。 PreAuthenticatedAuthenticationProvider
预先认证的提供商比为用户加载UserDetails对象要作的更多。它经过委托给AuthenticationUserDetailsService来实现。后者相似于标准UserDetailsService,但采用Authentication对象,而不只仅是用户名:
public interface AuthenticationUserDetailsService { UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException; }
该接口可能还有其余用途,可是经过预认证,它容许访问已打包在Authentication对象中的权限,正如咱们在上一节中所看到的那样。 PreAuthenticatedGrantedAuthoritiesUserDetailsService类执行此操做。或者,它能够经过UserDetailsByNameServiceWrapper实现委托给标准UserDetailsService。
2.1.3。 Http403ForbiddenEntryPoint
在技术概述一章中讨论了AuthenticationEntryPoint。一般,它负责启动未认证用户的身份验证过程(当他们尝试访问受保护的资源时),可是在预认证的状况下,这不适用。若是不使用预认证与其余身份验证机制结合使用,则只能将ExceptionTranslationFilter配置为此类的实例。若是用户被AbstractPreAuthenticatedProcessingFilter拒绝,将致使空值认证。若是调用,它老是返回一个403禁止的响应代码。
2.2。具体实施
X.509认证在其本身的章节中介绍。在这里,咱们将看一些对其余预认证场景提供支持的类。
2.2.1。请求头认证(Siteminder)
外部认证系统能够经过在HTTP请求上设置特定的头部来向应用程序提供信息。一个众所周知的例子是Siteminder,它将用户名传递给名为SM_USER的头文件。该机制由RequestHeaderAuthenticationFilter类支持,它只是从头中提取用户名。它默认使用名称SM_USER做为标题名称。有关详细信息,请参阅Javadoc。
请注意,当使用这样的系统时,框架根本不执行身份验证检查,外部系统配置正确并保护对应用程序的全部访问很是重要。 若是攻击者可以在原始请求中伪造头部,而不会被检测到,那么他们可能会选择他们但愿的任何用户名。
Siteminder示例配置
使用此过滤器的典型配置以下所示:
<security:http> <!-- Additional http configuration omitted --> <security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" /> </security:http> <bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter"> <property name="principalRequestHeader" value="SM_USER"/> <property name="authenticationManager" ref="authenticationManager" /> </bean> <bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> <property name="preAuthenticatedUserDetailsService"> <bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" ref="userDetailsService"/> </bean> </property> </bean> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="preauthAuthProvider" /> </security:authentication-manager>
咱们在这里假设安全命名空间正在用于配置。 还假定您已将UserDetailsService(称为“userDetailsService”)添加到配置中以加载用户的角色。
2.2.2。 Java EE容器认证
J2eePreAuthenticatedProcessingFilter类将从HttpServletRequest的userPrincipal属性中提取用户名。 使用此过滤器一般将与J2EEBasedPreAuthenticatedWebAuthenticationDetailsSource中所述的使用Java EE角色相结合。
在代码库中有一个示例应用程序,它使用这种方法,因此从subversion获得代码,若是你有兴趣,看看应用程序上下文文件。 代码在samples / preauth目录中。
3. LDAP认证
3.1。概观
企业一般使用LDAP做为用户信息和认证服务的中央存储库。它还能够用于存储应用程序用户的角色信息。
对于如何配置LDAP服务器有许多不一样的场景,所以Spring Security的LDAP提供程序是彻底可配置的。它使用单独的策略接口进行身份验证和角色检索,并提供可配置为处理各类状况的默认实现。
尝试使用Spring Security以前,您应该熟悉LDAP。如下连接提供了有关概念的良好介绍,以及使用免费LDAP服务器OpenLDAP设置目录的指南:http://www.zytrax.com/books/ldap/。熟悉Java中用于访问LDAP的JNDI API也颇有用。咱们不会在LDAP提供程序中使用任何第三方LDAP库(Mozilla,JLDAP等),但普遍使用Spring LDAP,所以若是您计划添加本身的自定义项,那么熟悉该项目可能会颇有用。
使用LDAP身份验证时,请务必确保正确配置LDAP链接池。若是您不熟悉如何执行此操做,能够参考Java LDAP文档。
3.2。使用LDAP与Spring Security
Spring Security中的LDAP认证大体分为如下几个阶段。
- 从登陆名获取惟一的LDAP“可分辨名称”或DN。这一般意味着在目录中执行搜索,除非提早知道用户名到DN的确切映射。所以,用户登陆时可能输入名称“joe”,但用于对LDAP进行身份验证的实际名称将是完整的DN,例如`uid = joe,ou = users,dc = springsource,dc = com`。
- 经过“绑定”做为该用户,或者经过对DN的目录条目中的password属性进行用户密码的远程“比较”操做来认证用户。
- 加载用户的权限列表。
例外状况是LDAP目录仅用于检索用户信息并在本地进行身份验证。这多是不可能的,由于目录一般被设置为对用户密码等属性的读取访问有限。
咱们将在下面看一些配置方案。有关可用配置选项的完整信息,请参阅安全命名空间架构(XML编辑器中应提供的信息)。
3.3。 配置LDAP服务器
您须要作的第一件事是配置服务器,以便进行认证。 这是使用安全命名空间中的<ldap-server>元素完成的。 可使用url属性将其配置为指向外部LDAP服务器:
<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
3.3.1。使用嵌入式测试服务器
<ldap-server>元素也可用于建立嵌入式服务器,这对于测试和演示很是有用。在这种状况下,您使用它没有url属性:
<ldap-server root="dc=springframework,dc=org"/>
这里咱们指定目录的根DIT应为“dc = springframework,dc = org”,这是默认值。使用这种方式,命名空间解析器将建立一个嵌入式Apache目录服务器,并扫描任何LDIF文件的类路径,它将尝试加载到服务器中。您可使用ldif属性来自定义此行为,该属性定义要加载的LDIF资源:
<ldap-server ldif="classpath:users.ldif" />
这使得使用LDAP更容易启动和运行,由于使用外部服务器可能不方便地工做。它还使用户不须要链接Apache Directory服务器所需的复杂bean配置。使用普通的Spring Bean,配置会更加混乱。您必须具备必需的Apache目录依赖项可用于您的应用程序使用。这些能够从LDAP示例应用程序得到。
3.3.2。使用绑定身份验证
这是最多见的LDAP身份验证方案。
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"/>
这个简单的例子将经过在提供的模式中替换用户登陆名称并尝试用该用户绑定登陆密码来获取用户的DN。若是您的全部用户都存储在目录中的单个节点下,则此操做是正常的。若是您想要配置LDAP搜索过滤器来定位用户,则可使用如下内容:
<ldap-authentication-provider user-search-filter="(uid={0})" user-search-base="ou=people"/>
若是与上述服务器定义一块儿使用,则将使用用户搜索过滤器属性的值做为过滤器执行DN ou = people,dc = springframework,dc = org下的搜索。再次,用户登陆名替换过滤器名称中的参数,所以它将搜索uid属性等于用户名的条目。若是不提供用户搜索库,则搜索将从根执行。
3.3.3。 装载Authorities
如何从LDAP目录中的组加载权限由如下属性控制。
- group-search-base 定义目录树中应执行组搜索的部分。
- group-role-attribute 包含由组条目定义的权限的名称的属性。 默认为`cn`
- group-search-filter 用于搜索组成员资格的过滤器。 默认值为`uniqueMember = {0},对应于`groupOfUniqueNames LDAP类[21]。 在这种状况下,替换参数是用户的完整可分辨名称。 若是要对登陆名进行过滤,可使用参数{1}。
若是咱们使用如下配置
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people" group-search-base="ou=groups" />
做为用户“ben”成功进行身份验证,随后加载的权限将在目录条目`you = groups,dc = springframework,dc = org`下执行搜索,寻找包含值为uid=ben,ou=people,dc=springframework,dc=org。默认状况下,权限名称将具备前缀ROLE_前缀。您可使用role-prefix属性更改它。若是您不想要任何前缀,请使用role-prefix =“none”。有关加载权限的更多信息,请参阅DefaultLdapAuthoritiesPopulator类的Javadoc。
3.4。实施类
咱们上面使用的命名空间配置选项使用简单,比使用Spring beans明确更简洁。有时您可能须要知道如何在应用程序上下文中直接配置Spring Security LDAP。例如,您可能但愿自定义某些类的行为。若是你很高兴使用命名空间配置,那么你能够跳过这个部分和下一个。
主要的LDAP提供程序类LdapAuthenticationProvider实际上并无作太多的工做,可是将工做委托给另外两个bean,一个LdapAuthenticator和一个LdapAuthoritiesPopulator,它们分别负责验证用户和检索用户的一组GrantedAuthority。
3.4.1。 LdapAuthenticator实现
认证者还负责检索任何所需的用户属性。这是由于对属性的权限可能取决于正在使用的身份验证的类型。例如,若是做为用户绑定,可能须要用用户本身的权限读取它们。
目前,Spring Security提供了两种认证策略:
- 直接认证到LDAP服务器(“绑定”身份验证)。
- 密码比较,将用户提供的密码与储存库中存储的密码进行比较。这能够经过检索password属性的值并在本地进行检查或经过执行LDAP“比较”操做来完成,其中提供的密码被传递到服务器进行比较,而且永远不会检索真实的密码值。
共同功能
在能够对用户进行身份验证(经过任一策略)以前,必须从提供给应用程序的登陆名获取可分辨名称(DN)。这能够经过简单的模式匹配(经过设置setUserDnPatterns数组属性)或经过设置userSearch属性来完成。对于DN模式匹配方法,使用标准Java模式格式,登陆名将替代参数{0}。该模式应与配置的SpringSecurityContextSource将绑定的DN相关(有关此服务器的更多信息,请参阅链接到LDAP服务器的部分)。例如,若是您使用具备URL`ldap:// monkeymachine.co.uk / dc = springframework,dc = org`的LDAP服务器,而且具备模式uid = {0},则ou = greatapes,而后登陆“大猩猩”的名称将映射到DN`uid = gorilla,ou = greatapes,dc = springframework,dc = org`。每一个配置的DN模式将依次尝试,直到找到匹配项。有关使用搜索的信息,请参阅下面的搜索对象部分。也可使用两种方法的组合 - 首先检查模式,而且若是找不到匹配的DN,则将使用搜索。
认证者
包org.springframework.security.ldap.authentication中的BindAuthenticator类实现了绑定身份验证策略。它只是尝试做为用户绑定。
PasswordComparisonAuthenticator
PasswordComparisonAuthenticator类实现密码比较认证策略。
3.4.2。链接到LDAP服务器
上面讨论的bean必须可以链接到服务器。它们都必须提供SpringSecurityContextSource,它是Spring LDAP的ContextSource的扩展。除非有特殊要求,不然您一般会配置一个DefaultSpringSecurityContextSource bean,该Bean可使用LDAP服务器的URL进行配置,而且还可使用默认状况下绑定到服务器时使用的“管理员”用户名和密码(而不是匿名绑定)。有关更多信息,请阅读此类的Javadoc和Spring LDAP的AbstractContextSource。
3.4.3。 LDAP搜索对象
一般须要比简单的DN匹配更复杂的策略来定位目录中的用户条目。这能够封装在LdapUserSearch实例中,该实例能够提供给认证者实现,例如容许他们定位用户。提供的实现是FilterBasedLdapUserSearch。
FilterBasedLdapUserSearch中
此bean使用LDAP筛选器来匹配目录中的用户对象。该过程在Javadoc中对相应的搜索方法进行了说明
thehttp://java.sun.com/j2se/1.4.2/docs/api/javax/naming/directory/DirContext.html#search(javax.naming.Name,%20java.lang.String,%20java.lang.Object[],%20javax.naming.directory.SearchControls)[JDK DirContext class]. As explained there, the search filter can be supplied with parameters. For this class, the only valid parameter is {0}
which will be replaced with the user’s login name.
3.4.4。LdapAuthoritiesPopulator在
成功验证用户后,LdapAuthenticationProvider将尝试经过调用配置的LdapAuthoritiesPopulator bean来为用户加载一组权限。 DefaultLdapAuthoritiesPopulator是一个实现,它将经过在目录中搜索用户所属的组(一般这些将是目录中的groupOfNames或groupOfUniqueNames条目)来加载权限。 有关此类的Javadoc详细信息,请参考Javadoc。
若是要仅使用LDAP进行身份验证,而是从差别源(如数据库)加载权限,那么您能够提供本身的该接口的实现并注入该接口。
3.4.5。 Spring Bean配置
使用咱们在这里讨论的一些bean的典型配置可能以下所示:
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> <constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/> <property name="userDn" value="cn=manager,dc=springframework,dc=org"/> <property name="password" value="password"/> </bean> <bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> <constructor-arg> <bean class="org.springframework.security.ldap.authentication.BindAuthenticator"> <constructor-arg ref="contextSource"/> <property name="userDnPatterns"> <list><value>uid={0},ou=people</value></list> </property> </bean> </constructor-arg> <constructor-arg> <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="contextSource"/> <constructor-arg value="ou=groups"/> <property name="groupRoleAttribute" value="ou"/> </bean> </constructor-arg> </bean>
这将设置提供者使用URL ldap:// monkeymachine:389 / dc = springframework,dc = org访问LDAP服务器。 将经过尝试与DN`uid = <user-login-name>,ou = people,dc = springframework,dc = org`进行绑定来执行身份验证。 成功认证后,经过使用默认过滤器(member = <user's-DN>)在DN ou = groups,dc = springframework,dc = org下搜索角色将分配给用户。 角色名称将取自每场比赛的“ou”属性。
要配置使用过滤器(uid = <user-login-name>)而不是DN模式(或除此以外)的用户搜索对象,您将配置如下bean
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg index="0" value=""/> <constructor-arg index="1" value="(uid={0})"/> <constructor-arg index="2" ref="contextSource" /> </bean>
并经过设置BindAuthenticator bean的userSearch属性来使用它。 而后,在尝试绑定该用户以前,认证器将调用搜索对象以获取正确的用户DN。
3.4.6。 LDAP属性和自定义用户详细信息
使用LdapAuthenticationProvider的身份验证的最终结果与使用标准UserDetailsService接口的普通Spring Security身份验证相同。 UserDetails对象被建立并存储在返回的Authentication对象中。 与使用UserDetailsService同样,一般的要求是可以自定义此实现并添加额外的属性。 当使用LDAP时,这些一般是来自用户条目的属性。 UserDetails对象的建立由提供商的UserDetailsContextMapper策略控制,该策略负责将用户对象映射到LDAP上下文数据:
public interface UserDetailsContextMapper { UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<GrantedAuthority> authorities); void mapUserToContext(UserDetails user, DirContextAdapter ctx); }
只有第一种方法与认证有关。若是您提供此接口的实现并将其注入到LdapAuthenticationProvider中,则能够精确控制如何建立UserDetails对象。第一个参数是Spring LDAP的DirContextOperations的一个实例,它容许您访问在身份验证期间加载的LDAP属性。 username参数是用于验证的名称,最终参数是由configure.LdapAuthoritiesPopulator为用户加载的权限集合。
加载上下文数据的方式会根据您使用的身份验证类型略有不一样。使用BindAuthenticator,从绑定操做返回的上下文将用于读取属性,不然将使用从配置的ContextSource获取的标准上下文读取数据(当搜索配置为定位用户时,这将是数据由搜索对象返回)。
3.5。 Active Directory身份验证
Active Directory支持其本身的非标准身份验证选项,而且正常使用模式与标准的LdapAuthenticationProvider不太干净。一般,使用域用户名(用户@域名)执行身份验证,而不是使用LDAP可分辨名称。为了使这更容易,Spring Security 3.1有一个身份验证提供程序,为一个典型的Active Directory设置定制。
3.5.1。 ActiveDirectoryLdapAuthenticationProvider
配置ActiveDirectoryLdapAuthenticationProvider是很是简单的。您只须要提供域名和提供服务器地址的LDAP URL [22]。而后,示例配置将以下所示:
<bean id="adAuthenticationProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider"> <constructor-arg value="mydomain.com" /> <constructor-arg value="ldap://adserver.mydomain.com/" /> </bean>
请注意,为了定义服务器位置,不须要指定单独的ContextSource - 该bean是彻底独立的。例如,名为“Sharon”的用户将可以经过输入用户名sharon或完整的Active Directory userPrincipalName(即sharon@mydomain.com)进行身份验证。而后将定位用户的目录条目,而且返回的属性可用于自定义建立的UserDetails对象(如上所述,能够为此目的注入UserDetailsContextMapper)。与目录的全部交互都与用户自己的身份进行。没有一个“经理”用户的概念。
默认状况下,用户权限是从用户条目的memberOf属性值得到的。分配给用户的权限能够再次使用UserDetailsContextMapper进行定制。您还能够将GrantedAuthoritiesMapper注入到提供者实例中,以控制身份验证对象中的权限。
Active Directory错误代码
默认状况下,失败的结果将致使标准的Spring Security BadCredentialsException。若是将属性convertSubErrorCodesToExceptions设置为true,则将解析异常消息以尝试提取Active Directory特定的错误代码并提出更具体的异常。查看类Javadoc了解更多信息。
4. JSP标签库
Spring Security拥有本身的taglib,它为访问安全信息和在JSP中应用安全约束提供了基本的支持。
4.1。声明Taglib
要使用任何标签,您必须在JSP中声明安全性标签lib:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
4.2。 受权标签
该标签用于肯定其内容是否应该被评估。 在Spring Security 3.0中,它能够以两种方式使用[23]。 第一种方法使用在标签的访问属性中指定的Web安全性表达式。 表达式评估将被委派给应用程序上下文中定义的SecurityExpressionHandler <FilterInvocation>(您应该在<http>命名空间配置中启用Web表达式以确保此服务可用)。 例如
<sec:authorize access="hasRole('supervisor')"> This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s. </sec:authorize>
一个常见的要求是只显示一个特定的连接,若是用户实际上被容许点击它。 咱们如何才能事先决定是否容许某些事情? 该标签也能够以替代模式进行操做,能够将特定的URL定义为属性。 若是用户被容许调用该URL,那么标签主体将被评估,不然将被跳过。 因此你可能会有这样的东西
<sec:authorize url="/admin"> This content will only be visible to users who are authorized to send requests to the "/admin" URL. </sec:authorize>
要使用此标签,还必须在应用程序上下文中具备WebInvocationPrivilegeEvaluator的实例。若是您使用命名空间,则会自动注册。这是DefaultWebInvocationPrivilegeEvaluator的一个实例,它为提供的URL建立一个虚拟Web请求,并调用安全拦截器来查看请求是成功仍是失败。这容许您委托您在<http>命名空间配置中使用intercept-url声明定义的访问控制设置,并保存必须在JSP中复制信息(例如必需的角色)。此方法也能够与方法属性组合,提供HTTP方法,以进行更具体的匹配。
经过将var属性设置为变量名称,能够将页面上下文范围变量存储在页面上下文范围变量中,从而避免复制和从新评估条件页。
4.2.1。禁用标签受权进行测试
在未经受权的用户隐藏页面中的连接不会阻止他们访问该URL。例如,他们能够直接将其输入到浏览器中。做为测试过程的一部分,您可能但愿透露隐藏的区域,以便检查连接是否真的在后端被保护。若是将系统属性spring.security.disableUISecurity设置为true,则受权标签仍将运行,但不会隐藏其内容。默认状况下,它还将包含<span class =“securityHiddenUI”> ... </ span>标签的内容。这容许您显示具备特定CSS样式(例如不一样背景颜色)的“隐藏”内容。例如,尝试运行启用此属性的“tutorial”示例应用程序。
您还能够设置属性spring.security.securedUIPrefix和spring.security.securedUISuffix,若是要从默认的span标签更改周围的文本(或使用空字符串将其彻底删除)。
4.3。authentication标签
此标签容许访问存储在安全上下文中的当前Authentication对象。它直接在JSP中呈现对象的属性。因此,例如,若是Authentication的principal属性是Spring Security的UserDetails对象的一个实例,那么使用<sec:authentication property =“principal.username”/>将渲染当前用户的名称。
固然,没有必要对这种事情使用JSP标签,有些人喜欢在视图中保持尽量少的逻辑。您能够访问MVC控制器中的Authentication对象(经过调用SecurityContextHolder.getContext()。getAuthentication()),并将数据直接添加到模型中,以便经过视图呈现。
4.4。accesscontrollist标签
此标记仅在与Spring Security的ACL模块一块儿使用时有效。它检查指定域对象所需权限的逗号分隔列表。若是当前用户具备任何这些权限,那么标签主体将被评估。若是没有,将被跳过。一个例子多是
<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}"> This will be shown if the user has either of the permissions represented by the values "1" or "2" on the given object. </sec:accesscontrollist>
权限被传递给应用程序上下文中定义的PermissionFactory,将它们转换为ACL权限实例,所以它们能够是工厂支持的任何格式 - 它们不必定是整数,它们能够是像READ或WRITE 。若是未找到PermissionFactory,将使用DefaultPermissionFactory的实例。来自应用程序上下文的AclService将用于加载提供的对象的Acl实例。 Acl将被调用所需的权限,以检查是否授予其中任何一个。
该标签也支持var属性,与authorize标签相同。
4.5。 csrfInput标签
若是启用了CSRF保护,则此标记将为CSRF保护令牌插入具备正确名称和值的隐藏表单域。若是未启用CSRF保护,则此标签不会输出任何内容。
一般,Spring Security会为您使用的任何<form:form>标签自动插入一个CSRF表单域,可是若是因为某些缘由您不能使用<form:form>,则csrfInput是一个方便的替换。
您应该将此标签放在HTML <form> </ form>块中,一般会放置其余输入字段。不要把这个标签放在一个Spring <form:form> </ form:form> block中 - Spring Security会自动处理Spring表单。
<form method="post" action="/do/something"> <sec:csrfInput /> Name:<br /> <input type="text" name="name" /> ... </form>
4.6。 csrfMetaTags标签
若是启用了CSRF保护,则此标记插入包含CSRF保护令牌表单字段和头名称以及CSRF保护令牌值的元标记。 这些元标记在您的应用程序中在JavaScript中使用CSRF保护是有用的。
您应该将csrfMetaTags放在HTML <head> </ head>块中,一般会放置其余元标记。 使用此标签后,您可使用JavaScript轻松访问表单字段名称,标题名称和令牌值。 在这个例子中使用JQuery来使任务更容易。
<!DOCTYPE html> <html> <head> <title>CSRF Protected JavaScript Page</title> <meta name="description" content="This is the description for this page" /> <sec:csrfMetaTags /> <script type="text/javascript" language="javascript"> var csrfParameter = $("meta[name='_csrf_parameter']").attr("content"); var csrfHeader = $("meta[name='_csrf_header']").attr("content"); var csrfToken = $("meta[name='_csrf']").attr("content"); // using XMLHttpRequest directly to send an x-www-form-urlencoded request var ajax = new XMLHttpRequest(); ajax.open("POST", "http://www.example.org/do/something", true); ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data"); ajax.send(csrfParameter + "=" + csrfToken + "&name=John&..."); // using XMLHttpRequest directly to send a non-x-www-form-urlencoded request var ajax = new XMLHttpRequest(); ajax.open("POST", "http://www.example.org/do/something", true); ajax.setRequestHeader(csrfHeader, csrfToken); ajax.send("..."); // using JQuery to send an x-www-form-urlencoded request var data = {}; data[csrfParameter] = csrfToken; data["name"] = "John"; ... $.ajax({ url: "http://www.example.org/do/something", type: "POST", data: data, ... }); // using JQuery to send a non-x-www-form-urlencoded request var headers = {}; headers[csrfHeader] = csrfToken; $.ajax({ url: "http://www.example.org/do/something", type: "POST", headers: headers, ... }); <script> </head> <body> ... </body> </html>
若是没有启用CSRF保护,csrfMetaTags不会输出任何内容。
5. Java认证和受权服务(JAAS)提供商
5.1。概观
Spring Security提供了一个可以将认证请求委托给Java认证和受权服务(JAAS)的软件包。此包将在下面详细讨论。
5.2。 AbstractJaasAuthenticationProvider
AbstractJaasAuthenticationProvider是提供的JAAS AuthenticationProvider实现的基础。子类必须实现一个建立LoginContext的方法。 AbstractJaasAuthenticationProvider具备能够注入到其中的若干依赖关系,这些依赖关系将在下面讨论。
5.2.1。 JAAS CallbackHandler
大多数JAAS LoginModule须要某种回调。这些回调一般用于从用户处获取用户名和密码。
在Spring Security部署中,Spring Security负责此用户交互(经过身份验证机制)。所以,当认证请求被委派给JAAS时,Spring Security的认证机制将已经彻底填充了包含JAAS LoginModule所需的全部信息的Authentication对象。
所以,Spring Security的JAAS包提供了两个默认的回调处理程序JaasNameCallbackHandler和JaasPasswordCallbackHandler。这些回调处理程序中的每个都实现JaasAuthenticationCallbackHandler。在大多数状况下,这些回调处理程序能够在不了解内部机制的状况下简单地使用。
对于那些须要彻底控制回调行为的人,内部AbstractJaasAuthenticationProvider将这些JaasAuthenticationCallbackHandler与一个InternalCallbackHandler进行包装。 InternalCallbackHandler是实际实现JAAS正常CallbackHandler接口的类。任什么时候候使用JAAS LoginModule,它都会传递一个应用程序上下文配置的内部列表。若是LoginModule针对InternalCallbackHandler请求回调,则回调将被传递给正在包装的JaasAuthenticationCallbackHandler。
5.2.2。 JAAS AuthorityGranter
JAAS与principals合做。即便“角色”也被表示为JAAS中的主体。另外一方面,Spring Security与Authentication对象一块儿使用。每一个验证对象包含一个主体和多个GrantedAuthority。为了方便这些不一样概念之间的映射,Spring Security的JAAS包包括一个AuthorityGranter接口。
一个AuthorityGranter负责检查JAAS主体并返回一组String,表明分配给委托人的权限。对于每一个返回的权限字符串,AbstractJaasAuthenticationProvider建立一个JaasGrantedAuthority(它实现Spring Security的GrantedAuthority接口),该接口包含权限字符串和AuthorityGranter传递的JAAS主体。 AbstractJaasAuthenticationProvider经过首先使用JAAS LoginModule成功验证用户的凭据,而后访问返回的LoginContext,来获取JAAS主体。调用LoginContext.getSubject()。getPrincipals(),每一个生成的主体都传递给根据AbstractJaasAuthenticationProvider.setAuthorityGranters(List)属性定义的每一个AuthorityGranter。
给予每一个JAAS主体具备实现特定含义的Spring Security不包括任何生产AuthorityGranter。可是,在单元测试中有一个TestAuthorityGranter,它演示了一个简单的AuthorityGranter实现。
5.3。 DefaultJaasAuthenticationProvider
DefaultJaasAuthenticationProvider容许将JAAS配置对象注入它做为依赖。而后使用注入的JAAS配置建立一个LoginContext。这意味着DefaultJaasAuthenticationProvider不会像JaasAuthenticationProvider那样绑定任何特定的配置实现。
5.3.1。 InMemoryConfiguration
为了方便将配置注入DefaultJaasAuthenticationProvider,提供了名为InMemoryConfiguration的内存实现中的默认值。实现构造函数接受一个Map,其中每一个键表示一个登陆配置名称,该值表示一个AppConfigurationEntry的数组。 InMemoryConfiguration还支持一个AppConfigurationEntry对象的默认数组,若是在所提供的Map中找不到映射,则将使用它们。有关详细信息,请参阅InMemoryConfiguration的类级别javadoc。
5.3.2。 DefaultJaasAuthenticationProvider示例配置
虽然InMemoryConfiguration的Spring配置能够比标准的JAAS配置文件更冗长,可是与DefaultJaasAuthenticationProvider结合使用它比JaasAuthenticationProvider更灵活,由于它不依赖于默认的配置实现。
下面提供了使用InMemoryConfiguration的DefaultJaasAuthenticationProvider的示例配置。 请注意,配置的自定义实现也能够轻松地注入到DefaultJaasAuthenticationProvider中。
<bean id="jaasAuthProvider" class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider"> <property name="configuration"> <bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration"> <constructor-arg> <map> <!-- SPRINGSECURITY is the default loginContextName for AbstractJaasAuthenticationProvider --> <entry key="SPRINGSECURITY"> <array> <bean class="javax.security.auth.login.AppConfigurationEntry"> <constructor-arg value="sample.SampleLoginModule" /> <constructor-arg> <util:constant static-field= "javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED"/> </constructor-arg> <constructor-arg> <map></map> </constructor-arg> </bean> </array> </entry> </map> </constructor-arg> </bean> </property> <property name="authorityGranters"> <list> <!-- You will need to write your own implementation of AuthorityGranter --> <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/> </list> </property> </bean>
5.4。JaasAuthenticationProvider
JaasAuthenticationProvider假定默认配置是ConfigFile的一个实例。 这是为了尝试更新配置而进行的。 JaasAuthenticationProvider而后使用默认配置来建立LoginContext。
假设咱们有一个JAAS登陆配置文件/WEB-INF/login.conf,其中包含如下内容:
JAASTest { sample.SampleLoginModule required; };
像全部Spring Security bean同样,JaasAuthenticationProvider经过应用程序上下文进行配置。 如下定义将对应于上述JAAS登陆配置文件:
<bean id="jaasAuthenticationProvider" class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider"> <property name="loginConfig" value="/WEB-INF/login.conf"/> <property name="loginContextName" value="JAASTest"/> <property name="callbackHandlers"> <list> <bean class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/> <bean class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/> </list> </property> <property name="authorityGranters"> <list> <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/> </list> </property> </bean>
5.5。做为主题运行
若是配置,JaasApiIntegrationFilter将尝试做为JaasAuthenticationToken上的Subject运行。这意味着可使用如下方式访问主题:
Subject subject = Subject.getSubject(AccessController.getContext());
可使用jaas-api-provision属性轻松配置此集成。当与依赖于JAAS主题的旧式或外部API进行集成时,此功能很是有用。
6. CAS认证
6.1。概观
JA-SIG生产一种名为CAS的企业级单点登陆系统。与其余举措不一样,JA-SIG的中央认证服务是开源,普遍使用,易于理解,独立于平台,并支持代理功能。 Spring Security彻底支持CAS,并提供从Spring Security的单应用程序部署到企业级CAS服务器保护的多应用程序部署的简单迁移路径。
您能够在http://www.ja-sig.org/cas上了解更多关于CAS的信息。您还须要访问此站点才能下载CAS Server文件。
6.2。 CAS如何工做
虽然CAS网站包含详细介绍CAS体系结构的文档,但咱们在Spring Security的上下文中再次介绍通常性概述。 Spring Security 3.x支持CAS 3.在编写本文时,CAS服务器为3.4版本。
在企业的某个地方,您将须要设置一个CAS服务器。 CAS服务器只是一个标准的WAR文件,因此设置服务器并不困难。在WAR文件中,您将自定义用户显示的登陆名和其余单一登陆页面。
部署CAS 3.4服务器时,还须要在CAS中包含的deployerConfigContext.xml中指定一个AuthenticationHandler。 AuthenticationHandler有一个简单的方法,它返回一个关于一组给定的凭据是否有效的布尔值。您的AuthenticationHandler实现将须要连接到某种类型的后端认证存储库,例如LDAP服务器或数据库。 CAS自己包括许多AuthenticationHandler的开箱即用的协助。当您下载并部署服务器战争文件时,设置为成功验证输入与用户名匹配的密码的用户,这对于测试很是有用。
除了CAS服务器自己,其余关键的用户固然是部署在整个企业中的安全Web应用程序。这些Web应用程序被称为“服务”。有三种类型的服务。那些认证服务票,那些能够得到代理票的人,以及认证代理票的那些。验证代理机票是不一样的,由于代理列表必须通过验证,一般能够重用代理机票。
6.2.1。 Spring Security和CAS交互序列
Web浏览器,CAS服务器和Spring Security安全服务之间的基本交互以下:
- 网络用户正在浏览服务的公共页面。 CAS或Spring Security不涉及。
- 用户最终请求一个安全的页面,或者它使用的一个bean是安全的。 Spring Security的ExceptionTranslationFilter将检测到AccessDeniedException或AuthenticationException。
- 由于用户的Authentication对象(或缺乏)致使了AuthenticationException,因此ExceptionTranslationFilter会调用配置的AuthenticationEntryPoint。若是使用CAS,这将是CasAuthenticationEntryPoint类。
- CasAuthenticationEntryPoint将用户的浏览器重定向到CAS服务器。它还将指示一个服务参数,它是Spring Security服务(您的应用程序)的回调URL。例如,浏览器重定向到的URL多是https://my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Fj_spring_cas_security_check。
- 用户浏览器重定向到CAS后,系统将提示输入用户名和密码。若是用户显示一个表示它们之前登陆的会话cookie,则不会再提示他们再次登陆(这个过程有一个例外,稍后将介绍)。 CAS将使用上面讨论的PasswordHandler(或AuthenticationHandler,若是使用CAS 3.0)来决定用户名和密码是否有效。
- 成功登陆后,CAS将把用户的浏览器重定向到原始服务。它还将包括一个票据参数,它是一个不透明的字符串,表明“服务票证”。继续咱们早期的示例,浏览器重定向到的URL多是https://server3.company.com/webapp/j_spring_cas_security_check?ticket=ST-0-ER94xMJmn6pha35CQRoZ。
- 回到服务Web应用程序,CasAuthenticationFilter老是监听/ j_spring_cas_security_check的请求(这是可配置的,但咱们将使用本介绍中的默认值)。处理过滤器将构造一个表示服务票证的UsernamePasswordAuthenticationToken。主体将等于CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER,而凭证将是服务票据不透明值。而后,该认证请求将交给配置的AuthenticationManager。
- AuthenticationManager实现将是ProviderManager,它依次配置了CasAuthenticationProvider。 CasAuthenticationProvider仅响应包含CAS特定主体(如CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER)和CasAuthenticationToken(稍后讨论)的UsernamePasswordAuthenticationToken。
- CasAuthenticationProvider将使用TicketValidator实现验证服务票证。这一般是一个Cas20ServiceTicketValidator,它是CAS客户端库中包含的一个类。若是应用程序须要验证代理机票,则使用Cas20ProxyTicketValidator。 TicketValidator向CAS服务器发出HTTPS请求,以验证服务票证。它还可能包括代理回调URL,该URL包含在此示例中:https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Fj_spring_cas_security_check&ticket=ST- 0-ER94xMJmn6pha35CQRoZ&pgtUrl = HTTPS://server3.company.com/webapp/j_spring_cas_security_proxyreceptor。
- 返回CAS服务器,验证请求将被接收。若是所提供的服务票证与该票据发出的服务URL匹配,则CAS将提供XML中的确定回复,表示用户名。若是任何代理涉及身份验证(以下所述),代理列表也包含在XML响应中。
- [可选]若是对CAS验证服务的请求包含代理回调URL(在pgtUrl参数中),CAS将在XML响应中包含一个pgtIou字符串。这个pgtIou表明一个代理受权票IOU。而后CAS服务器将建立本身的HTTPS链接回到pgtUrl。这是为了相互认证CAS服务器和声明的服务URL。 HTTPS链接将用于向原始Web应用程序发送代理受权单。例如,https://server3.company.com/webapp/j_spring_cas_security_proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH。
- Cas20TicketValidator将解析从CAS服务器收到的XML。它将返回到CasAuthenticationProvider一个TicketResponse,其中包括用户名(强制性),代理列表(若是涉及到)和代理受权票据IOU(若是请求代理回调)。
下一个CasAuthenticationProvider将调用一个配置的CasProxyDecider。 CasProxyDecider指示是否在票证中的代理列表
6.3。 CAS客户端的配置
因为Spring Security,CAS的Web应用程序很容易。假设你已经知道使用Spring Security的基础知识,因此这些再也不在下面被覆盖。咱们假设正在使用基于命名空间的配置,并根据须要添加到CAS bean中。每一个部分创建在上一节。一个fullCAS示例应用程序能够在Spring Security Samples中找到。
6.3.1。服务票证实
本节介绍如何设置Spring Security以验证服务门票。一般这是一个Web应用程序须要的。您将须要在应用程序上下文中添加一个ServiceProperties bean。这表明您的CAS服务:
<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> <property name="service" value="https://localhost:8443/cas-sample/j_spring_cas_security_check"/> <property name="sendRenew" value="false"/> </bean>
该服务必须等于由CasAuthenticationFilter监视的URL。 sendRenew默认为false,但若是应用程序特别敏感,则应将其设置为true。这个参数是告诉CAS登陆服务登陆的一个登陆名是不可接受的。相反,用户须要从新输入用户名和密码才能访问该服务。
如下bean应配置为启动CAS身份验证过程(假设您正在使用命名空间配置):
<security:http entry-point-ref="casEntryPoint"> ... <security:custom-filter position="CAS_FILTER" ref="casFilter" /> </security:http> <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> </bean> <bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <property name="loginUrl" value="https://localhost:9443/cas/login"/> <property name="serviceProperties" ref="serviceProperties"/> </bean>
要使CAS运行,ExceptionTranslationFilter必须将其authenticationEntryPoint属性设置为CasAuthenticationEntryPoint bean。这可使用入门点参考来作到这一点。 CasAuthenticationEntryPoint必须引用ServiceProperties bean(上面讨论过),它提供了企业的CAS登陆服务器的URL。这是用户浏览器将被重定向的位置。
CasAuthenticationFilter与UsernamePasswordAuthenticationFilter具备很是类似的属性(用于基于表单的登陆)。您可使用这些属性来定制身份验证成功和失败的行为。
接下来,您须要添加一个CasAuthenticationProvider及其协做者:
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="casAuthenticationProvider" /> </security:authentication-manager> <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <property name="authenticationUserDetailsService"> <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <constructor-arg ref="userService" /> </bean> </property> <property name="serviceProperties" ref="serviceProperties" /> <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <constructor-arg index="0" value="https://localhost:9443/cas" /> </bean> </property> <property name="key" value="an_id_for_this_auth_provider_only"/> </bean> <security:user-service id="userService"> <security:user name="joe" password="joe" authorities="ROLE_USER" /> ... </security:user-service>
一旦CASAuthenticationProvider经过CAS认证,使用UserDetailsService实例来加载用户的权限。咱们在这里展现了一个简单的内存设置。请注意,CasAuthenticationProvider实际上并无使用密码进行身份验证,但它确实使用了权限。
若是您回到“CAS工做部分”部分,那么这些bean都是合理的。
这完成了CAS的最基本的配置。若是您没有发生任何错误,您的Web应用程序应该在CAS单点登陆的框架内快乐工做。 Spring Security的其余任何部分都不须要关心CAS处理认证的事实。在如下部分中,咱们将讨论一些(可选)更高级的配置。
6.3.2。 单一注销
CAS协议支持单一注销,能够轻松添加到您的Spring Security配置中。 如下是处理单一注销的Spring Security配置的更新
<security:http entry-point-ref="casEntryPoint"> ... <security:logout logout-success-url="/cas-logout.jsp"/> <security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/> <security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/> </security:http> <!-- This filter handles a Single Logout Request from the CAS Server --> <bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/> <!-- This filter redirects to the CAS Server to signal Single Logout should be performed --> <bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> <constructor-arg value="https://localhost:9443/cas/logout"/> <constructor-arg> <bean class= "org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/> </constructor-arg> <property name="filterProcessesUrl" value="/j_spring_cas_security_logout"/> </bean>
注销元素将用户记录在本地应用程序以外,但不会终止与CAS服务器或已登陆的任何其余应用程序的会话。 requestSingleLogoutFilter过滤器将容许/ spring_security_cas_logout的url重定向到配置的CAS Server注销URL。那么CAS服务器将向登陆的全部服务发送单一注销请求。 singleLogoutFilter经过在静态Map中查找HttpSession而后使其无效来处理Single Logout请求。
这可能会混淆为何须要注销元素和singleLogoutFilter。因为SingleSignOutFilter将HttpSession存储在静态Map中以便调用invalidate,因此被认为是最佳实践。经过上述配置,注销流程为:
- 用户请求/ j_spring_security_logout,将用户登陆本地应用程序,并将用户发送到注销成功页面。
- 注销成功页面/cas-logout.jsp应指示用户单击指向/ j_spring_cas_security_logout的连接,以便退出全部应用程序。
- 当用户单击连接时,用户被重定向到CAS单个注销URL(https:// localhost:9443 / cas / logout)。
- 在CAS服务器端,CAS单一注销URL而后将一个注销请求提交给全部的CAS服务。在CAS服务端,JASIG的SingleSignOutFilter经过使原始会话无效来处理注销请求。
下一步是将如下内容添加到您的web.xml中
<filter> <filter-name>characterEncodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class> org.jasig.cas.client.session.SingleSignOutHttpSessionListener </listener-class> </listener>
当使用SingleSignOutFilter时,您可能会遇到一些编码问题。所以,建议在使用SingleSignOutFilter时添加CharacterEncodingFilter以确保字符编码正确。再次参考JASIG的文档资料。 SingleSignOutHttpSessionListener确保当HttpSession到期时,用于单次注销的映射被删除。
6.3.3。使用CAS验证无状态服务
本节介绍如何使用CAS对服务进行身份验证。换句话说,本节讨论如何设置使用与CAS进行身份验证的服务的客户端。下一节介绍如何使用CAS进行身份验证设置无状态服务。
配置CAS以获取代理受权单
为了对无状态服务进行身份验证,应用程序须要得到代理受权票证(PGT)。本节介绍如何配置Spring Security,以便在thencas-st [服务票证验证]配置中获取PGT构建。
第一步是在您的Spring Security配置中包含一个ProxyGrantingTicketStorage。这用于存储由CasAuthenticationFilter获取的PGT,以便它们可用于获取代理票证。示例配置以下所示
<!-- NOTE: In a real application you should not use an in memory implementation. You will also want to ensure to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup() --> <bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
下一步是更新CasAuthenticationProvider以得到代理机票。为此,请用Cas20ProxyTicketValidator替换Cas20ServiceTicketValidator。 proxyCallbackUrl应设置为应用程序将接收PGT的URL。最后,配置还应该引用ProxyGrantingTicketStorage,所以可使用PGT来获取代理机票。您能够在下面找到配置更改的示例。
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> ... <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"> <constructor-arg value="https://localhost:9443/cas"/> <property name="proxyCallbackUrl" value="https://localhost:8443/cas-sample/j_spring_cas_security_proxyreceptor"/> <property name="proxyGrantingTicketStorage" ref="pgtStorage"/> </bean> </property> </bean>
最后一步是更新CasAuthenticationFilter以接受PGT并将其存储在ProxyGrantingTicketStorage中。 proxyReceptorUrl与Cas20ProxyTicketValidator的proxyCallbackUrl匹配很重要。示例配置以下所示。
<bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> ... <property name="proxyGrantingTicketStorage" ref="pgtStorage"/> <property name="proxyReceptorUrl" value="/j_spring_cas_security_proxyreceptor"/> </bean>
使用代理机构调用无状态服务
如今Spring Security得到PGT,您可使用它们来建立可用于向无状态服务进行身份验证的代理凭证。 CAS示例应用程序在ProxyTicketSampleServlet中包含一个工做示例。示例代码以下:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // NOTE: The CasAuthenticationToken can also be obtained using // SecurityContextHolder.getContext().getAuthentication() final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal(); // proxyTicket could be reused to make calls to the CAS service even if the // target url differs final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl); // Make a remote call using the proxy ticket final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8"); String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8"); ... }
6.3.4。代理机票认证
CasAuthenticationProvider区分有状态和无状态的客户端。有状态的客户端被认为是提交给CasAuthenticationFilter的filterProcessUrl的任何客户端。无状态客户端是在FilterProcessUrl之外的URL上向CasAuthenticationFilter提供认证请求的任何客户端。
由于远程处理协议没有办法在HttpSession的上下文中呈现本身,因此不可能依赖于在请求之间的会话中存储安全上下文的默认作法。此外,因为CAS服务器在TicketValidator验证以后使票据无效,所以在后续请求中呈现相同的代理票证将不起做用。
一个明显的选择是不使用CAS来远程处理协议客户端。然而,这将消除CAS的许多理想特征。做为中间层,CasAuthenticationProvider使用StatelessTicketCache。这仅用于使用等于CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER的主体的无状态客户端。 CasAuthenticationProvider会将生成的CasAuthenticationToken存储在StatelessTicketCache中,并键入代理机票。所以,远程协议客户端能够呈现相同的代理凭证,而且CasAuthenticationProvider将不须要与CAS服务器联系以进行验证(除了第一个请求)。一旦通过身份验证,代理机票能够用于除原始目标服务以外的其余网址。
本部分创建在前面的部分以适应代理票证验证。第一步是指定验证全部工件,以下所示。
<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> ... <property name="authenticateAllArtifacts" value="true"/> </bean>
下一步是为CasAuthenticationFilter指定serviceProperties和authenticationDetailsSource。 serviceProperties属性指示CasAuthenticationFilter尝试对全部工件进行身份验证,而不是仅对存在于filterProcessUrl上的工件进行身份验证。 ServiceAuthenticationDetailsSource建立一个ServiceAuthenticationDetails,确保当验证门票时,基于HttpServletRequest的当前URL被用做服务URL。生成服务URL的方法能够经过注册自定义的AuthenticationDetailsSource来自定义,该代码返回一个自定义的ServiceAuthenticationDetails。
<bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> ... <property name="serviceProperties" ref="serviceProperties"/> <property name="authenticationDetailsSource"> <bean class= "org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"> <constructor-arg ref="serviceProperties"/> </bean> </property> </bean>
您还须要更新CasAuthenticationProvider来处理代理机票。 为此,请用Cas20ProxyTicketValidator替换Cas20ServiceTicketValidator。 您将须要配置statelessTicketCache以及您要接受的代理。 您能够在下面找到接受全部代理所需更新的示例。
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> ... <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"> <constructor-arg value="https://localhost:9443/cas"/> <property name="acceptAnyProxy" value="true"/> </bean> </property> <property name="statelessTicketCache"> <bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache"> <property name="cache"> <bean class="net.sf.ehcache.Cache" init-method="initialise" destroy-method="dispose"> <constructor-arg value="casTickets"/> <constructor-arg value="50"/> <constructor-arg value="true"/> <constructor-arg value="false"/> <constructor-arg value="3600"/> <constructor-arg value="900"/> </bean> </property> </bean> </property> </bean>
7. X.509认证
7.1。概观
X.509证书身份验证最多见的用途是在使用SSL时验证服务器的身份,最多见的是在浏览器中使用HTTPS。浏览器将自动检查由服务器呈现的证书是否已被其维护的可靠证书颁发机构的列表之一发出(即数字签名)。
您也可使用SSL进行“相互认证”;服务器而后将从SSL客户端请求有效的证书做为SSL握手的一部分。服务器将经过检查其证书由可接受的权限签名来验证客户端。若是提供了有效的证书,则能够经过应用程序中的servlet API获取。 Spring Security X.509模块使用过滤器提取证书。它将证书映射到应用程序用户,并加载该用户的一组已受权的权限,以便与标准的Spring Security基础结构一块儿使用。
在尝试使用Spring Security以前,您应该熟悉使用证书并为您的servlet容器设置客户端身份验证。大部分工做是在建立和安装合适的证书和密钥。例如,若是您使用Tomcat,请阅读如下指示http://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html。在使用Spring Security进行尝试以前,请务必从新开始工做
7.2。将X.509认证添加到Web应用程序
启用X.509客户端身份验证很是简单。只需将<x509 />元素添加到您的http安全命名空间配置。
<http> ... <x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>; </http>
该元素有两个可选属性:
- 主题主要正则表达式。用于从证书主题名称中提取用户名的正则表达式。默认值如上所示。这是将被传递给UserDetailsService的用户名,以加载用户的权限。
- 用户服务-REF。这是与X.509一块儿使用的UserDetailsService的bean ID。若是您的应用程序上下文中只定义了一个,则不须要它。
主题 - 正则表达式应包含单个组。例如,默认表达式“CN =(。*?)”匹配通用名称字段。所以,若是证书中的主题名称为“CN = Jimi Hendrix,OU = ...”,则将给出“Jimi Hendrix”用户名。比赛不区分大小写。因此“emailAddress =(。?)”将匹配“EMAILADDRESS = jimi @ hendrix.org,CN = ...”给用户名“jimi@hendrix.org”。若是客户端呈现证书而且成功提取了有效的用户名,那么在安全上下文中应该有一个有效的Authentication对象。若是没有找到证书,或者没有找到相应的用户,则安全上下文将保持为空。这意味着您能够轻松地使用X.509身份验证和其余选项(如基于表单的登陆)。
7.3。在Tomcat中设置SSL
Spring Security项目中的samples / certificate目录中有一些预生成的证书。若是不想本身生成,可使用这些来启用SSL进行测试。文件server.jks包含服务器证书,私钥和颁发证书颁发机构证书。还有一些来自示例应用程序的用户的客户端证书文件。您能够在浏览器中安装这些,以启用SSL客户端身份验证。
要运行具备SSL支持的tomcat,请将server.jks文件放入tomcat conf目录,并将如下链接器添加到server.xml文件
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="${catalina.home}/conf/server.jks" keystoreType="JKS" keystorePass="password" truststoreFile="${catalina.home}/conf/server.jks" truststoreType="JKS" truststorePass="password" />
若是仍然但愿SSL链接成功,即便客户端不提供证书,clientAuth也能够设置为须要。不提供证书的客户端将没法访问Spring Security所保护的任何对象,除非您使用非X.509认证机制,例如表单验证。
8.运行认证替换
8.1。概观
AbstractSecurityInterceptor可以在安全对象回调阶段临时替换SecurityContext和SecurityContextHolder中的Authentication对象。只有当AuthenticationManager和AccessDecisionManager成功处理了原始的Authentication对象时,才会发生这种状况。 RunAsManager将指示SecurityInterceptorCallback中应该使用的替换Authentication对象(若是有)。
经过在安全对象回调阶段临时替换Authentication对象,安全调用将可以调用须要不一样身份验证和受权凭证的其余对象。它还能够对特定的GrantedAuthority对象执行任何内部安全检查。因为Spring Security提供了一些辅助类,能够根据SecurityContextHolder的内容自动配置远程处理协议,这些运行替换在调用远程Web服务时特别有用
8.2。组态
RunAsManager接口由Spring Security提供:
Authentication buildRunAs(Authentication authentication, Object object, List<ConfigAttribute> config); boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
第一种方法返回在方法调用期间应替换现有Authentication对象的Authentication对象。若是该方法返回null,则表示不该进行替换。 AbstractSecurityInterceptor使用第二种方法做为其启动验证配置属性的一部分。支持(Class)方法由安全拦截器实现调用,以确保配置的RunAsManager支持安全拦截器将显示的安全对象的类型。
Spring Security提供了一个RunAsManager的具体实现。若是任何ConfigAttribute以RUN_AS_开头,RunAsManagerImpl类将返回一个替换RunAsUserToken。若是找到任何这样的ConfigAttribute,则替换的RunAsUserToken将包含与原始Authentication对象相同的主体,凭据和授予的权限,以及每一个RUN_AS_ ConfigAttribute的新GrantedAuthorityImpl。每一个新的GrantedAuthorityImpl将以ROLE_为前缀,后跟RUN_AS ConfigAttribute。例如,RUN_AS_SERVER将致使替换RunAsUserToken包含ROLE_RUN_AS_SERVER授予权限。
替换RunAsUserToken就像任何其余的Authentication对象同样。它须要经过AuthenticationManager进行身份验证,可能经过委托给合适的AuthenticationProvider进行身份验证。 RunAsImplAuthenticationProvider执行此类身份验证。它只会接受呈现的任何RunAsUserToken的有效。
为了确保恶意代码不会建立RunAsUserToken并提供它,以保证RunAsImplAuthenticationProvider的接受,密钥的哈希存储在全部生成的令牌中。 RunAsManagerImpl和RunAsImplAuthenticationProvider在具备相同键的bean上下文中建立:
<bean id="runAsManager" class="org.springframework.security.access.intercept.RunAsManagerImpl"> <property name="key" value="my_run_as_password"/> </bean> <bean id="runAsAuthenticationProvider" class="org.springframework.security.access.intercept.RunAsImplAuthenticationProvider"> <property name="key" value="my_run_as_password"/> </bean>
经过使用相同的密钥,每一个RunAsUserToken均可以经过已批准的RunAsManagerImpl来建立。出于安全缘由,RunAsUserToken在建立后是不可变的
9. Spring Security加密模块
9.1。介绍
Spring Security Crypto模块支持对称加密,密钥生成和密码编码。该代码做为核心模块的一部分进行分发,但不依赖任何其余Spring Security(或Spring)代码。
9.2。加密机
Encryptors类提供了构建对称加密器的工厂方法。使用此类,您能够建立ByteEncryptors以原始byte []形式加密数据。您还能够构建TextEncryptors来加密文本字符串。加密程序是线程安全的。
9.2.1。 BytesEncryptor
使用Encryptors.standard工厂方法构建一个“standard”BytesEncryptor:
Encryptors.standard("password", "salt");
“标准”加密方法是使用PKCS#5的PBKDF2(基于密码的密钥导出功能#2)的256位AES。该方法须要Java 6.用于生成SecretKey的密码应该保存在一个安全的地方,而不是共享的。若是您的加密数据受到威胁,该盐将用于防止对该密钥的字典攻击。也应用16字节的随机初始化向量,所以每一个加密的消息是惟一的。
提供的盐应该是十六进制编码的String形式,是随机的,长度至少为8个字节。这样的盐可使用KeyGenerator生成:
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
9.2.2。 TextEncryptor
使用Encryptors.text工厂方法构建标准的TextEncryptor:
Encryptors.text("password", "salt");
TextEncryptor使用标准BytesEncryptor来加密文本数据。加密结果做为十六进制编码字符串返回,以便在文件系统或数据库中轻松存储。
使用Encryptors.queryableText工厂方法构造一个“queryable”TextEncryptor:
Encryptors.queryableText("password", "salt");
可查询的TextEncryptor和标准TextEncryptor之间的区别与初始化向量(iv)处理有关。在可查询的TextEncryptor#encrypt操做中使用的iv是共享的,或者是常量的,不是随机生成的。这意味着屡次加密的相同文本将老是产生相同的加密结果。这不太安全,但须要查询的加密数据是必需的。可查询的加密文本的一个例子是OAuth apiKey。
9.3。关键发电机
KeyGenerators类为构建不一样类型的密钥生成器提供了许多便利的工厂方法。使用这个类,能够建立一个BytesKeyGenerator来生成byte []键。您还能够构造一个StringKeyGenerator来生成字符串键。 KeyGenerators是线程安全的。
9.3.1。 BytesKeyGenerator
使用KeyGenerators.secureRandom工厂方法生成由SecureRandom实例支持的BytesKeyGenerator:
KeyGenerator generator = KeyGenerators.secureRandom(); byte[] key = generator.generateKey();
默认密钥长度为8字节。还有一个KeyGenerators.secureRandom变体,能够控制关键长度:
KeyGenerators.secureRandom(16);
使用KeyGenerators.shared工厂方法来构造一个BytesKeyGenerator,它在每次调用时老是返回相同的键:
KeyGenerators.shared(16);
9.3.2。 StringKeyGenerator
使用KeyGenerators.string工厂方法构造一个8字节的SecureRandom KeyGenerator,它将每一个键做为字符串进行十六进制编码:
KeyGenerators.string();
9.4。密码编码
spring-security-crypto模块的密码包提供对密码编码的支持。 PasswordEncoder是中央服务接口,具备如下签名:
public interface PasswordEncoder { String encode(String rawPassword); boolean matches(String rawPassword, String encodedPassword); }
若是rawPassword一旦编码,则等于encodePassword,则matches方法返回true。该方法旨在支持基于密码的身份验证方案。
BCryptPasswordEncoder实现使用普遍支持的“bcrypt”算法对密码进行散列。 Bcrypt使用随机16字节的盐值,是一种故意慢的算法,以阻止密码破解者。可使用“强度”参数来调整其工做量,该参数的取值范围为4到31.值越高,计算散列的工做就越多。默认值为10.您能够在部署的系统中更改此值,而不会影响现有密码,由于该值也存储在编码散列中。
// Create an encoder with strength 16 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
10. 并发支持
在大多数环境中,Security以每一个Thread为基础存储。这意味着当一个新线程完成工做时,SecurityContext将丢失。 Spring Security提供了一些基础设施,能够帮助用户轻松实现。 Spring Security提供了在多线程环境中使用Spring Security的低级抽象。实际上,这就是Spring Security创建在与AsyncContext.start(Runnable)和Spring MVC Async Integration集成的基础之上。
10.1。 DelegatingSecurityContextRunnable
Spring Security的并发支持中最基础的构建块之一是DelegatingSecurityContextRunnable。它包装一个委托Runnable,以便为委托初始化具备指定SecurityContext的SecurityContextHolder。而后它调用委托Runnable确保之后清除SecurityContextHolder。 DelegatingSecurityContextRunnable看起来像这样:
public void run() { try { SecurityContextHolder.setContext(securityContext); delegate.run(); } finally { SecurityContextHolder.clearContext(); } }
虽然很是简单,它能够无缝地将SecurityContext从一个线程传输到另外一个线程。这是很是重要的,由于在大多数状况下,SecurityContextHolder会以每一个线程为基础。例如,您可能已经使用Spring Security的<global-method-security>支持来保护您的某个服务。您如今能够轻松地将当前线程的SecurityContext传输到调用安全服务的线程。下面能够找到一个能够作到这一点的例子:
Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; SecurityContext context = SecurityContextHolder.getContext(); DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable, context); new Thread(wrappedRunnable).start();
上述代码执行如下步骤:
- 建立一个能够调用咱们的安全服务的Runnable。请注意,它不知道Spring Security
- 获取咱们但愿从SecurityContextHolder使用的SecurityContext,并初始化DelegatingSecurityContextRunnable
- 使用DelegatingSecurityContextRunnable建立一个线程
- 启动咱们建立的线程
因为使用SecurityContextHolder的SecurityContext建立一个DelegatingSecurityContextRunnable是很常见的,所以它有一个快捷方式构造函数。如下代码与上述代码相同:
Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable); new Thread(wrappedRunnable).start();
咱们使用的代码很简单,可是它仍然须要咱们使用Spring Security的知识。在下一节中,咱们将介绍如何使用DelegatingSecurityContextExecutor来隐藏咱们使用Spring Security的事实。
10.2。 DelegatingSecurityContextExecutor
在上一节中,咱们发现很容易使用DelegatingSecurityContextRunnable,可是因为咱们必须了解Spring Security才能使用它,这并不理想。咱们来看看DelegatingSecurityContextExecutor如何能够屏蔽咱们的代码,不知道咱们使用的是什么Spring Security。
DelegatingSecurityContextExecutor的设计与DelegatingSecurityContextRunnable很是类似,除了它接受一个委托Executor而不是一个委托Runnable。您能够在下面看到如何使用它的示例:
SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER")); context.setAuthentication(authentication); SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor, context); Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; executor.execute(originalRunnable);
代码执行如下步骤:
- 建立要用于咱们的DelegatingSecurityContextExecutor的SecurityContext。请注意,在本示例中,咱们只需手动建立SecurityContext。然而,在什么地方或如何得到SecurityContext并不重要(即若是咱们想要的话,咱们能够从SecurityContextHolder获取它)。
- 建立一个负责执行提交的“Runnable”的delegateExecutor
- 最后,咱们建立一个DelegatingSecurityContextExecutor,它负责使用DelegatingSecurityContextRunnable将任何被传递到execute方法的Runnable进行包装。而后将包装的Runnable传递给delegateExecutor。在这种状况下,相同的SecurityContext将被用于提交给咱们的DelegatingSecurityContextExecutor的每一个Runnable。若是咱们运行后台任务须要由具备提高的权限的用户运行,这是很好的。
- 在这一点上,您可能会问本身“这是如何屏蔽我对Spring Security知识的代码?咱们能够注入一个已经初始化的DelegatingSecurityContextExecutor实例,而不是在咱们本身的代码中建立SecurityContext和DelegatingSecurityContextExecutor。
@Autowired private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor public void submitRunnable() { Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; executor.execute(originalRunnable); }
如今咱们的代码不知道SecurityContext被传播到Thread,而后执行originalRunnable,而后SecurityContextHolder被清除。在此示例中,正在使用相同的用户来执行每一个线程。当咱们调用executor.execute(Runnable)(即当前登陆的用户)来处理originalRunnable时,若是咱们想使用SecurityContextHolder中的用户呢?这能够经过从DelegatingSecurityContextExecutor构造函数中删除SecurityContext参数来完成。例如:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor);
如今随时执行executor.execute(Runnable),SecurityContext首先由SecurityContextHolder获取,而后使用SecurityContext建立咱们的DelegatingSecurityContextRunnable。这意味着咱们正在使用与用于调用executor.execute(Runnable)代码的用户相同的Runnable。
10.3。 Spring安全并发类
有关Java并发API和Spring任务抽象的更多集成,请参考Javadoc。一旦您了解之前的代码,他们就很是自我解释。
- DelegatingSecurityContextCallable
- DelegatingSecurityContextExecutor
- DelegatingSecurityContextExecutorService
- DelegatingSecurityContextRunnable
- DelegatingSecurityContextScheduledExecutorService
- DelegatingSecurityContextSchedulingTaskExecutor
- DelegatingSecurityContextAsyncTaskExecutor
- DelegatingSecurityContextTaskExecutor
11. Spring MVC集成
Spring Security提供了一些与Spring MVC的可选集成。本节将进一步详细介绍整合。
11.1。 @EnableWebMvcSecurity
要使Spring Security与Spring MVC集成,请将@EnableWebMvcSecurity注释添加到您的配置中。一个典型的例子是这样的:
@Configuration @EnableWebMvcSecurity public class SecurityConfig { // ... }
11.2。 @AuthenticationPrincipal
Spring Security提供了AuthenticationPrincipalArgumentResolver,它能够自动解析Spring MVC参数的当前Authentication.getPrincipal()。经过使用@EnableWebMvcSecurity,您将自动将其添加到您的Spring MVC配置中。若是您使用基于XML的配置,您必须本身添加。
一旦AuthenticationPrincipalArgumentResolver正确配置,您能够在Spring MVC层中彻底与Spring Security进行解耦。
考虑一种自定义UserDetailsService返回实现UserDetails的对象和您本身的CustomUser对象的状况。可使用如下代码访问当前验证的用户的CustomUser:
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal; // ... @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal(); // .. find messags for this user and return them ... }
从Spring Security 3.2开始,咱们能够经过添加注释来更直接的解决这个论点。例如:
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) { // .. find messags for this user and return them ... }
咱们能够经过将@AuthenticationPrincipal做为咱们本身的注释的元注释来进一步消除对Spring Security的依赖。下面咱们演示如何在一个名为@CurrentUser的注释上作到这一点。
重要的是要意识到,为了消除对Spring Security的依赖,消费应用程序将建立@CurrentUser。 此步骤不是严格要求的,但有助于将您对Spring Security的依赖性隔离到更中心的位置。
@Target({ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @AuthenticationPrincipal public @interface CurrentUser {}
如今已经指定了@CurrentUser,咱们可使用它来发出信号来解决咱们当前认证用户的CustomUser。 咱们也将咱们对Spring Security的依赖性隔离成一个文件。
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) { // .. find messags for this user and return them ... }
11.3。 Spring MVC异步集成
Spring Web MVC 3.2+对异步请求处理有很好的支持。 没有其余配置,Spring Security将自动将SecurityContext设置为执行控制器返回的Callable的Thread。 例如,如下方法将自动使用Callable在Callable建立时可用的SecurityContext执行:
@RequestMapping(method=RequestMethod.POST) public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() { public Object call() throws Exception { // ... return "someView"; } }; }
将SecurityContext与Callable关联
技术上来讲,Spring Security与WebAsyncManager集成。 用于处理Callable的SecurityContext是在调用startCallableProcessing时SecurityContextHolder上存在的SecurityContext。
与控制器返回的DeferredResult不进行自动整合。 这是由于DeferredResult由用户处理,所以没法自动与其集成。 可是,您仍然可使用[并发支持]来提供与Spring Security的透明集成。
11.4。 Spring MVC和CSRF集成
Spring Security将在使用Spring MVC表单标签的表单中自动包含CSRF令牌。 例如,如下JSP:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:form="http://www.springframework.org/tags/form" version="2.0"> <jsp:directive.page language="java" contentType="text/html" /> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <!-- ... --> <c:url var="logoutUrl" value="/logout"/> <form:form action="${logoutUrl}" method="post"> <input type="submit" value="Log out" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form:form> <!-- ... --> </html> </jsp:root>
Will output HTML that is similar to the following:
<!-- ... --> <form action="/context/logout" method="post"> <input type="submit" value="Log out"/> <input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/> </form> <!-- ... -->