Spring Security 参考手册

Spring Security 参考手册

Spring security 是一个强大的和高度可定制的身份验证和访问控制框架。它是确保基于Spring的应用程序的标准。
前言
Spring Security 为基于javaEE的企业应用程序提供一个全面的解决方案。正如你将从这个参考指南发现的,我们试图为你提供一个有用的并且高度可配置的安全系统。
安全是一个不断移动的目标,采取一个全面的全系统的方法很重要。在安全领域,我们鼓励你采取”layers of security”(安全层),这样每一层尽可能的在自己范围内诶保证安全,连续的层提供额外的安全性。安全层更密集你的程序将更加健壮更加安全。在最底层,你需要处理传输安全和系统识别这些问题,以减少中间人攻击。接下来,你讲通常利用防火墙,或许使用VPN或者IP担保以确保只有授权的系统能够尝试连接。在企业环境中你可以部署一个DMZ将面向公众的服务器和数据库以及应用服务器分隔开来。你的操作系统也将扮演重要的部分,解决问题,类似,使用非特权用户运行进程和提高文件系统安全性。操作系统通常会配置自己的防火墙。但愿在某处前进的道路上,你会试图阻止拒绝服务和暴力攻击。一个入侵检测系统将在监视和相应攻击时非常有用,这种系统能采取保护动作,比如实时阻断违规的TCP/IP地址。在更高的层,你的java虚拟机希望被配置为尽量减少不同的java类型授予的权限,然后将你的应用程序增加到器自身的制定域特定的安全配置。Spring Security 使后者 ,应用程序将更加安全更加容易。
当然你需要妥善处理上面提到的所有安全层,连同各层的管理因素。这样的管理因素答题包括安全公告监测、补丁、人员审查、审计、变更控制、工程管理系统、数据备份、灾难恢复、性能基准测试、负载监控、集中式日志记录、事件相应程序等。
Spring Secruity 致力于在企业应用程序安全层对你进行帮助,你会发现这里有如此不同的需求正如业务问题的领域。一个银行应用程序具有与电子商务应用不同的需求。电子商务应用程序同企业销售自订花工具具有不同的需求。这些定制需求使得应用安全有趣、有挑战性和有回报。
请阅读入门第二部分, 从“入门”开始。 这一章将为你介绍的框架和基于命名空间的配置系统,你可以配置好应用,来了解Spring Security 如何工作和一些你可能 需要使用的类 。你需要阅读 架构与实现。本指南的其余部分是更传统的引用样式结构,设计用于按需进行阅读。我们也建议你尽可能于都完一般的应用安全问题。Spring Secruity 不是万能的,不能解决所有的安全问题。重要的实在应用设计开始之初就考虑到安全性。后期改造不是一个好的主意。特别是,如果你正在构件一个Web应用程序,你应该知道许多潜在的漏洞,比如跨站脚本、请求伪造和会话劫持。这些你一开始就应该考虑。这个网站 (http://www.owasp.org/) 维护了一个大网站应用漏洞列表和一些有用的参考信息。
我希望这个参考手册对你来说比较有用,欢迎你的反馈和建议。 suggestions.
最后欢迎你来到Spring Security 社区。 community.
入门
本指南的稍后张杰会对框架的架构和实现类进行一个深度的讨论,如果你的相对Spring Security进行一个深度定制没这一章节将会包含你需要了解的内容。在本章我们将会介绍Spring Security 3.0 给项目的历史进行简要的概述,简单的讲讲如何开始使用设个框架。尤其是我们将看看命名空间配置,他提供与传统Spring Bean你必须连接所有实现类的途径更简单的方式保护你的应用程序。
我们也会看看实例应用。在你阅读后面的章节之前你指的试着运行体验它。当时你对框架连接更多的时候你还可以汇过来回顾一下。 project website 同时请参阅项目的网站,因为他有创建这个项目的有用的信息,以及文章、视频和教程。
简介
Spring Security是什么?
Spring Security 提供了基于javaEE的企业应有个你软件全面的安全服务。这里特别强调支持使用SPring框架构件的项目,Spring框架是企业软件开发javaEE方案的领导者。如果你还没有使用Spring来开发企业应用程序,我们热忱的鼓励你仔细的看一看。熟悉Spring特别是一来注入原理两帮助你更快更方便的使用Spring Security。
人们使用Spring Secruity的原因有很多,单大部分都发现了javaEE的Servlet规范或EJB规范中的安全功能缺乏典型企业应用场景所需的深度。提到这些规范,重要的是要认识到他们在WAR或EAR级别无法移植。因此如果你更换服务器环境,这里有典型的大量工作去重新配置你的应用程序员安全到新的目标环境。使用Spring Security 解决了这些问题,也为你提供许多其他有用的,可定制的安全功能。
正如你可能知道的两个应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。“认证”,是建立一个他声明的主题的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。这个概念是通用的而不只在Spring Security中。
在身份验证层,Spring Security 的支持多种认证模式。这些验证绝大多数都是要么由第三方提供,或由相关的标准组织,如互联网工程任务组开发。另外Spring Security 提供自己的一组认证功能。具体而言,Spring Security 目前支持所有这些技术集成的身份验证:
● HTTP BASIC 认证头 (基于 IETF RFC-based 标准)
● HTTP Digest 认证头 ( IETF RFC-based 标准)
● HTTP X.509 客户端证书交换 ( IETF RFC-based 标准)
● LDAP (一个非常常见的方法来跨平台认证需要, 尤其是在大型环境)
● Form-based authentication (用于简单的用户界面)
● OpenID 认证
● Authentication based on pre-established request headers (such as Computer Associates Siteminder) 根据预先建立的请求有进行验证
● JA-SIG Central Authentication Service (CAS,一个开源的SSO系统 )
● Transparent authentication context propagation for Remote Method Invocation (RMI) and HttpInvoker (Spring远程协议)
● Automatic “remember-me” authentication (你可以勾选一个框以避免预定的时间段再认证)
● Anonymous authentication (让每一个未经验证的访问自动假设为一个特定的安全标识)
● Run-as authentication (在一个访问应该使用不同的安全标识时非常有用)
● Java Authentication and Authorization Service (JAAS)
● JEE container autentication (所以如果愿你以可以任然使用容器管理的认证)
● Kerberos
● Java Open Source Single Sign On (JOSSO) *
● OpenNMS Network Management Platform *
● AppFuse *
● AndroMDA *
● Mule ESB *
● Direct Web Request (DWR) *
● Grails *
● Tapestry *
● JTrac *
● Jasypt *
● Roller *
● Elastic Path *
● Atlassian Crowd *
● Your own authentication systems (see below)
● 表示由第三方提供
很多独立软件供应商,因为灵活的身份验证模式二选择Spring Security。这样做允许他们快速的集成到他们的终端客户需求的解决方案而不用进行大量工程或者改变客户的环境。如果上面的验证机制不符合你的需求,Spring Security 是一个开放的平台,要实现你 自己的验证机制检查。Spring Security 的许多企业用户需要与不遵循任何安全标准的“遗留”系统集成,Spring Security可以很好的与这类系统集成。
无论何种身份验证机制,Spring Security提供一套的授权功能。这里有三个主要的热点区域,授权web请求、授权方法是否可以被调用和授权访问单个域对象的实例。为了帮助让你分别了解这些差异,认识在Servlet规范网络模式安全的授权功能,EJB容器管理的安全性和文件系统的安全。Spring Security在这些重要的区域提供授权功能,我们将在手册后面进行介绍。
历史
Spring Security 以“The Acegi Secutity System for Spring” 的名字始于2013年晚些时候。一个问题提交到Spring 开发者的邮件列表,询问是否已经有考虑一个机遇Spring 的安全性社区实现。那时候Spring 的社区相对较小(相对现在)。实际上Spring自己在2013年只是一个存在于ScourseForge的项目,这个问题的回答是一个值得研究的领域,虽然目前时间的缺乏组织了我们对它的探索。
考虑到这一点,一个简单的安全实现建成但是并没有发布。几周后,Spring社区的其他成员询问了安全性,这次这个代码被发送给他们。其他几个请求也跟随而来。到2014年一月大约有20万人使用了这个代码。这些创业者的人提出一个SourceForge项目加入是为了,这是在2004三月正式成立。
在早些时候,这个项目没有任何自己的验证模块,身份验证过程依赖于容器管理的安全性和Acegi安全性。而不是专注于授权。开始的时候这很适合,但是越来越多的用户请求额外的容器支持。容器特定的认证领域接口的基本限制变得清晰。还有一个相关的问题增加新的容器的路径,这是最终用户的困惑和错误配置的常见问题。
Acegi安全特定的认证服务介绍。大约一年后,Acegi安全正式成为了Spring框架的子项目。1.0.0最终版本是出版于2006 -在超过两年半的大量生产的软件项目和数以百计的改进和积极利用社区的贡献。
Acegi安全2007年底正式成为了Spring组合项目,更名为”Spring Security”。
如今Spring Security有一个强大的和积极的开源社区。在支持论坛上有成千上万的关于Spring Security的消息。有一个活跃的核心的开发人员,他们的代码本身和一个活跃的社区,也经常分享补丁和支持他们的同龄人。
发布版本号
了解Spring Security发布版本号如何工作是很有用的,他可以帮助你识别出工作(或缺乏的功能)设计到参与迁移到项目的未来版本,每个发布使用3个整数,MAJOR.MINOR.PATCH(主版本、次要版本、补丁版本).这样做的目的是主版本是不兼容的,API大范围的升级。次要版本应该保留大部分源代码和二进制兼容旧版本的次要版本,认为可能有一些设计变更和不兼容的更新。补丁版本应该向前向后完美兼容。包含一些bug和缺陷修复这些意外的改变。
在某种程度上,你受到变化的影响取决于你的代码是如何紧密集成的。如果你正在做大量定制,你更可能受到比简单的命名空间配置更大的影响。
你应该总是推出一个新版本之前彻底测试你的应用程序。
Getting Spring Security
你可以通过几种方式获取Spring Security。你可以从 Spring Security 页面下载一个分发包。从Maven库下载分离的jar文件。另外你也可以从源代码自己编译。
使用Maven
一个最小的SPring Security Maven 依赖通常和下面的类似:
pom.xml

<dependencies>

<dependency>
    <groupId>org.springframework.securitygroupId>
    <artifactId>spring-security-webartifactId>
    <version>{spring-security-version}version>
dependency>
<dependency>
    <groupId>org.springframework.securitygroupId>
    <artifactId>spring-security-configartifactId>
    <version>{spring-security-version}version>
dependency>
dependencies>

如果你使用的额外的功能比如LDAP,OpenID,等等,你需要包含适当的模块,查阅Section 1.4.3,”Project Modules” 项目模块.
Maven 仓库
所有GA发布版本(版本号以.RELEASE结尾)都被部署到Maven Central ,所以不需要在你的pom里设置额外的库
如果你使用了一个 SNAPSHOT 版本,你需要确认你设置了Snapshot库,如下:
pom.xml

<repositories>

<repository>
    <id>spring-snapshotid>
    <name>Spring Snapshot Repositoryname>
    <url>http://repo.spring.io/snapshoturl>
repository>
repositories>

如果你正在使用一个里程碑或者发布候选版本,你需要确保你所定义的Spring里程碑库,如下图所示:
pom.xml

<repositories>

<repository>
    <id>spring-milestoneid>
    <name>Spring Milestone Repositoryname>
    <url>http://repo.spring.io/milestoneurl>
repository>
repositories>

Spring 框架 Bom
Spring Security是针对Spring 框架 4.1.6RELEASE 建立的{spring-version}, 但是快要与4.0.x正常工作。很多用户都会有这个问题,Spring Security 传递依赖解析到Spring框架4.1.6 RELEASE这可能导致奇怪的classpath问题。{spring-version}
规避这个问题的一种方式是在你的pom文件的 的部分.另一种办法是像下面这样在pom.xml中包含spring-framework-bom :
pom.xml

<dependencyManagement>
    <dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-framework-bomartifactId>
        <version>{spring-version}version>
        <type>pomtype>
        <scope>importscope>
    dependency>
    dependencies>
dependencyManagement>

这样将确保Spring Security传递的所有依赖都使用 Spring 4.16.RELEASE {spring-version} 模块.
这种但是使用了 Maven’s “bill of materials” (BOM) 概念, Maven 2.0.9+可用. 如果想了解Maven如何解析依赖请参考Maven的依赖机制文档的介绍。 Maven’s Introduction to the Dependency Mechanism documentation.
Gradle
最小的Gradle一组典型的依赖如下:
build.gradle

dependencies {
    compile 'org.springframework.security:spring-security-web:{spring-security-version}'
    compile 'org.springframework.security:spring-security-config:{spring-security-version}'
}

如果你使用了 LDAP, OpenID, 等等. 你需要包含相应的模块,请参阅“Project Modules” 项目模块.
Gradle Repositories
所有的GA版本 (以.RELEASE结尾)都部署到了Maven Central库, 所以使用mavenCentral()库就足够了 .
build.gradle

repositories {
    mavenCentral()
}

如果你正在使用SNAPSHOT版本,你需要确保你使用了Spring的Snapshot库,定义如下:
build.gradle

repositories {
    maven { url 'https://repo.spring.io/snapshot' }
}

如果你正在使用里程碑或者发布候选版。你需要确认你使用了SPring的里程碑仓库,定义如下:
build.gradle

repositories {
    maven { url 'https://repo.spring.io/milestone' }
}

使用 Spring 4.0.x 和 Gradle
解析依赖传递是Gradle默认会使用最新的版本。这意为这当在Spring4.1.6RELEASE 下使用Spring Security 4.0.2RELEASE我们不需要做额外的工作。 {spring-security-version} {spring-version} 然而有时出现一些问题,最好适应Gradle的ResolutionStrategy 按照下面这种方式处理 Gradle’s ResolutionStrategy:
build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.group == 'org.springframework') {
            details.useVersion '{spring-version}'
        }
    }
}

这样将确保Spring Security 传递的依赖使用Spring 4.1.6.RELEASE {spring-version}模块.
这个例子使用 Gradle 1.9,但可能需要在gradle的未来版本中进行修改,因为这是在一个Gradle的孵化功能.
项目模块
在 Spring Security 3.0, 代码库被分割到单独的jar,这样可以更清楚的分隔不用功能区域和第三方依赖。如果你使用Maven构建你的项目,那么这些都是需要你添加到你的pom.xml中的. 甚至你没有使用Maven,我们建议你请求pom.xml文件来获取第三方依赖和版本。另一个好办法是检查实例应用程序的库。
核心模块 - spring-security-core.jar
包含核心的验证和访问控制类和接口,远程支持和基本的配置API。任何使用Spring Security的应用程序都需要这个模块。支持独立应用程序、远程客户端、服务层方法安全和JDBC用户配置。包含以下顶层包:

● org.springframework.security.core
● org.springframework.security.access
● org.springframework.security.authentication
● org.springframework.security.provisioning

远程调用 - spring-security-remoting.jar
提供与Spring Remoting的集成,通常你不需要这个模块,除非你挣使用Spring Remoting编写远程客户端。主要的包是 org.springframework.security.remoting.
网页 - spring-security-web.jar
包括锅炉汽和网站安全相关的基础代码。使用Servlet API的任何应用依赖他。如果你需要Spring Security网页验证服务和基于URL的访问控制你需要这个模块。主包名为 org.springframework.security.web.
配置 - spring-security-config.jar
包含安全命令空间的解析代码。如果你使用Spring Security XML命令空间进行配置你需要包含这个模块。 主包名为org.springframework.security.config. 没有类需要被应用程序直接使用.
LDAP - spring-security-ldap.jar
LDAP验证和配置代码,如果你需要使用LDAP验证和管理LDAP用户实体,你需要这个模块。主包名为 org.springframework.security.ldap.
ACL访问控制表 - spring-security-acl.jar
ACL专门的领域对象的实现。用来在你的应用程序中应用安全特定的领域对象实例。主包名为 org.springframework.security.acls.
CAS - spring-security-cas.jar
Spring Security的CAS客户端集成。如果你想用CAS的SSO服务器使用Spring Security网页验证需要该模块。顶层的包是 org.springframework.security.cas.
OpenID - spring-security-openid.jar
OpenID 网页验证支持。使用外部的OpenID服务器验证用户。 org.springframework.security.openid. 需要 OpenID4Java.
Test - spring-security-test.jar
支持Spring Security的测试。
签出源代码
因为Spring Security是一个开源项目,我们强烈建议您使用Git签出源代码。这可以让你完全访问所有势力应用程序,你可以轻松地构建最先进的最新版本的项目。有了项目的源代码对debug有非常大的帮助。异常堆栈跟踪不再是模糊的黑盒问题,你可以沿着他找出到底发生了什么。源代码是中级文档,通常也是最简答的项目实际是如何工作的地方。
要获取项目源代码,请使用一下git命令:
git clone https://github.com/spring-projects/spring-security.git
这可以让你在本地机器上访问到整个项目的历史记录(包括所有版本和分支).
Spring Security 4.1新特性
Spring Security 4.1 新特性有关的链接 100+ RC1 issues and 60+ RC2 issues 这里是新特性的列表:
Java 配置提升
● Simplified UserDetailsService Java Configuration
● Simplified AuthenticationProvider Java Configuration
● 通过 LogoutConfigurer配置内容谈判 LogoutSuccessHandler(s)
● 通过SessionManagementConfigurer可配置InvalidSessionStrategy
● 使用HttpSecurity.addFilterAt在链中的特定位置添加一个Filter的能力
Web应用程序安全性提升
● MvcRequestMatcher
● Content Security Policy (CSP)
● HTTP Public Key Pinning (HPKP)
● CORS
● CookieCsrfTokenRepository 提供简单的 AngularJS 与 CSRF 整合
● 添加 ForwardAuthenticationFailureHandler 和 ForwardAuthenticationSuccessHandler
● AuthenticationPrincipal 支持表达式属性从而支持转换 Authentication.getPrincipal() 对象 (即处理不可变的自定义的User域对象)
授权改进
● Path Variables in Web Security Expressions
● Method Security Meta Annotations
密码模块的改进
● SCrypt 支持 SCryptPasswordEncoder
● PBKDF2 支持 Pbkdf2PasswordEncoder
● BouncyCastle 中新改进的 BytesEncryptor 使用 AES/CBC/PKCS5Padding 和 AES/GCM/NoPadding 算法
测试的改进
● @WithAnonymousUser
● @WithUserDetails 允许指定 UserDetailsService bean 名称
● Test Meta Annotations
● 模拟一个列表的能力GrantedAuthority 使用 SecurityMockMvcResultMatchers.withAuthorities
一般的改进
● 重新组织样本项目
● 移动到GitHub的问题
样品和指南 (Start Here)
如果您想从Spring Security开始,最好的从我们的示例应用程序开始。
源描述指南{gh-samples-url}/javaconfig/helloworld[Hello Spring Security]演示如何整合Spring Security和基于java的配置的现有应用程序。链接:../../guides/html5/helloworld-javaconfig.html[Hello Spring Security Guide]{gh-samples-url}/boot/helloworld[Hello Spring Security Boot]演示如何整合Spring Security和 Spring Boot应用程序。链接:../../guides/html5/helloworld-boot.html[Hello Spring Security Boot Guide]{gh-samples-url}/xml/helloworld[Hello Spring Security XML]演示如何整合Spring Security和基于XML配置使用的现有应用程序。链接:../../guides/html5/helloworld-xml.html[Hello Spring Security XML Guide]{gh-samples-url}/javaconfig/hellomvc[Hello Spring MVC Security]演示如何整合Spring Security和Spring MVC 应用程序。链接:../../guides/html5/hellomvc-javaconfig.html[Hello Spring MVC Security Guide]{gh-samples-url}/javaconfig/form[Custom Login Form]演示如何创建自定义表单。链接:../../guides/html5/form-javaconfig.html[Custom Login Form Guide]
Java 配置
java配置的支持主要在Spring框架的3.1加入. Java Configuration. Spring Security 从3.2开始加入java配置支持。这让用户不使用任何XML用更简单方式配置Spring Security。
如果你熟悉第四章,安全命名空间配置,安全命名空间配置 你应该找到它和java的安全配置支持之间的相当多的相似之处.
Spring Security 提供大量的示例应用 lots of sample applications 使用-jc参数演示使用java配置Spring Security。
基础的网站安全java配置
第一步是创建我们的java配置。这个配置在你的应用程序中创建一个springSecurityFilterChain 的Servlet的过滤器 springSecurityFilterChain负责所有安全(例如 保护应用程序的URL,验证提交的用户名和密码,重定向到登陆的表单等等)。你可以在下面找到大部分java配置项的例子:

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }
}

configureGlobal方法的名字不重要,然而,重要的是只在一个被@EnableWebSecurity, @EnableGlobalMethodSecurity,或者@EnableGlobalAuthentication注解的类中配置AuthenticationManagerBuilder,否则会有不可预知的后果。
真的是没有太多的配置,但它确实有很多功能,你可以在下面找到功能摘要:
● 在你的应用程序中对每个URL进行验证
● 为你生成一个登陆表单
● 允许使用用户名 Username user 和密码 Password password 使用验证表单进行验证。
● 允许用户登出
● CSRF attack CSPF攻击防范
● Session Fixation Session保护
● 安全 Header 集成
○ HTTP Strict Transport Security 对安全要求严格的HTTP传输安全
○ X-Content-Type-Options X-Content-Type-Options集成
○ 缓存控制(稍后可以允许你缓存静态资源)
○ X-XSS-Protection X-XSS-Protection集成
○ X-Frame-Options 集成防止点击劫持 Clickjacking
● 和以下 Servlet API 方法集成
○ HttpServletRequest#getRemoteUser()
○ HttpServletRequest.html#getUserPrincipal()
○ HttpServletRequest.html#isUserInRole(java.lang.String)
○ HttpServletRequest.html#login(java.lang.String, java.lang.String)
○ HttpServletRequest.html#logout()
AbstractSecurityWebApplicationInitializer
下一步是在war里注册springSecurityFilterChain。这可以通过Spring在Servlet 3.0+环境中对 Spring’s WebApplicationInitializer supportWebApplicationInitializer的支持进行了java配置,Spring Security提供了基本的抽象类 AbstractSecurityWebApplicationInitializer这可以确保springSecurityFilterChain 已经被注册。我们使用AbstractSecurityWebApplicationInitializer 的不同方式取决于你是已经在使用Spring框架还是只是用了Spring Security。
● [abstractsecuritywebapplicationinitializer-without-existing-spring] - 如果你没有使用框架就使用这个说明
● [abstractsecuritywebapplicationinitializer-with-spring-mvc] - 如果你正在使用Spring框架使用这个说明
AbstractSecurityWebApplicationInitializer 不与Spring一起使用
如果你没有使用Spring MVC 或Spring,你需要传递 WebSecurityConfig 到超类来确保配置被使用,你可以参考下面的例子:

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
    extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(WebSecurityConfig.class);
    }
}

SecurityWebApplicationInitializer 将会做下面的事情:
● 自动为你的应用程序的每个URL注册 springSecurityFilterChain过滤器
● 添加一个ContextLoadListener 用来载入 WebSecurityConfig.
AbstractSecurityWebApplicationInitializer 与 Spring MVC一起使用
如果我们在应用程序的其他地方已经使用了Spring,那么我们已经有了一个 WebApplicationInitializer用来载入Spring的配置。如果我们使用上面的配置将会得到一个错误。所以我们应该使用已经存在的 ApplicationContext 注册Spring Security。举个例子,如果我们使用Spring MVC我们的 SecurityWebApplicationInitializer 应该看起来和下面差不多:

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
    extends AbstractSecurityWebApplicationInitializer {

}

这回简单的只为你的应用程序的所有URL注册springSecurityChain过滤器。然后我们需要确保这个Security配置被载入到我们已经存在的 WebSecurityConfig。例如,如果你使用Spring MVC 它应该被加入到 getRootConfigClasses()

public class MvcWebApplicationInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] { WebSecurityConfig.class };
    }

    // ... other overrides ...
}

HttpSecurity
到目前为止我们的WebSecurityConfig只包含了关于如何验证我们的用户的信息。Spring Security怎么知道我们相对所有的用户进行验证?Spring Securityn怎么知道我们需要支持基于表单的验证?原因是WebSecurityConfigurerAdapter在configure(HttpSecurity http)方法中提供了一个默认的配置,看起来和下面类似:

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .and()
        .httpBasic();
}

上面的默认配置:
● 确保我们应用中的所有请求都需要用户被认证
● 允许用户进行基于表单的认证
● 允许用户使用HTTP基于验证进行认证
你可以看到这个配置和下面的XML命名配置相似:

<http>
    <intercept-url pattern="/**" access="authenticated"/>
    <form-login />
    <http-basic />
http>

java配置使用and()方法相当于XML标签的关闭。 这样允许我们继续配置父类节点。如果你阅读代码很合理,想配置请求验证, 并使用表单和HTTP基本身份验证进行登录。
然而,java配置有不同的默认URL和参数,当你自定义用户登录页是需要牢记这一点。让我们的URL冯家RESTful,另外不要那么明显的观察出我么你在使用Spring Security这样帮助我们避免信息泄露。 information leaks。比如:
Java配置和表单登录
你可能会想知道系统提示您登录表单从哪里来的,因为我们都没有提供任何的HTML或JSP文件。由于Spring Security的默认配置并没有明确设定一个登录页面的URL,Spring Security自动生成一个,基于这个功能被启用,使用默认URL处理登录的提交内容,登录后跳转的URL等等。
自动生成的登录页面可以方便应用的快速启动和运行,大多数应用程序都需要提供自己的登录页面。要做到这一点,我们可以更新我们的配置,如下所示:

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login") 
            .permitAll();        
}

指定登录页的路径我们必须允许所有用户访问我们的登录页(例如为验证的用户),这个formLogin().permitAll()方法允许基于表单登录的所有的URL的所有用户的访问。
一个我么当前配置使用的JSP实现的页面如下:
下面这个登陆页是我们的当前配置,如果不符合我们的要求我们可以很容易的更新我们的配置。

<c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post">       
    <c:if test="${param.error != null}">        
        <p>
            Invalid username and password.
        p>
    c:if>
    <c:if test="${param.logout != null}">       
        <p>
            You have been logged out.
        p>
    c:if>
    <p>
        <label for="username">Usernamelabel>
        <input type="text" id="username" name="username"/>  
    p>
    <p>
        <label for="password">Passwordlabel>
        <input type="password" id="password" name="password"/>  
    p>
    <input type="hidden"                        
        name="${_csrf.parameterName}"
        value="${_csrf.token}"/>
    <button type="submit" class="btn">Log inbutton>
form>

一个POST请求到/login用来验证用户如果参数有error, 验证尝试失败如果请求蚕食logout存在则登出登录名参数必须被命名为username密码参数必须被命名为passwordCSRF参数,了解更多查阅后续 包括CSRF令牌 和Cross Site Request Forgery (CSRF) 相关章节
验证请求
我们的例子中要求用户进行身份验证并且在我们应用程序的每个URL这样做。我么你可以通过给http.authorizeRequests()添加多个子节点来指定多个定制需求到我们的URL。例如:

protected void configure(HttpSecurity http) throws Exception {
    http
    .authorizeRequests()                                                                
    .antMatchers("/resources/**", "/signup", "/about").permitAll()                  
    .antMatchers("/admin/**").hasRole("ADMIN")                                      
    .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")            
    .anyRequest().authenticated()                                                   
    .and()
    // ...
    .formLogin();
}

http.authorizeRequests()方法有多个子节点,每个macher按照他们的声明顺序执行。我们指定任何用户都可以通过访问的多个URL模式。任何用户都可以访问URL以”/resources/”, equals “/signup”, 或者 “/about”开头的URL。以 “/admin/” 开头的URL只能由拥有 “ROLE_ADMIN”角色的用户访问。请注意我们使用 hasRole 方法,没有使用 “ROLE_” 前缀.任何以”/db/” 开头的URL需要用户同时具有 “ROLE_ADMIN” 和 “ROLE_DBA”。和上面一样我们的 hasRole 方法也没有使用 “ROLE_” 前缀.尚未匹配的任何URL要求用户进行身份验证
处理登出
当使用WebSecurityConfigurerAdapter, 注销功能会自动应用。默认是访问URL/logout将注销登陆的用户:
● 使HTTP Session 无效
● 清楚所有已经配置的 RememberMe 认证
● 清除SecurityContextHolder
● 跳转到 /login?logout
和登录功能类似,你也有不同的选项来定制你的注销功能:

protected void configure(HttpSecurity http) throws Exception {
    http
        .logout()                                                                
            .logoutUrl("/my/logout")                                                 
            .logoutSuccessUrl("/my/index")                                           
            .logoutSuccessHandler(logoutSuccessHandler)                              
            .invalidateHttpSession(true)                                             
            .addLogoutHandler(logoutHandler)                                         
            .deleteCookies(cookieNamesToClear)                                       
            .and()
        ...
}

提供注销支持,使用WebSecurityConfigurerAdapter会自动被应用。设置触发注销操作的URL (默认是/logout). 如果CSRF内启用(默认是启用的)的话这个请求的方式被限定为POST。 请查阅相关信息 JavaDoc相关信息.注销之后跳转的URL。默认是/login?logout。具体查看 the JavaDoc文档.让你设置定制的 LogoutSuccessHandler。如果指定了这个选项那么logoutSuccessUrl()的设置会被忽略。请查阅 JavaDoc文档.指定是否在注销时让HttpSession无效。 默认设置为 true。 在内部配置SecurityContextLogoutHandler选项。 请查阅 JavaDoc文档.添加一个LogoutHandler.默认SecurityContextLogoutHandler会被添加为最后一个LogoutHandler。允许指定在注销成功时将移除的cookie。这是一个现实的添加一个CookieClearingLogoutHandler的快捷方式。
注销也可以通过XML命名空间进行配置,请参阅Spring Security XML命名空间相关文档获取更多细节logout element。
一般来说,为了定制注销功能,你可以添加 LogoutHandler和LogoutSuccessHandler的实现。 对于许多常见场景,当使用流食式API时,这些处理器会在幕后进行添加。
LogoutHandler
一般来说,LogoutHandler的实现类可以参阅到注销处理中。他们被用来执行必要的清理,因而他们不应该抛出错误,我们提供您各种实现:
● PersistentTokenBasedRememberMeServices
● TokenBasedRememberMeServices
● CookieClearingLogoutHandler
● CsrfLogoutHandler
● SecurityContextLogoutHandler
请查看 Remember-Me的接口和实现 获取详情。
流式API提供了调用相应的LogoutHandler 实现的快捷方式, 而不是直接提供LogoutHandler 的实现。例如:deleteCookies() 允许指定注销成功时要删除一个或者多个cookie。这是一个添加 CookieClearingLogoutHandler的快捷方式。
LogoutSuccessHandler
LogoutSuccessHandler被LogoutFilter在成功注销后调用,用来进行重定向或者转发相应的目的地。注意这个借口与LogoutHandler几乎一样,但是可以抛出异常。
下面是提供的一些实现:
● SimpleUrlLogoutSuccessHandler
● HttpStatusReturningLogoutSuccessHandler
和前面提到的一样,你不需要直接指定SimpleUrlLogoutSuccessHandler。而使用流式API通过设置logoutSuccessUrl()快捷的进行设置SimpleUrlLogoutSuccessHandler。注销成功 后将重定向到设置的URL地址。默认的地址是 /login?logout.
在REST API场景中HttpStatusReturningLogoutSuccessHandler会进行一些有趣的改变。LogoutSuccessHandler允许你设置一个返回给客户端的HTTP状态码(默认返回200)来替换重定向到URL这个动作。
进一步的注销相关的参考(TODO)
● 处理注销
● 测试注销
● HttpServletRequest.logout()
● 章节”Remember-Me接口和实现”
● 注销的CSRF说明
● 点点注销(CAS协议)
● 注销的XML命名空间章节
验证
到现在为止我们只看了一下基本的验证配置,让我们看看一些稍微高级点的身份验证配置选项。
内存中的身份验证
我们已经看到了一个单用户配置到内存验证的示例,下面是配置多个用户的例子:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .inMemoryAuthentication()
            .withUser("user").password("password").roles("USER").and()
            .withUser("admin").password("password").roles("USER", "ADMIN");
}

JDBC 验证
你可以找一些更新来支持JDBC的验证。下面的例子假设你已经在应用程序中定义好了DataSource, jdbc-javaconfig 示例提供了一个完整的基于JDBC的验证。

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .jdbcAuthentication()
            .dataSource(dataSource)
            .withDefaultSchema()
            .withUser("user").password("password").roles("USER").and()
            .withUser("admin").password("password").roles("USER", "ADMIN");
}

LDAP 验证
你可以找一些更新来支持LDAP的身份验证, ldap-javaconfig 提供了一个完成的使用基于LDAP的身份验证的示例。

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .ldapAuthentication()
            .userDnPatterns("uid={0},ou=people")
            .groupSearchBase("ou=groups");
}

上面的例子中使用一下LDIF和前如何Apache DS LDAP示例。
users.ldif
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password

dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password

dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org

dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
AuthenticationProvider
您可以通过一个自定义的AuthenticationProvider为bean定义自定义身份验证。 例如, 下面这个例子假设自定义身份验证SpringAuthenticationProvider实现了AuthenticationProvider:
NOTE:AuthenticationManagerBuilder如果还不密集这将仅被使用。

@Bean
public SpringAuthenticationProvider springAuthenticationProvider() {
    return new SpringAuthenticationProvider();
}

UserDetailsService
你可以通过一个自定义的UserDetailsService为bean定义自定义身份验证。 例如,下面这个例子假设自定义身份验证SpringDataUserDetailsService实现了UserDetailsService:
NOTE:AuthenticationManagerBuilder如果还不密集这将被仅被使用并且没有AuthenticationProviderBean申明。

@Bean
public SpringDataUserDetailsService springDataUserDetailsService() {
    return new SpringDataUserDetailsService();
}

你也可以通过让passwordencoder为bean自定义密码如何编码。 例如,如果你使用BCrypt,你可以添加一个bean定义如下图所示:

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

LDAP 验证
多个HttpSecurity
我们可以配置多个HttpSecurity实例,就像我们可以有多个块. 关键在于对WebSecurityConfigurationAdapter进行多次扩展。例如下面是一个对/api/开头的URL进行的不同的设置。

@EnableWebSecurity
public class MultiHttpSecurityConfig {
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) { 
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER").and()
                .withUser("admin").password("password").roles("USER", "ADMIN");
    }

    @Configuration
    @Order(1)                                                        
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/api/**")                               
                .authorizeRequests()
                    .anyRequest().hasRole("ADMIN")
                    .and()
                .httpBasic();
        }
    }

    @Configuration                                                   
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .formLogin();
        }
    }
}

配置正常的验证。创建一个WebSecurityConfigurerAdapter,包含一个@Order注解,用来指定个哪一个WebSecurityConfigurerAdapter更优先。http.antMatcher指出,这个HttpSecurity只应用到以/api/开头的URL上。创建另外一个WebSecurityConfigurerAdapter实例。用于不以/api/开头的URL,这个配置的顺序在ApiWebSecurityConfigurationAdapter之后,因为他没有指定@Order值为1(没有指定@Order默认会被放到最后).
方法安全
从2.0 开始Spring Security对服务层的方法的安全有了实质性的改善。他提供对JSR-250的注解安全支持像框架原生@Secured注解一样好。从3.0开始你也可以使用新的基于表达式的注解 expression-based annotations。你可以应用安全到单独的bean,使用拦截方法元素去修饰Bean声明,或者你可以在整个服务层使用 AspectJ风格的切入点保护多个bean。
EnableGlobalMethodSecur

@EnableGlobalMethodSecurity(securedEnabled = true) public class
MethodSecurityConfig { // … }

个注解到一个方法(或者一个类或者接机)会限制对相应方法的访问。Spring Security的原生注解支持定义了一套用于该方法的属性。这些将被传递AccessDecisionManager到来做实际的决定:

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

使用如下代码启用JSR-250注解的支持

@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}

这些都是基于标准的,并允许应有个你简单的基于角色的约束,但是没有Spring Security的本地注解的能力。要使用基于表达书的语法,你可以使用:

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}

和响应Java代码如下:

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

GlobalMethodSecurityConfiguration
有时候你可能需要执行一些比@EnableGlobalMethodSecurity注解允许的更复杂的操作。对于这些情况,你可以扩展GlobalMethodSecurityConfiguration确保@EnableGlobalMethodSecurity注解出现在你的子类。例如如果你想提供一个定制的MethodSecurityExpressionHandler,你可以使用下面的配置:

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        // ... create and return custom MethodSecurityExpressionHandler ...
        return expressionHandler;
    }
}

关于可以被重写的方法的更多信息,请参考GlobalMethodSecurityConfiguration的java文档。
已配置对象的后续处理
Spring Security的Java配置没有公开每个配置对象的每一个属性,这简化了广大用户的配置。毕竟如果要配置每一个属性,用户可以使用标准的Bean配置。
虽然有一些很好的理由不直接暴露所有属性,用户可能仍然需要更多高级配置,为了解决这个Spring Security引入了ObjectPostProcessor概念,用来替换java配置的对象实例。例如:如果你想在filterSecurityPublishAuthorizationSuccess里配置FilterSecurityInterceptor属性,你可以想下面一样:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .withObjectPostProcessor(new ObjectPostProcessor() {
                public  O postProcess(
                        O fsi) {
                    fsi.setPublishAuthorizationSuccess(true);
                    return fsi;
                }
            });
}

安全命名空间配置
简介
命名空间配置在Spring框架的2.0版本就可以使用了,他允许你通过额外的XML架构元素补充传统的Spring bean应用程序上下文。你可以从Spring的参考文档找到更多信息 Reference Documentation.。命名空间元素可以简单的允许配置单个bean,或者更强大的,定义一个可选的配置语法,这样更贴近问题域并且对用户隐藏背后的复杂性。一个简单的元素可以隐藏多个bean和添加到应用程序上下文的多个处理步骤。例如:从安全命名空间添加后面的元素到应用程序上下文将开始一个LDAP服务到应用程序内用于置一个Apache木服务器bean简单的多。最常见的替代配置需求是ldap-server元素的属性支持,用户不用担心他们要创建的bean属名称:[你可以从:specialcharacters,macros[LDAP 身份验证]找到如何使用ldap-server元素的更多的信息. ] 。当编译应用的Context文件时良好的XML编译器应该可以提供您可用的属性和元素的信息。我们建议你尝试使用 Spring Tool Suite,它具有与标准Spring命名空间工作的特别的功能。
为了在你的应用程序上下文中使用安全命名空间,你需要将spring-security-config包含到你的classpath中。然后在你的上下文文件中加入以下的结构声明:

"http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security.xsd">
    ...

在很多示例(包括示例应用程序)中你将会看到,我们经常使用security作为默认的命名空间而不是使用beans,这样我们可以在所有安全命名空间中忽略前缀,使得内容更加容易阅读。如果你的应用程序上下文被分割成单独的文件,大部分的安全配置被放到一个文件中,你可能也想这样做。你的安全应用上下文文件应该像下面的一样:

"http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security.xsd">
    ...

我们假设在这一章节我们都使用这种语法。
命名空间的设计
命名空间是设计用来捕捉框架最常见用途和提供一个简化和简介的语法用来在应用程序中打开他们。设计师基于框架中的大规模依赖,并且可以划分为以下几个方面:
● Web/HTTP 安全 - 最复杂的部分,设置过滤器和应用框架验证机制的相关服务bean,渲染登录和错误页面等等。
● 业务对象(方法)安全 - 业务层安全选项。
● AuthenticationManager - 处理来自框架其他部分的认证请求。
● AccessDecisionManager - 为网页和方法安全提供访问决策,会注册一个默认的但是你可以使用一个定制的来取代他,使用一般的Spring bean语法即可定义。
● AuthenticationProvider- 认证管理器认证用户的机制,命名空间提供多种标准选项的支持,同时使用传统语法添加自定义bean的方法。
● UserDetailsService - 和AuthenticationProviders密切相关,但往往也被其他bean需要。
我们将在后续章节查看怎么配置他们。
开始使用安全命名空间配置
在这一章,我们将看看怎么创建一个命名空间配置来使用框架的主要功能。让我们假设你想要快速的使用命名空间配置添加验证支持和访问控制和一些测试的登录到一个已经存在的网站应用程序。然后我们看看如何验证数据库和其他的安全仓库。在后续章节我们将介绍更多高级的命名空间配置选项。
web.xml 配置
你需要做的第一件事情是添加下面的过滤器定义到你的web.xml文件:

<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>

<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
/*
filter-mapping>

这提供了一个钩子到Spring Security的网页基础设施。DelegatingFilterProxy是一个委托了在应用的Context中定义为bean的过滤器实现的一个Spring框架类,在这种情况下,bean的名字是springSecurityFilterChain,这是网络安全命名空间创建的一个基础类。注意你不能自己使用这个bean名字。一旦你添加这个到你的web.xml,你就可以开始编辑你的应用程序上下文文件,网络安全服务被配置到元素。
最小配置
开始开启网页安全你只需要:

<http>
<intercept-url pattern="/**" access="hasRole('USER')" />
<form-login />
<logout />
http>

这表示,我们想要对我们应用程序中的所有URL进行性检测。需要角色ROLE_USER访问他们,我们使用一个包含username和password的表单登录到应用程序,我们希望有一个注销的URL,让我们可以登出应用程序。元素是所有网站相关的命名空间功能的父元类。元素定义了一个规则使用ant语法去匹配传入的请求的URL。 (在网站应用基础设施章节,查看 Request Matching and HttpFirewall来了解匹配是如何执行的) 你也可以环衬正则表达式语法(查看附录了解更多)。这个access属性定义了这个匹配的规则需要的访问需求。默认是一个典型的用逗号分隔的角色列表,有其中一个符合则允许执行这个请求。 前缀”ROLE_” 是一个标记,只是一个和用户的授权进行简单的比较。换句话说,一个简单的基于角色的检查会被应用。Spring Security的访问控制不仅限于简单角色(因此使用前缀来区分不同类型的)。
你可以使用多个元素来为不同的URL定于不同的访问需求,他是他们将按照顺序计算,第一个匹配的会被使用,说以你必须将最特别的匹配放到最上面,你也可以添加一个method属性 用来限制HTTP方法(GET, POST, PUT 等等).
为了添加一些用户,你可以直接在命名空间直接定义一组测试数据。

<authentication-manager>
<authentication-provider>
    <user-service>
    <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
    <user name="bob" password="bobspassword" authorities="ROLE_USER" />
    user-service>
authentication-provider>
authentication-manager>

如果你熟悉框架的预命名空间版本,你很可能已经猜到这里怎么回事了。元素负责创建FilterChainProxy和它使用的过滤器bean。像不正确过滤排序常见的问题是不再是一个问题,因为过滤器的位置都是预定义的。
元素创建DaoAuthenticationProvider bean并且元素创建了一个InMemoryDaoImpl。所有的验证供应商元素必须是元素的子元素。他创建一个ProviderManager并注册为验证供应商。你可以在 namespace appendix找到更多信息。如果你想了解框架的重要的一些类了解他们如何使用特别是后面你想定制一些事情,你值得去查看一下。
上面的配置定义了两个用户,他们的密码和在这个应用程序里的角色(这将用于访问控制)。也可以使用用户服务从标准的属性文件properties载入用户信息user-service。查看内存认证部分文件格式的更多细节in-memory authentication 。使用 元素意思是这些用户信息将被认证管理器使用到请求验证。你可以设置多个元素来定义不同的验证源每一个都会被一次访问到。
此时你应该可以开始你的应用程序,你将被要求登录。尝试一下或者尝试使用附带的项目教程实例。
表单和基本登录选项
你可能会想当系统提示你登录这些登录表单哪里来的,因为我们都没有提供任何HTML和JSP文件。事实上我们并没有明确的设定一个登陆页的URL,Spring Security自动生成了一个,基于处理登录的URL标准值。以及登录后跳转的URL然后命名空间提供了大量的支持,让你可以自定义这些选项,例如如果你想自己设计登录页面,你可以使用:

<http>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
http>

另外请注意一个额外的intercept-url元素,来指定登录页的请求可以被任何匿名用户访问:[AuthenticatedVoter 类可以提供更多 匿名身份验证] AuthenticatedVoter IS_AUTHENTICATED_ANONYMOUSLY被处理的细节]. 否则请求会被/**规则匹配这将是不可能访问到登录页面。这是一个常见的配置错误,将导致应用无限循环。Spring Security会产生一条经过到日志中如果你的登录页被保护了。它也可以设置让所有请求完全匹配特定模式而绕过安全过滤器,通过定义像这样单独http元素的规则:

<http pattern="/css/**" security="none"/>
<http pattern="/login.jsp*" security="none"/>

<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
http>

从Spring Security 3.1开始允许使用多个http元素来为不用的请求规则分别定义Security Filter Chain配置。如果http元素的pattern属性为设置则匹配所有请求。创建不进行安全检查的规则是这个语法的一个简答的例子,这个规则会被映射到一个空的过滤链。我们将会在后面的安全过滤链章节找到更多细节。[多个元素的使用时一个非常重要的功能,例如它允许命名空间在一个应用程序中同时支持有状态和无状态的相同URL之前的语法,使用filters=”none”属性到一个intercept-url元素与这一变化相抵触,在3.1将不再支持 。
重要的一点需要认识到,这些不安全的请求将完全无视任何Spring Security 的web相关的配置或附加属性,例如requires-channel通道的限定,所以在请求期间你将无法访问当前用户信息,或调用需要进行安全验证的方法。如果你想让安全过滤链被应用另外也可以使用 access=’IS_AUTHENTICATED_ANONYMOUSLY’。
如果你想使用基本验证来替换掉表单的登录,你可以将配置修改如下:

<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<http-basic />
http>

当用户访问被保护的资源时,基本身份验证将优先弹出提示框来进行登录。在这总配置如果想使用基于表单的登录,他仍然有效。例如,将登录嵌入另外一个网页。
设置默认的登录后地址
如果不是由试图访问受保护的资源促成的登录,默认目标URL就会起作用。这是一个当用户成功登录后会被带去的URL默认是”/”,你也可以通过配置default-target-target为true让用户一直跳转到这个地址而无论用户是按需还是明确的选择登录。这在你的应用程序需要用户一直从主页开始时很有用。例如:

<http pattern="/login.htm*" security="none"/>
<http use-expressions="false">
<intercept-url pattern='/**' access='ROLE_USER' />
<form-login login-page='/login.htm' default-target-url='/home.htm'
        always-use-default-target='true' />
</http>

为了对跳转目标进行更多控制,你可以使用authentication-success-handler-ref 来替代default-target-url。这个bean的引用应该是AuthenticationSuccessHandler的一个实例。你可以在 Core Filters 章节和命名空间附录找到如何定制认证失败的流程的更多信息。
处理登出
logout元素,增加了一个登出导航到一个特定URL的支持,默认的登出URL是 /logout。但是你可以使用logout-url属性指定为其他的URL。更多的属性你可以从附录找到。
使用其他的验证供应商
在实践中,你将需要将一些名字添加到应用程序上下文文件中更具扩展性的数据源。你有可能想把用户信息存放在注入数据库或者LDAP服务器中。LDAP命名空间的配置在LDAP chapter章节,这里就不说了。,如果你的应用程序上下文中有一个Spring Security的 UserDetailsService的实现叫做”myUserDetailsService”,你可以这样使用:

<authentication-manager>
    <authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

如果你想用一个数据库,你可以这样用:

<authentication-manager>
<authentication-provider>
    <jdbc-user-service data-source-ref="securityDataSource"/>
</authentication-provider>
</authentication-manager>

在应用程序上下文中,”securityDataSource”是DataSource bean的名字,指明一个数据库包含标准的 Spring Security user data tables. 另外, 你可以配置一个Spring Security JdbcDaoImpl bean 并且指明它使用user-service-ref属性:

<authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
authentication-manager>

<beans:bean id="myUserDetailsService"
    class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
beans:bean>

你也可以像下面一样使用标准的AuthenticationProvider

<authentication-manager>
    <authentication-provider ref='myAuthenticationProvider'/>
authentication-manager>

myAuthenticationProvider是应用程序上下文的一个bean名字 。他实现了AuthenticationProvider接口。 你可以设置多个验证供应商,这样这些供应商就会按照定义的顺序被查询。看看 验证管理器和命名空间 章节“验证管理器和命名空间”连接AuthenticationManager在命名空间中怎么被配置。
添加密码编码器
密码应该一直使用一个尽可能安全的哈希算法进行编码(非标准的算法,例如SHA或者MD5)。这在中进行支持,使用bcrypt编码密码,原始的供应商配置应该类似这样:

<beans:bean name="bcryptEncoder"
    class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<authentication-manager>
<authentication-provider>
    <password-encoder ref="bcryptEncoder"/>
    <user-service>
    <user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f"
            authorities="ROLE_USER, ROLE_ADMIN" />
    <user name="bob" password="4e7421b1b8765d8f9406d87e7cc6aa784c4ab97f"
            authorities="ROLE_USER" />
    user-service>
authentication-provider>
authentication-manager>

Bcrypt 在大部分时候是一个好的选择。除非你有一个旧系统强迫你使用一个不用的算法。如果你正在使用一个简单的哈希算法或者更糟糕存储了明文密码,你应该考虑迁移到bcrypt这个更安全的选项。
高级web功能
Remember-Me 验证
查看独立的Remember-Me chapter章节来查看该功能的配置。
添加 HTTP/HTTPS 通道安全
如果你的应用程序同时支持HTTP和HTTPS,你要求特定的URL只能使用HTTPS,这是可以直接使用的requires-channel属性:


"/secure/**" access="ROLE_USER" requires-channel="https"/>
"/**" access="ROLE_USER" requires-channel="any"/>
...

有了这个配置,如果用户试图访问任何匹配的”/secure/**”模式使用HTTP,他们将首先被重定向到一个HTTPS URL脚注:[想要了解channel-processing 如何实现,请查阅java文档ChannelProcessingFilter以及相关的类]。可用的选项是“http”、“https”或“任何”。使用价值“任何”意味着可以使用HTTP或HTTPS。
该选项可选值为”http”,”https”或者”any”,使用”any”意思是使用HTTP或者HTTPS均可。如果应用程序使用HTTP和HTTPS非标准的端口,你可以指定如下端口映射的列表:


...

    "9080" https="9443"/>

请注意,为了真正的安全,应用程序不应该使用HTTP或HTTP和HTTPS之间切换。它应该开始在HTTPS(与用户进入一个HTTPS URL)使用安全连接,以避免任何中间人攻击的可能性。
Session 管理
检测超时
你可以配置Spring Security检测无效的Session ID提交并且将用户重定向到一个适当的URL,这通过session-management元素来达到:


...
"/invalidSession.htm" />

请注意,如果你使用这个机制来检测会话超时,如果用户注销,然后重新登录,但不关闭浏览器它可能会错误地抛出一个错误。这是因为当你让Session失效时,Cookie没有被清理干净,就算用户已注销还是会重新提交session的cookie而不会清除。你可以在注销时明确的删除JSESSIONID的cookie,例如:通过在注销处理程序使用一下语法:

<http>
<logout delete-cookies="JSESSIONID" />
http>

不幸的是这个不保证在所有servlet容器都会正常工作,所以你需要在你的环境下进行测试。
如果你的应用程序运行在代理后面,你还可以配置代理服务器去删除session的cookie。例如,使用Apache httpd的mod_headers模块。下面的指令可以通过在注销请求的响应头删除JSESSIONID的cookie。(假设应用程序部署在 /tutorial路径下):

  <LocationMatch "/tutorial/logout"> 
  Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT" 
  </LocationMatch>

并发Session控制
如果你想限制单个用户访问你的应用程序的能力。Spring Security通过后面简单的配置马上启用。首先你需要添加后面的监听器到你的web.xml文件。让Spring Security获得session的生存事件:



    org.springframework.security.web.session.HttpSessionEventPublisher

然后添加后面的行到你的应用程序上下文:


...

    "1" />

这将放置用户登录多次。第二次登录将导致第一次登录变成无效。通常我们更想放置第二次登录,在这种情况下,你可以使用:


...

    "1" error-if-maximum-exceeded="true" />

第二次登录将被拒绝,如果基于表单的验证被启用这个用户将会被发送到 authentication-failure-url。如果第二次登录是通过其他非交互的机制,比如“记住我”功能,进行登录的。那么一个“unauthorized”(401)错误将会被发送给客户端。如果你想替换成一个错误页面,你可以为session-management添加一个session-authentication-error-url属性。
如果你正在使用基于表单的登录定制验证过滤器,那么你必须明确地配置同步会话控制的支持。更多的细节可以在 Session Management chapter章节中找到。
Session Fixation 攻击保护
Session fixation完成攻击是一个潜在的威胁。攻击者访问网站生成一个Session,然后诱使其他用户用同一个会话登录(例如:通过发送包含会话标识符作为一个参数链接)。Spring Security通过在登录时创建新的Session或者修改Session ID来应对这种情况。如果你不需要这个保护或者与一些其他需求冲入你可以通过 的session-fixation-protection属性来控制这个行为。它有4个选项:
● none - 什么都不做,原来的会话将会保留
● newSession - 创建一个新的干净的Session,不会复制已经存在的Session属性到新的Session,这是Servlet3.0及之前的容器的默认设置
● migrateSession - 创建一个新的Session并且拷贝所有已经存在的Session属性到新的Session,这是Servlet3.0及之前的容器的默认设置。
● changeSessionId - 不创建新的Session,使用Servlet容器提供的Session完成攻击保护(HttpServletRequest#changeSessionId())。这个选项只有在Servlet3.1(java EE 7)和更新的容器下可用。在旧的容器设置这个选项会产生一个异常。在Servlet3.0和更新的容器就默认该选项。
当会话完成保护发生时,它会产生SessionFixationProtectionEvent发布到应用程序上下文,如果使用changeSessionId, 这种保护也将导致 任何javax.servlet.http.HttpSessionIdListener被通知,所以如果你的代码监听这两个事件要特别小心。查看Session Management 管理章节查看更多信息。
OpenID 支持
通过一个简单的改变,命名空间可支持用 OpenID 替换或者添加到普通的基于表单登录:

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

然后,你应该将自己注册为一个OpenID供应商(如myopenid.com),并添加用户信息到你的基于内存的:

<user name="http://jimi.hendrix.myopenid.com/" authorities="ROLE_USER" />

你应该能够使用myopenid.com网站登录来进行验证。另外,也可以通过设置 openid-login元素的user-service-ref属性来指定一个UserDetailsService bean来使用OpenID。查看前面的authentication providers章节了解更多信息请注意,我们省略从上述用户配置中的密码属性,由于该组的用户数据只被用于加载当前的用户。内部会产生一个随机密码,放置你意外的将这个用户数据用作验证源到你的配置中的其他地方。
属性交换
OpenID 的属性交换支持 attribute exchange。下面的例子尝试接受从OpenID供应商接收邮件和全名,用于应用程序中:

<openid-login>
<attribute-exchange>
    <openid-attribute name="email" type="http://axschema.org/contact/email" required="true"/>
    <openid-attribute name="name" type="http://axschema.org/namePerson"/>
</attribute-exchange>
</openid-login>

每个OpenID属性的“type”是一个URI,有一种特定的模式来确定,这个例子中是 http://axschema.org/。如果属性必须在成功认证后接收,可以设置required 属性。确切的模式和属性的支持将取决于你的OpenID提供商。该属性值返回作为认证过程的一部分,随后可以使用下面的代码访问:

OpenIDAuthenticationToken token =
(OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
List attributes = token.getAttributes();

OpenIDAttribute包含了属性类型和接收到的值(在有多个属性值的情况下包含多个值),通过查看Spring Security核心组件的technical overview章节我们可以了解更多的SecurityContextHolder类的使用方法。如果希望使用多个身份提供者方,多重属性交换配置也被支持。你可以提供多个attribute-exchange元素。在每个上面使用identifier-matcher属性。它包含一个正则表达式,会匹配由用户提供的OpenID标识符。查看代码库的OpenID示例应用的一个示例配置。对 Google, Yahoo 和MyOpenID 提供了不同的属性列表。
相应头
查看如何定制头元素的更多信息请查看安全HTTP响应头章节。
添加自己的过滤器
如果你以前使用过Spring Security,你就会知道,这个 框架维护一个过滤器链,以便应用它的服务。你可能想要添加自己的过滤器到过滤器堆栈的特定位置,或者使用一个Spring Security还没有一个命名空间配置的选项的过滤器(比如CAS)。或者你想使用一个标准命名空间过滤器的定制化版本,比如UsernamePasswordAuthenticationFilter是由 元素显式的使用Bean来获取一些额外的高级配置选项,在过滤器链不直接暴露的情况下,你怎么使用命名空间配置这一点?
使用命名空间时过滤器的顺序始终严格执行,当创建应用程序上下文,过滤器Bean被命名空间处理代码进行排序,标准的Spring Security过滤器都具有的命名空间和一个众所周知的位置的别名。
在以前的版本中,排序发生在过滤器实例创建之后,在应用程序上下文后处理中。语法在3.0有一些轻微的改变,这会影响到解析元素时,你自己的过滤器如何被添加到整个过滤器列表中
标准过滤器别名和顺序,别名和创建的命名空间元素/属性在下表列出,按照过滤器在链中出现的顺序列出:

AliasFilter ClassNamespace Element or
AttributeCHANNEL_FILTERChannelProcessingFilterhttp/intercept-url@requires-channelSECURITY_CONTEXT_FILTERSecurityContextPersistenceFilterhttpCONCURRENT_SESSION_FILTERConcurrentSessionFiltersession-management/concurrency-controlHEADERS_FILTERHeaderWriterFilterhttp/headersCSRF_FILTERCsrfFilterhttp/csrfLOGOUT_FILTERLogoutFilterhttp/logoutX509_FILTERX509AuthenticationFilterhttp/x509PRE_AUTH_FILTERAbstractPreAuthenticatedProcessingFilter
SubclassesN/ACAS_FILTERCasAuthenticationFilterN/AFORM_LOGIN_FILTERUsernamePasswordAuthenticationFilterhttp/form-loginBASIC_AUTH_FILTERBasicAuthenticationFilterhttp/http-basicSERVLET_API_SUPPORT_FILTERSecurityContextHolderAwareRequestFilterhttp/@servlet-api-provisionJAAS_API_SUPPORT_FILTERJaasApiIntegrationFilterhttp/@jaas-api-provisionREMEMBER_ME_FILTERRememberMeAuthenticationFilterhttp/remember-meANONYMOUS_FILTERAnonymousAuthenticationFilterhttp/anonymousSESSION_MANAGEMENT_FILTERSessionManagementFiltersession-managementEXCEPTION_TRANSLATION_FILTERExceptionTranslationFilterhttpFILTER_SECURITY_INTERCEPTORFilterSecurityInterceptorhttpSWITCH_USER_FILTERSwitchUserFilterN/A

你可以添加自己的过滤器到列表中,使用custom-filter过滤元件和这些名字来指定你的过滤器应该出现在的位置之一:

<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
http>
id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>

你也可以使用after和before属性来让你的过滤器插入到列表中的其他过滤器的前面和后面。FIRST和LAST可以用在position属性来设置你希望将你的过滤器插入到整个列表的前面或者后面。
Avoiding filter position conflicts
如果你插入的定制的过滤器可能会占用由命名空间创建的标准过滤器相同的位置,很重要的一点是不要错误的包含了命名空间的版本。移除所有你想替换的功能的过滤器元素。注意,你不能移除有本身创建的过滤器,SecurityContextPersistenceFilter, ExceptionTranslationFilter 和 FilterSecurityInterceptor,一些其他的过滤器是默认被添加的,但是你可以禁止他们。除非你禁用session-fixation protection ,一个AnonymousAuthenticationFilter 会默认被添加,一个SessionManagementFilter也会被添加到过滤器链。
如果你替换的命名空间过滤器需要一个验证入口点(例如:在认证过程是通过尝试触发未认证用户访问以受保护资源)你也需要添加一个定制的入口点Bean。
创建自定义的 AuthenticationEntryPoint
如果你没有使用表单登录,OpenID或基本验证,你可能想像我们之前看到的一样,使用传统的bean语法,定义一个验证过滤器和入口点链接到命名空间。相应的AuthenticationEntryPoint可以使用元素的entry-point-ref属性进行设置。
CAS示例应用是一个很好的示例,展示命名空间的定制bean的使用。如果你不熟悉认证的入口点,可以在technical overview章节看到相关讨论。
方法安全
从2.0版本开始Spring Security改进了对服务层方法的安全支持,它提供了对JSR-250注解安全支持以及框架的原生@Secured注解支持。从3.0开始你也可以使用新的expression-based annotations。你可以将安全应用到单个bean。使用intercept-methods元素装饰Bean的声明。或者你可以在使用AspectJ风格的切入点应用安全到整个服务层的多个Bean类。
The 元素
这个元素用来在你的应用程序中启用基于安全性(通过设置元素的appropriate属性),同时分组将应用到整个应用程序上下文的安全切入点声明。你应该只定义一个元素。下面的定义可以开启Spring Security的@Secured支持:

<global-method-security secured-annotations="enabled" />

添加一个注解到类或者接口的方法中可以限制对相应方法的访问。Spring Security的原生注解支持定义了一套用于该方法的属性。这些将被传递到AccessDecisionManager用来做实际的决定:

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

启用 JSR-250 注解使用

<global-method-security jsr250-annotations="enabled" />

这些都是基于标准的,并允许应用简单的基于角色的约束,但是没有Spring Security的原生注解强大。要使用新的基于表达式的语法,你可以使用

<global-method-security pre-post-annotations="enabled" />

等价的Java代码如下

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

基于表达式的注解是一个很好的选择,如果你需要定义超过一个检查当前用户列表中的角色名称的简单的规则。
被注解的方法将仅在被定义为Spring 的Bean的实例时才能确保安全(在相同的应用程序的上下文中该方法-启用方法安全检查)。如果你想确保非Sprign创建的实例的安全性(比如使用new操作符创建的)那么你需要使用 AspectJ.
你可以在同一个应用程序中启用多种注解,但是在一个借口或者类中只能使用一种类型的注解,否则会出现不明确的行为。如果对特定的方法使用了两个注解,只有其中的一个会被应用。
使用 protect-pointcut
使用protect-pointcut非常有用, 因为它允许你只用简单的声明就能对很多bean添加安全性。看看下面的例子:

<global-method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"
    access="ROLE_USER"/>
global-method-security>

这将保护在应用程序上下文定义的所有在com.mycompany包下类名以“service”结尾的类的方法。只有拥有ROLE_USER角色用户才能执行这些方法。和URL匹配一样,列表中多个匹配的话将会使用第一个匹配的安全注解,比切入点有更高的优点级。
默认的 AccessDecisionManager
这一章节假设你有一些Spring Security中访问控制的底层架构的知识。如果没有,你可以跳过它,后面再来看,这部分针对那些真正需要进行一些定制而不是简单的基于角色的安全用户。
当你使用命名空间配置时,一个AccessDecisionManager实例将会自动创建并注册用来按照你在intercept-url和protect-pointcut(还有如果你使用了方法注解安全也包含在内)定义的访问属性进行访问决策。
默认的策略是使用一个AffirmativeBased、AccessDecisionManager和RoleVoter“AuthenticatedVoter,你也可以从authorization章节找到更多信息.
自定义 AccessDecisionManager
如果你需要使用一个更复杂的访问控制策略,那么很容易的为方法和Web安全设置替代方案。
对于方法安全,通过在access-decision-manager-ref上设置global-method-security 属性来为应用程序指导适当的 AccessDecisionManager的Bean ID。

"myAccessDecisionManagerBean">
...

网站安全的语法一样,但是是在http元素上。

"myAccessDecisionManagerBean">
...

验证管理器和命名空间
Spring Security中主要的提高验证服务的借口是AuthenticationManager,这通常是一个Spring Security的ProviderManager类的实例。如果你以前用过框架你可能已经熟悉了。如果不是后面的 technical overview chapter章节会讲到。这个bean是通过authentication-manager命名空间来注册。你不能使用自定义的AuthenticationManager如果通过命名空间使用HTTP或方法安全,但是这不应该是一个问题,因为你可以完全控制所使用的好的AuthenticationProvider。
你可能需要使用ProviderManager注册其他的AuthenticationProvider Bean,你可以使用元素的ref属性,属性的值是你要添加的bean的名字,例如:


"casAuthenticationProvider"/>


"casAuthenticationProvider"
    class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...

另一个常见的需求是,上下文中的另一个bean可能需要引用AuthenticationManager,你可以注册一个AuthenticationManager的别名在应用程序上下文中的其他地方使用这个名字。

"authenticationManager">
...


"customizedFormLoginFilter"
    class="com.somecompany.security.web.CustomFormLoginFilter">
"authenticationManager" ref="authenticationManager"/>
...

应用程序示例
这里有几个可用的网站应用程序示例。为了避免大量的下载。只有”totorial”“contacts”示例包含到了分发的zip文件。其他的可用按照the introduction从源代码构建。你可以很容易的自己构建项,通过 http://spring.io/spring-security/网站可以获取更多信息。本章中提到的所有路径是相对于项目的源目录。
Tutorial 示例
本教程样本是一个让你入门的很好的基础的例子。它完全使用而来简单命名空间配置。编译好的应用程序包含在分发的zip包中。随时可以部署到你的Web容器(spring-security-samples-tutorial-3.1.x.war)。 form-based的认证机制结合remember-me 功能,验证供应商提供使用cookie自动基础登录。
我们建议你从本示例开始,因为XML非常小,易于遵循。最重要的是,你可以把这个XML文件(和它对应的web.xml入口)轻松地添加到现有的应用程序中。在这个基本集成成功的时候,我们建议你试着添加方法验证和领域对象安全。
Contacts 示例
该示例是一个高级的例子,它展示除了基本的应用程序安全领域对象的访问控制列表(ACL)的更强大的功能。本申请提供了一个接口让用户能够管理简单的联系人数据库(域对象)。
拷贝WAR问价你从Spring Security 分发包到你自己的容器的webapps 目录来部署它。war名字是spring-security-samples-contacts-3.1.x.war(扩展的版本号取决于你的版本号)。
开始你的容器后检查应用程序是否可用载入,访问 http://localhost:8080/contacts (或者其他适合你的容器URL)。
接下来,单击”Debug”。系统将提示你进行身份验证,以及页面提示的一系列用户名和密码。简单验证,显示一个结果页面。它应该包含类似于一下成功信息:

Security Debug Information

Authentication object is of type:
org.springframework.security.authentication.UsernamePasswordAuthenticationToken

Authentication object as a String:

org.springframework.security.authentication.UsernamePasswordAuthenticationToken@1f127853:
Principal: org.springframework.security.core.userdetails.User@b07ed00:
Username: rod; \ Password: [PROTECTED]; Enabled: true;
AccountNonExpired: true; credentialsNonExpired: true;
AccountNonLocked: true; \ Granted Authorities: ROLE_SUPERVISOR,
ROLE_USER; \ Password: [PROTECTED]; Authenticated: true; \ Details:
org.springframework.security.web.authentication.WebAuthenticationDetails@0:
\ RemoteIpAddress: 127.0.0.1; SessionId: 8fkp8t83ohar; \ Granted
Authorities: ROLE_SUPERVISOR, ROLE_USER

Authentication object holds the following granted authorities:

ROLE_SUPERVISOR (getAuthority(): ROLE_SUPERVISOR) ROLE_USER
(getAuthority(): ROLE_USER)

Success! Your web filters appear to be properly configured!

一旦你成功地收到上述消息,返回到示例应用程序的主页,点击”Manage”。然后,你可以使用这个应用程序。注意只有当前用户的联系人会被显示,并且只有拥有ROLE_SUPERVISOR可用授权去删除联系人,在幕后MethodSecurityInterceptor保护业务对象。
这应用程序允许你修改与不同的联系人相关联的访问控制列表。一定要试试这个,并了解它是如何工作通过检查应用程序上下文XML文件。
LDAP 示例
LDAP例子程序提供了一个基本的配置和使用命名空间和使用传统的bean同等设置在同一个应用程序上下文文件。这意味着,其实是在这个应用程序中配置了两个相同的身份验证提供者。
OpenID 示例
OpenID的示例演示了如何使用命名空间来配置OpenID和如何设置为Google,Yahoo和OpenID设置 attribute exchange配置(如果你愿意的话,也可以添加其他的供应商)。它使用一个基于JQery的 openid-selector,以提供用户友好的登录页面,它允许用户很容易的选择一个验证提供者,而不是输入OpenID标识符。
应用程序的不同之处在于它允许任何用户访问该站点认证情景(只要他们的OpenID身份验证是成功的)。当你第一次登录时,你会得到一个”Welcome [your name]”的消息。如果你注销并重新登录(以相同的身份),那么这应更改为”欢迎回来”,这是通过使用一个实现自定义的UserDetailsService它分配一个标准角色给任何用户,并在内部存储身份的map。显然,一个真正的应用程序将使用一个数据库替换它。看一看源代码了解更多信息,此类还考虑到,不同供应商返回不同属性,并建立相应用户名。
CAS 示例
CAS示例要求你同时运行CAS服务器和CAS客户端。 它不包含在发行包,所以你应该按照 the introduction描述签出目标代码。你会发现sample/cas目录下的相关文件。还有在那里一个Readme.txt文件,这也解释了如何直接从源代码运行服务器和客户端,完全支持SSL。
JAAS 示例
该JAAS是很简单的如何用Spring Security 使用JAAS LoginModule 的例子。所提供的LoginModule将成功地验证用户,如果用户名和密码相等,否则抛出一个LoginException异常。在本示例中使用的AuthorityGranter总是授予角色ROLEUSER。示例应用程序还演示了如何通过设置LoginModule的jaas-api-provision等于true返回JAAS Subjec来运行。
Pre-Authentication 示例
此示例应用程序演示了如何绑定bean从pre-authentication 框架从Java EE容器使用登录信息。用户名和角色是由容器设置的。
代码是samples/preauth.
Spring Security 社区
问题跟踪
Spring Security 使用JIRA来管理bug报告和改进请求,如果你发现错误,请使用JIRA记录报告。不要在支持论坛,邮件列表,或通过电子邮件项目的记录。这些方法是临时的,我们更喜欢使用正式的流程。
如果可能的话,在你的问题的报告摸清提供一个JUnit测试,演示任何不正确的行为。或者,更好的是,提供了一个可以解决这个问题的补丁。同样,非常欢迎的提出改进需求,虽然我们只接受、有对应的单元测试的改进请求。这是必要的,以确保保持项目的测试覆盖率。
问题跟踪系统你可以通过这里访问: https://github.com/spring-projects/spring-security/issues.
成为参与者
我们欢迎你参与Spring Security项目。有贡献的方法很多,包括阅读论坛,并响应来自其他人的问题,编写新的代码,改进现有的代码,协助文档,开发样品或教程,或则干脆提出建议。
更多信息
欢迎为Spring Security 提出问题和意见。你可以使用Spring社区 论坛网站http://spring.io/questions[http://spring.io/questions] 和其他Spring Security框架的用户进行讨论。请记住,正如前面说的使用JIRA提交bug报告。
架构与实现
一旦你熟悉了设置和运行一些基于命名空间配置的应用程序,你可能希望开发更多的框架去理解命名空间门面后面实际上是如何运转。类似大部分软件, Spring Security有一定的中央接口,以及通常在整个框架中使用的概念抽象类。在参考指南的这一部分,我们将看看其中的一些,看看它们如何协同工作去支持Spring Security中的身份验证和访问控制。
技术概述
运行环境
Spring Security 3.0需要Java 5.0的运行环境或者更高的版本. 由于 Spring Security 是以独立的方式运作, 就不需要什么特殊的配置文件到你的Java运行环境。特别是, 不需要配置专门的Java认证和授权服务(JAAS)策略文件或将Spring Security的位置放到普通路径中。
同样,如果你使用的是EJB容器或者Servlet容器也没有必要把任何特殊的配置文件放到任何地方,也不包括Spring Security的服务器类加载器。所有必须的文件都将包含在你的应用程序中。
这种设计给部署时间提供了最大的灵活性,你可以简单的复制你的目标文件(可以是JAR, WAR或者EAR)从一个系统到另一个系统,它会立即开始工作。
核心组件
在Spring Security 3.0的版本中,spring-security-core中的内容被精简到了最低限度。它不再包含web应用安全,LDAP或命名空间配置的任何代码。我们来看看一些Java类型,你会在核心模块中找到。它们代表了框架的基石,所有如果你需要越过一个简单的命名空间配置,那么最重要的是你要明白它们是什么,即使你不需要直接与它们进行交互。
SecurityContextHolder, SecurityContext和Authentication 对象
最根本的对象是SecurityContextHolder。我们把当前应用程序的当前安全环境的细节存储到它里边了, 它也包含了应用当前使用的主体细节。默认情况下SecurityContextHolder使用ThreadLocal存储这些信息, 这意味着,安全环境在同一个线程执行的方法一直是有效的, 即使这个安全环境没有作为一个方法参数传递到那些方法里。这种情况下使用ThreadLocal是非常安全的,只要记得在处理完当前主体的请求以后,把这个线程清除就行了。当然,Spring Security自动帮你管理这一切了, 你就不用担心什么了。
有些程序并不适合使用ThreadLocal,因为它们处理线程的特殊方法。比如Swing客户端也许希望Java Virtual Machine里所有的线程 都使用同一个安全环境。SecurityContextHolder可以配置启动策略来指定你希望上下文怎么被存储。对于一个独立的应用程序,你会使用SecurityContextHolder.MODE_GLOBAL策略。其他程序可能也想由安全线程产生的线程也承担同样的安全标识。这是通过使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL实现。你可以通过两种方式更改默认的SecurityContextHolder.MODE_THREADLOCAL模式。第一个是设置系统属性,第二个是调用SecurityContextHolder的静态方法。大多数应用程序不需要修改默认值,但是如果你想要修改,可以看一下SecurityContextHolder的JavaDocs中的详细信息了解更多。
当前用户获取信息
我们在SecurityContextHolder内存储目前与应用程序交互的主要细节。Spring Security使用一个Authentication对象来表示这些信息。 你通常不需要创建一个自我认证的对象,但它是很常见的用户查询的Authentication对象。你可以使用以下代码块-从你的应用程序的任何部分-获得当前身份验证的用户的名称,例如:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

通过调用getContext()返回的对象是SecurityContext接口的实例。这是保存在线程本地存储中的对象。我们将在下面看到,大多数的认证机制以Spring Security返回UserDetails实例为主。
The UserDetailsService
从上面的代码片段中还可以看出一件事,就是你可以从Authentication对象中获得安全主体。这个安全主体就是一个Object。大多数情况下,可以强制转换成UserDetails对象 。 UserDetails是一个Spring Security的核心接口。它代表一个主体,是扩展的,而且是为特定程序服务的。 想一下UserDetails章节,在你自己的用户数据库和如何把Spring Security需要的数据放到SecurityContextHolder里。为了让你自己的用户数据库起作用,我们常常把UserDetails转换成你系统提供的类,这样你就可以直接调用业务相关的方法了(比如 getEmail(), getEmployeeNumber()等等)。
现在,你可能想知道,我应该什么时候提供这个UserDetails对象呢?我怎么做呢?我想你说这个东西是声明式的,我不需要写任何代码,怎么办?简单的回答是,这里有一个特殊的接口叫UserDetailsService。这个接口里的唯一的一个方法,接收String类型的用户名参数,返回UserDetails:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
这是Spring Security用户加载信息的最常用的方法并且每当需对用户的信息时你会看到它使用的整个框架。
成功认证后,UserDetails用于构建存储在SecurityContextHolder(详见 以下)的Authentication对象。好消息是,我们提供了一些UserDetailsService的实现,包括一个使用内存映射(InMemoryDaoImpl)而另一个使用JDBC(JdbcDaoImpl)。大多数用户倾向于写自己的,常常放到已有的数据访问对象(DAO)上使用这些实现,表示他们的雇员,客户或其他企业应用中的用户。记住这个优势,无论你用UserDetailsService返回的什么数据都可以通过SecurityContextHolder获得,就像上面的代码片段讲的一样。
关于UserDetailsService常常有一些混乱。它纯粹是用于用户数据的DAO并没有其它功能,除了提供该数据到其他组件的框架内。特别是,它不会对用户进行身份验证,这是由AuthenticationManager完成。在许多情况下,如果你需要自定义身份验证过程,直接实现 AuthenticationProvider更有意义 。
GrantedAuthority
除了主体,另一个Authentication提供的重要方法是getAuthorities()。这个方法提供了GrantedAuthority对象数组。毫无疑问,GrantedAuthority是赋予到主体的权限。这些权限通常使用角色表示,比如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。这些角色会在后面,对web验证,方法验证和领域对象验证进行配置。Spring Security的其他部分用来拦截这些权限,期望他们被表现出现。GrantedAuthority对象通常是使用UserDetailsService读取的。
通常情况下,GrantedAuthority对象是应用程序范围下的授权。它们不会特意分配给一个特定的领域对象。因此,你不能设置一个GrantedAuthority,让他有权限展示编号54的Employee对象,因为如果有成千上万的这种授权,你会很快用光内存(或者,至少,导致程序花费大量时间去验证一个用户)。当然,Spring Security被明确设计成处理常见的需求,但是你最好别因为这个目的使用项目领域模型安全功能。
小结
简单回顾一下,Spring Security主要由以下几部分组成的:
● SecurityContextHolder, 提供几种访问 SecurityContext的方式。
● SecurityContext, 保存Authentication信息和请求对应的安全信息。
● Authentication, 展示Spring Security特定的主体。
● GrantedAuthority, 反应,在应用程序范围你,赋予主体的权限。
● UserDetails,通过你的应用DAO,提供必要的信息,构建Authentication对象。
● UserDetailsService, 创建一个UserDetails,传递一个 String类型的用户名(或者证书ID或其他).
现在,你应该对这种重复使用的组件有一些了解了。 让我们贴近看一下验证的过程。
验证
Spring Security可以在很多不同的认证环境下使用。虽然我们推荐人们使用Spring Security,不与已存在的容器管理认证系统结合,但它也是支持的-使用你自己的属性验证系统进行整合。
什么是Spring Security验证?
让我们考虑一个大家都很熟悉的标准的验证场景。
1. 提示用户输入用户名和密码进行登录。
2. 该系统 (成功) 验证该用户名的密码正确。
3. 获取该用户的环境信息 (他们的角色列表等).
4. 为用户建立安全的环境。
5. 用户进行,可能执行一些操作,这是潜在的保护的访问控制机制,检查所需权限,对当前的安全的环境信息的操作。
前三个项目构成的验证过程,所以我们将看看这些是如何发生在Spring Security中的。
1. 用户名和密码进行组合成一个实例UsernamePasswordAuthenticationToken (一个Authentication接口的实例, 我们之前看到的).
2. 令牌传递到AuthenticationManager实例进行验证。
3. 该AuthenticationManager完全填充Authentication实例返回成功验证。
4. 安全环境是通过调用 SecurityContextHolder.getContext().setAuthentication(…​), 传递到返回的验证对象建立的。
从这一点上来看,用户被认为是被验证的。让我们看看一些代码作为一个例子:

import org.springframework.security.authentication.*;
import org.springframework.security.core.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();

public static void main(String[] args) throws Exception {
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    while(true) {
    System.out.println("Please enter your username:");
    String name = in.readLine();
    System.out.println("Please enter your password:");
    String password = in.readLine();
    try {
        Authentication request = new UsernamePasswordAuthenticationToken(name, password);
        Authentication result = am.authenticate(request);
        SecurityContextHolder.getContext().setAuthentication(result);
        break;
    } catch(AuthenticationException e) {
        System.out.println("Authentication failed: " + e.getMessage());
    }
    }
    System.out.println("Successfully authenticated. Security context contains: " +
            SecurityContextHolder.getContext().getAuthentication());
}
}

class SampleAuthenticationManager implements AuthenticationManager {
static final List AUTHORITIES = new ArrayList();

static {
    AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}

public Authentication authenticate(Authentication auth) throws AuthenticationException {
    if (auth.getName().equals(auth.getCredentials())) {
    return new UsernamePasswordAuthenticationToken(auth.getName(),
        auth.getCredentials(), AUTHORITIES);
    }
    throw new BadCredentialsException("Bad Credentials");
}
}

在这里我们已经写了一个小程序,要求用户输入一个用户名和密码并执行上述序列。这个AuthenticationManager我们这里将验证用户的用户名和密码将其设置成一样的,它给每一个用户分配一个单一的角色。从上面输出的将是类似的东西:

Please enter your username: bob Please enter your password: password
Authentication failed: Bad Credentials Please enter your username: bob
Please enter your password: bob Successfully authenticated. Security
context contains: \
org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230:
\ Principal: bob; Password: [PROTECTED]; \ Authenticated: true;
Details: null; \ Granted Authorities: ROLE_USER

请注意,你通常不需要写任何这样的代码。这个过程通常会发生在内部,以一个web认证过滤器为例,我们刚刚在这里的代码显示,在Spring Security中究竟是什么构成了验证的问题,有一个相对简单的答案。用户验证时,SecurityContextHolder包含一个完全填充的Authentication对象的用户进行身份验证。
直接设置SecurityContextHolder的内容
事实上,Spring Security不介意你如何把Authentication对象包含在SecurityContextHolder内。唯一的关键要求是SecurityContextHolder包含Authentication在AbstractSecurityInterceptor之前(我们会看到更多的版本)需要用户授权操作。
你可以(很多用户都这样做)写一个自己的过滤器或MVC控制器来提供验证系统的交互,这些都不是基于Spring Security的。比如,你也许使用容器管理认证,从ThreadLocal或JNDI里获得当前用户信息。或者,你的公司可能有一个遗留系统,它是一个企业标准,你不能控制它。这种情况下,很容易让Spring Security工作,也能提供验证能力。你所需要的就是写一个过滤器(或等价物)从指定位置读取第三方用户信息,把它放到SecurityContextHolder里。在这种情况下,你还需要考虑的事情通常是由内置的认证基础设施自动照顾。例如,缓存请求的情况下你可能需要事先创建一个HTTP会话,在编写客户端响应之前 [1]。
如果你想知道AuthenticationManager是如何以现实世界的例子来实现,我们可以来看看 核心服务一章.
在Web应用程序中的身份验证
现在让我们来看看你在Web应用程序中使用Spring Security的情况(不启用web.xml安全性)。用户如何进行身份验证和建立安全环境?
考虑一个典型的Web应用程序的身份验证过程:
1. 你访问首页, 点击一个链接。
2. 向服务器发送一个请求,服务器判断你是否在访问一个受保护的资源。
3. 如果你还没有进行过认证,服务器发回一个响应,提示你必须进行认证。响应可能是HTTP响应代码,或者是重新定向到一个特定的web页面。
4. 依据验证机制,你的浏览器将重定向到特定的web页面,这样你可以添加表单,或者浏览器使用其他方式校验你的身份(比如,一个基本校验对话框,cookie,或者X509证书,或者其他)。
5. 浏览器会发回一个响应给服务器。 这将是HTTP POST包含你填写的表单内容,或者是HTTP头部,包含你的验证信息。
6. 下一步,服务器会判断当前的证书是否是有效的, 如果他们是有效的,下一步会执行。 如果他们是非法的,通常你的浏览器会再尝试一次(所以你返回的步骤二)。
7. 你发送的原始请求,会导致重新尝试验证过程。有希望的是,你会通过验证,得到足够的授权,访问被保护的资源。如果你有足够的权限,请求会成功。否则,你会收到一个HTTP错误代码403,意思是访问被拒绝。
Spring Security使用鲜明的类负责上面提到的每个步骤。主要的部分是(为了使用他们) ExceptionTranslationFilter, 一个 AuthenticationEntryPoint 一个验证机制, 我们在上一节看到它负责调用AuthenticationManager。
ExceptionTranslationFilter
ExceptionTranslationFilter是一个Spring Security过滤器,用来检测是否抛出了Spring Security异常。这些异常会被AbstractSecurityInterceptor抛出,它主要用来提供验证服务。我们会在下一节讨论AbstractSecurityInterceptor,但是现在,我们只需要知道,它是用来生成Java,并且要知道和HTTP没什么关系,或者如何验证一个主体。而ExceptionTranslationFilter提供这些服务,使用特点那个的响应,返回错误代码403(如果主体被验证了,但是权限不足-在上边的步骤七),或者启动一个AuthenticationEntryPoint(如果主体没有被认证,然后我们需要进入步骤三)。
AuthenticationEntryPoint
AuthenticationEntryPoint对应上面列表中的步骤三。如你所想的,每个web应用程序都有默认的验证策略(好的,这可以在Spring Security里配置一切,但是让我们现在保持简单)。每个主要验证系统会有它自己的AuthenticationEntryPoint实现, 会执行动作,如同步骤三里的描述一样。
验证机制
在你的浏览器决定提交你的认证证书之后(使用HTTP表单发送或者是HTTP头),服务器部分需要有一些东西来”收集”这些验证信息。现在我们到了上述的第六步。 在Spring Security里,我们需要一个特定的名字,来描述从用户代码(通常是浏览器)收集验证信息的功能,这个名字就是”验证机制”。实例是窗体的基本登录和基本的身份验证。一旦认证细节已从用户代理收集,建立一个Authentication “request”对象,然后提交给AuthenticationManager。
验证机制重新获得了组装好的Authentication对象时,它会认为请求有效,把Authentication放到SecurityContextHolder里的,然后导致原始请求重审(第七步)。另一方面,如果AuthenticationManager驳回了请求,验证机制会让用户代码重试(第二步)。
Storing the SecurityContext between requests
根据不同的应用程序类型,在用户操作的过程中需要有合适的策略来保存security信息。在一个典型的web应用中,一个用户登录系统之后就会被一个特有的session Id所唯一标识,服务器会将session作用期间的principal数据保存在缓存中。在Spring Security中,保存SecurityContext的任务落在了SecurityContextPersistenceFilter身上,它默认将上下文当做HttpSession属性保存在HTTP请求中,并且将每一个请求的上下文保存在SecurityContextHolder中,最重要的功能,是在请求结束之后,清理SecurityContextHolder。你不需要处于安全的目的直接和HttpSession打交道。在这里仅仅只是不需要那样做-总是使用SecurityContextHolder来代替HttpSession。
许多其他的应用(举个例子:一个无状态的RESTful风格web服务)不使用Http Session并且每次请求过来都会进行验证。然而比较重要的是:SecurityContextPersistenceFilter被包含在过滤器链中,并确保每次请求完毕之后清理SecurityContextHolder。
其中有一个应用程序接收一个会话的并发请求,同样的SecurityContext实例将线程之间共享。即使正在使用ThreadLocal,它是相同的实例,从每个线程的HttpSession检索。如果你希望暂时改变一个线程正在运行的上下文这很有意义。如果你只是使用SecurityContextHolder.getContext(),和调用setAuthentication(anAuthentication)返回的上下文对象,那么Authentication对象将在全部并发线程共享相同的SecurityContext情况的变化。 你可以自定义SecurityContextPersistenceFilter的行为,为每一个请求创建一个完全新的SecurityContext,防止在一个线程的变化影响另一个。或者,你可以创建一个新的实例,只是在这个点上,你暂时改变了背景。方法SecurityContextHolder.createEmptyContext()总是返回一个新的上下文实例。
Spring Security的访问控制(授权)
负责Spring Security访问控制决策的主要接口是AccessDecisionManager。它有一个decide方法,它需要一个Authentication对象请求访问,一个”secure object”(见下文)和安全元数据属性的列表适用的对象(如一个列表哪些角色需要被访问授权)。
安全和AOP建议
如果你熟悉AOP的话,就会知道有几种不同的拦截方式:之前,之后,抛异常和环绕。 其中环绕是非常有用的,因为advisor可以决定是否执行这个方法,是否修改返回的结果,是否抛出异常。 Spring Security为方法调用提供了一个环绕advice,就像web请求一样。 我们使用Spring的标准AOP支持制作了一个处理方法调用的环绕advice,我们使用标准Filter建立了对web请求的环绕advice。
对那些不熟悉AOP的人,需要理解的关键问题是Spring Security可以帮助你保护方法的调用,就像保护web请求一样。大多数人对保护服务层里的安全方法非常按兴趣。这是因为在目前这一代J2EE程序里,服务器放了更多业务相关的逻辑。如果你只是需要保护服务层的方法调用,Spring标准AOP平台就够了。如果你想直接保护领域对象,你会发现AspectJ非常值得考虑。
可以选择使用AspectJ还是SpringAOP处理方法验证,或者你可以选择使用filters处理web请求验证。 你可以不选,选择其中一个,选择两个,或者三个都选。主流的应用是处理一些web请求验证,再结合一些在服务层里的Spring AOP方法调用验证。
安全对象和AbstractSecurityInterceptor
那么什么是一个”安全对象”呢?Spring Security使用术语是指可以有安全性的任何对象(如授权决策)应用于它。最常见的例子就是方法调用和web请求。
Spring Security支持的每个安全对象类型都有它自己的类型,他们都是AbstractSecurityInterceptor的子类。很重要的是,如果主体是已经通过了验证,在AbstractSecurityInterceptor被调用的时候,SecurityContextHolder将会包含一个有效的Authentication。
AbstractSecurityInterceptor提供了一套一致的工作流程,来处理对安全对象的请求,通常是:
1. 查找当前请求里分配的”配置属性”。
2. 把安全对象,当前的Authentication和配置属性,提交给AccessDecisionManager来进行以此认证决定。
3. 有可能在调用的过程中,对Authentication进行修改。
4. 允许安全对象进行处理(假设访问被允许了)。
5. 在调用返回的时候执行配置的AfterInvocationManager。如果调用引发异常,AfterInvocationManager将不会被调用。
配置属性是什么?
一个”配置属性”可以看做是一个字符串,它对于AbstractSecurityInterceptor使用的类是有特殊含义的。它们由框架内接口ConfigAttribute表示。它们可能是简单的角色名称或拥有更复杂的含义,这就与AccessDecisionManager实现的先进程度有关了。AbstractSecurityInterceptor和配置在一起的 SecurityMetadataSource 用来为一个安全对象搜索属性。通常这个属性对用户是不可见的。配置属性将以注解的方式设置在受保护方法上,或者作为受保护URLs的访问属性。例如,当我们看到像命名空间中的介绍,这是说配置属性ROLE_A和ROLE_B适用于匹配Web请求的特定模式。在实践中,使用默认的AccessDecisionManager配置, 这意味着,任何人谁拥有GrantedAuthority只要符合这两个属性将被允许访问。严格来说,它们只是依赖于AccessDecisionManager实施的属性和解释。使用前缀ROLE_是一个标记,以表明这些属性是角色,应该由Spring Security的RoleVoter前缀被消耗掉。这只是使用AccessDecisionManager的选择基础。我们将在授权章看到AccessDecisionManager是如何实现的。
RunAsManager
假设AccessDecisionManager决定允许执行这个请求,AbstractSecurityInterceptor会正常执行这个请求。话虽如此,罕见情况下,用户可能需要把SecurityContext的Authentication换成另一个Authentication, 这是由AccessDecisionManager 调用RunAsManager。这也许在,有原因,不常见的情况下有用,比如服务层方法需要调用远程系统表现不同的身份。 因为Spring Security自动传播安全身份,从一个服务器到另一个(假设你使用了配置好的RMI或者HttpInvoker远程调用协议客户端),就可以用到它了。
AfterInvocationManager
按照下面安全对象执行和返回的方式-可能意味着完全的方法调用或过滤器链的执行-在AbstractSecurityInterceptor得到一个最后的机会来处理调用。这种状态下AbstractSecurityInterceptor对有可能修改返回对象感兴趣。你可能想让它发生,因为验证决定不能“关于如何在”一个安全对象调用。高可插拔性,AbstractSecurityInterceptor通过控制AfterInvocationManager在实际需要的时候修改对象。这里类实际上可能替换对象,或者抛出异常,或者什么也不做。如果调用成功后,检查调用才会执行。如果出现异常,额外的检查将被跳过。
AbstractSecurityInterceptor 和它的相关对象 Security interceptors and the “secure object” model

Figure 1. Security interceptors and the “secure object” model
扩展安全对象模型
只有当开发人员考虑一个全新的拦截方法和授权请求时才需要直接使用安全对象。例如,为了确保对消息系统的调用,它有可能建立建立一个新的安全对象。任何东西都需要安全,并且还提供了一种方法去调用(如建议语义的AOP)能够被做成一个安全对象。不得不说的是,大多数Spring应用程序将只使用三种目前完全支持的安全对象类型(AOP Alliance MethodInvocation, AspectJ JoinPoint和web请求FilterInvocation)。
Localization
Spring Security支持终端用户看到异常消息的本地化。如果你的应用程序是专为讲英语的用户设计的,你不需要做任何事情,因为默认所有的安全信息都是英文的,如果你需要支持其他地方,你需要知道的一切都被包含在这部分。
所有异常消息都可以本地化,包括有关验证失败和访问被拒绝(授权失败)的消息。这主要集中在开发者和系统发布(包括不正确的属性,接口违反合同,使用不正确的构造器,开始验证,日志调试等级)异常和日志消息没有本地化,而是使用硬编码的Spring Security的英文代码。
在spring-security-core-xx.jar的运输中你会发现一个org.springframework.security 包含了 messages.properties文件,以及一些常用版本的本地化语言。这应该是你的ApplicationContext,因为Spring Security实现了Spring的MessageSourceAware界面,希望这些消息是依赖于应用程序上下文启动的时候注入。通常你需要做的是创建你的应用程序上下文参考消息里面的bean。一个例子如下所示:

id="messageSource"
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:org/springframework/security/messages"/>

该messages.properties是按照标准的资源束命名方式,为Spring Security的消息所支持的默认语言。这个默认的文件是英文的。
如果您希望自定义messages.properties文件,或支持其他语言,您应该复制该文件,相应地重命名它,并在上面的bean定义中注释它。在这个文件中没有大量的消息密钥,因此本地化不应该被认为是一个重大举措。如果你对这个文件执行定为操作,请考虑与社区分享你的工作通过记录JIRA任务和附加被你恰当命名的messages.properties本地化版本。
Spring Security依赖于Spring”s的本地化支持,以实际查找适当的消息。为了这项工作,你必须确保从传入请求的区域存储在Spring’sorg.springframework.context.i18n.LocaleContextHolder。Spring MVC的DispatcherServlet 会自动为你的程序做,但因为Spring Security的过滤器在那之前被调用,localecontextholder需在过滤器被呼叫之前建立在包含正确的Locale里。你也可以在你自己的过滤器里面做这个(必须做完这项在Spring Security的web.xml过滤之前)或者你可以使用Spring的RequestContextFilter。请参阅Spring Framework文档,以进一步详细说明使用Spring定位。
“联系人”示例应用程序设置为使用本地化消息。
核心服务
现在,我们对Spring Security的架构和核心类进行高级别的概述,让我们在一个或两个核心接口及其实现的仔细看看,尤其是AuthenticationManager,UserDetailsService和AccessDecisionManager这些东西的信息都在这个文档的里面,所以这一点很重要,你要知道他们是如何配置如何操作的。
The AuthenticationManager, ProviderManager and AuthenticationProvider
该 AuthenticationManager只是一个接口,这样的实现可以是我们选择的任何东西,但它是如何在实践中运作的?如果我们需要检查多个授权数据库或者将不同的授权服务结合起来,类似数据库和LDAP服务器?
Spring Security的默认实现被称为ProviderManager而非处理身份验证请求本身,它委托给一个列表去配置AuthenticationProvider,其中每个查询反过来,看它是否能进行认证。每个提供程序都将抛出一个异常或返回一个完全填充的身份验证对象。还记得我们的好朋友,UserDetails和UserDetailsService吗?如果没有,回到前面的章节刷新你的记忆。到验证的认证请求的最常见的方法是加载相应UserDetails并针对已经由用户输入所述一个检查加载密码。这是由DaoAuthenticationProvider所使用的方法(见下文)。加载的UserDetails对象-尤其是GrantedAuthority的IT包含-建设是返回一个成功验证,并存储在SecurityContext完全填充Authentication对象时,将被使用。
如果你使用的命名空间,创建并在内部进行维护ProviderManager的一个实例,您可以通过使用命名空间身份验证提供元素添加提供商。(see 命名空间章节)。在这种情况下,你不应该声明在应用程序上下文中的ProviderManager bean。但是,如果你没有使用命名空间,那么你会这样声明:

id="authenticationManager"
        class="org.springframework.security.authentication.ProviderManager">
    
        <list>
            <ref local="daoAuthenticationProvider"/>
            <ref local="anonymousAuthenticationProvider"/>
            <ref local="ldapAuthenticationProvider"/>
        list>
    

在上面的例子中,我们有三个提供者。它们试图在顺序显示(它是通过使用一个List的暗示),每个提供者都能尝试验证,或者通过简单的返回null跳过认证。如果所有的实现都返回null,则ProviderManager将抛出一个ProviderNotFoundException。如果你有兴趣了解更多的有关提供者,请参考ProviderManager的JavaDocs。
身份验证机,如Web表单登录处理过滤器被注入到ProviderManager的引用,将调用它来处理自己的身份验证请求。你需要的供应商有时可以与认证机制互换,而在其他时间,他们将依赖于特定的认证机制。例如,DaoAuthenticationProvider和LdapAuthenticationProvider给它提交一个简单的用户名/密码验证请求,并因此将与基于表单登录或HTTP基本验证工作的机制兼容。另一方面,一些认证机制创建只能由单一类型AuthenticationProvider解释的认证请求对象。这一方面的一个例子是JA-SIG CAS,它使用一个服务票据的概念,因此可以仅通过一个CasAuthenticationProvider进行认证。你不必太在意这一点,因为如果你忘记注册一个合适的供应商,你会简单地收到一个ProviderNotFoundException不进行认证的尝试。
清楚成功认证的凭据
默认情况下(从Spring Security 3.1开始)的ProviderManager将试图清除它返回一个成功的认证请求的Authentication对象的任何敏感的身份验证信息。这可以防止密码等个人资料超过保留时间。
当使用用户对象的高速缓存时,例如,改善在无状态情况下应用程序的性能,这可能导致问题。如果Authentication包含在高速缓存(诸如UserDetails实例)的对象的引用中,将其凭证移除,则它将不再能够进行对缓存的值进行验证。你需要考虑到这一点,如果你使用的是高速缓存。一个显而易见的解决方案是让一个对象的副本,无论是在高速缓存中执行或在AuthenticationProvider它创建返回Authentication对象。另外,你可以在ProviderManager中禁用eraseCredentialsAfterAuthentication。查看Javadoc了解更多信息。
DaoAuthenticationProvider
Spring Security中实现最简单的AuthenticationProvider是DaoAuthenticationProvider,也是最早支持的框架。它利用了UserDetailsService(作为DAO)去查找用户名和密码。它的用户进行身份验证通过userdetailsservice加载
usernamepasswordauthenticationtoken `提交密码进行一对一的比较。配置提供程序是非常简单的:

id="daoAuthenticationProvider"
    class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="passwordEncoder" ref="passwordEncoder"/>

这个PasswordEncoder是可选的。一个PasswordEncoder提供编码以及UserDetails对象提出的密码是从配置UserDetailsService返回的解码。 这将更加详细 如下。
UserDetailsService实现
本参考指南早些时候提到的,大多数的认证供应商利用的userdetails 和 userdetailsservice 接口。回想一下,UserDetailsService是一个方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
返回的UserDetails是提供给getters的一个接口,以保证非空的认证信息,例如,用户名,密码,授权和用户帐户是否被启用或禁用。大多数认证供应商将使用UserDetailsService,即使用户名和密码不作为认证决定的一部分。他们可以使用返回的UserDetails对象为其GrantedAuthority信息对象,因为其他的一些系统(如LDAP或X.509或CAS等)承担了实际验证凭证的的责任。
鉴于UserDetailsService就是这么简单实现的,它应该便于用户检索使用自己选择的持久化策略的认证信息。话虽如此,Spring Security确实包括了许多有用的基本实现,我们将在下面看到。
在内存认证
简单的使用去创建一个自定义的UserDetailsService实现选择从一个持久性引擎中提取信息,但许多应用程序不需要这么复杂。尤其是如果你正在建设一个原型应用或刚刚开始结合Spring Security当你真的不想花时间配置数据库或写作userdetailsservice实现。对于这种情况,一个简单的选项是使用安全性 命名空间的user-service元素:

<user-service id="userDetailsService">
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
user-service>

这也支持一个外部属性文件的使用:

<user-service id="userDetailsService" properties="users.properties"/>

属性文件应包含在表单条目
username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
例如
jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled
bob=bobspassword,ROLE_USER,enabled
JdbcDaoImpl
Spring Security还包括UserDetailsService,它可以从一个JDBC数据源获得认证信息。内部Spring JDBC的使用,避免了一个全功能对象关系映射(ORM)的复杂性来存储用户信息。如果你的应用程序不使用ORM工具,你可以写一个自定义UserDetailsService重用在你可能已经创建好的映射文件上。回到 JdbcDaoImpl,实例的配置如下::

id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="username" value="sa"/>
<property name="password" value=""/>


id="userDetailsService"
    class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>

您可以通过修改上面的DriverManagerDataSource使用不同的关系型数据库管理系统。你也可以从JNDI获得,与任何其他的Spring配置使用一个全球性的数据源。
Authority Groups
默认情况下,JdbcDaoImpl加载权限直接映射到用户的角色(见 数据库架构附录)。另一种方法是将权限分成组并分配组给用户。有些人喜欢这种方式作为管理用户权限的一种手段。见 JdbcDaoImpl Javadoc获得如何能够使用权限组的更多信息。该组架构也包括在附录中。
Password Encoding
Spring Security的 PasswordEncoder接口用于支持密码以某种方式在持久存储中进行编码。你不应该在纯文本中存储密码。总是使用单向密码算法如BCrypt使用内置的混淆值,对于每个存储的密码都是不同的。不要使用普通的哈希函数,如MD5或SHA,甚至是一个混淆的版本。BCrypt是故意设计成慢,用于阻碍离线密码破解,而标准的散列算法是快速和能轻易地被用来测试在并行密码定制的硬件上。你可能会认为这并不适用于你,因为你的密码数据库是安全的,和离线攻击不是一个风险线上的。如果是这样的话,做一些研究并阅读所有的高知名度、一直被嘲笑存储密码不安全并已妥协的网站。最好是在安全的一边。对于安全性使用org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder”是一个不错的选择。在其他常见的编程语言中也有兼容的实现,所以对于互相操作性它也是一个很好的选择。
如果你使用的是已经有哈希密码的遗留系统,那么你需要使用一个解码器来匹配你当前的算法,至少要等到你可以将用户迁移到一个更安全的方案(通常这将涉及到要求用户设置一个新的密码,因为哈希值是不可逆的)。Spring Security具有包含传统的密码编码功能的实现,即org.springframework.security.authentication.encoding包。该DaoAuthenticationProvider可与新的或旧的PasswordEncoder类型注入。
什么是hash?
密码散列对于Spring Security不是唯一的,但是一个共同的来源是对于不熟悉概念的用户。哈希(或摘要)算法是一个单向函数,它由一些输入数据,如密码产生一块固定长度的输出数据(散列)的。作为一个例子,字符串”密码”(十六进制)的MD5哈希。
5f4dcc3b5aa765d61d8327deb882cf99
从这一方面来说hash的是”单向”,要获得给定的散列值这是很困难的(实际上是不可能的),或者说实际上任何可能的输出都会产生散列值的原始输入。这个属性使得哈希值对于进行身份验证是非常有用的。它们可以被存储在用户数据库作为替代明文口令,即使值受到损害它们也不会立刻露出可用于登录的密码。请注意,这也意味着一旦它被编码,你就没有办法恢复密码。
在Hash中添加Salt
随着密码的哈希值的使用有一个潜在的问题就是如果一个普通的词用于输入,则相对容易得到哈希的单向属性。人们倾向于选择类似的密码和巨大的字典,这些以前被黑客攻击的网站都可以在线。例如,如果你使用谷歌搜索哈希值5f4dcc3b5aa765d61d8327deb882cf99,你会很快发现原来的”密码”。以类似的方式,攻击者可以构建散列的字典从标准单词列表中查找原来的密码。有一种方法帮助防止这种情况就是有一个合适的强密码策略,以防止常用的单词被使用。另一个是计算散列何时使用”salt”。这是它与计算散列之前密码组合的每个用户的已知数据的附加字符串。理想情况下,数据应该是随机的,但在实践中,任何salt值通常是最好没有。使用的salt意味着攻击者必须建立散列每个salt值的一个单独的字典,使得攻击更加复杂(但不是不可能)。
Bcrypt自动生成每个密码随机salt值时,它被编码,并存储在一个标准格式的BCrypt的字符串。
传统的方法来处理salt是注入一个SaltSource到DaoAuthenticationProvider,这将获得一个特定的用户salt值并将其传递到PasswordEncoder。使用BCrypt意味着你不用担心salt处理的细节(如值存储在什么地方),因为它是在内部完成的。所以,我们强烈建议你使用BCrypt,除非你已经有一个系统分开其中所存储的salt。
Hashing and Authentication
当一个认证供应商(如Spring Security的DaoAuthenticationProvider)需要对某个用户的已知值提交的认证请求来检查密码和存储的密码以某种方式进行编码,然后提交的值必须准确使用编码相同的算法。这个取决于你的检查,这些都是兼容的,因为Spring Security没有对持久值的控制。如果添加的密码散列以你的身份验证配置Spring Security的,和你的数据库中包含明文密码,那么就没有办法验证成功。即使你知道你的数据库是使用MD5编码的密码,例如,你的应用程序被配置为使用Spring Security的Md5PasswordEncoder,那么有很多事情会是错误的。数据库可以具有在Base 64编码的密码,例如当编码器使用十六进制字符串(缺省值)。替代地数据库可以是使用大写,而从编码器的输出为小写。为了确保,你需要写一个测试,以检查输出从你配置的密码编码器与一个已知的密码和salt的组合,并检查它相匹配的数据库值之前,进一步尝试通过您的应用程序进行验证。使用诸如BCrypt标准将避免这些问题。
如果你想生成您的用户数据库编码密码,直接在Java中进行存储,那么你可以使用PasswordEncoder上的encode方法。
测试

本节介绍了Spring安全提供的测试支持。

若要使用Spring安全测试支持,必须将Spring安全测试(Spring安全性版本。

安全性测试方法

本节演示如何使用Spring安全的测试支持以测试方法为基础的安全性。首先介绍了信息服务,需要被认证的用户才能访问它。

public class HelloMessageService implements MessageService {

    @PreAuthorize("authenticated")
    public String getMessage() {
        Authentication authentication = SecurityContextHolder.getContext()
                                                            .getAuthentication();
        return "Hello " + authentication;
    }
}

getMessage的结果是一个String,表示当前的Spring Security Authentication的“Hello”。以下显示输出的示例。
Hello org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
安全测试设置
在我们可以使用Spring Security Test支持之前,我们必须执行一些设置。以下可以看

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class WithMockUserTests {}

设置Spring Security Test的基本示例。亮点是:
@RunWith指示spring-text模块应该创建一个ApplicationContext这与使用现有的Spring Test支持没有什么不同。有关其他信息,请参阅Spring Reference

@ ContextConfiguration指示spring-text用于创建ApplicationContext的配置。由于未指定配置,因此将尝试默认配置位置。这与使用现有的Spring Test支持没有什么不同。有关其他信息,请参阅“Spring参考”
Spring Security使用WithSecurityContextTestExecutionListener挂接到Spring测试支持,这将确保我们的测试与正确的用户一起运行。它在运行我们的测试之前填充SecurityContextHolder来完成此操作。测试完成后,它将清除SecurityContextHolder。如果您只需要与Spring Security相关的支持,可以使用@SecurityExecutionListeners替换@ContextConfiguration。
记住我们将@PreAuthorize注释添加到我们的HelloMessageService中,因此需要经过身份验证的用户来调用它。如果我们进行了以下测试,我们期望以下测试将

@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void getMessageUnauthenticated() {
messageService.getMessage();
}

@WithMockUser
问题是“我们如何最容易地以特定用户的身份运行测试?” 答案是使用@WithMockUser。 以用户名“user”,密码“password”和角色“ROLE_USER”作为用户进行以下测试。

@Test
@WithMockUser
public void getMessageWithMockUser() {
String message = messageService.getMessage();
...
}

具体如下:
●用户名为“user”的用户不需要存在,因为我们嘲笑用户
●SecurityContext中填写的身份验证的类型为UsernamePasswordAuthenticationToken
●身份验证的主体是Spring Security的User对象
●用户的用户名为“user”,密码为“password”,并使用一个名为“ROLE_USER”的GrantedAuthority。
我们的例子很好,因为我们能够利用大量的默认值。如果我们想用不同的用户名运行测试怎么办?以下测试将使用用户名“customUser”运行。再次,用户不需要实际存在。

@Test
@WithMockUser( “customUsername”)
public void getMessageWithMockUserCustomUsername(){
String message = messageService.getMessage();
...
}

我们也可以轻松地自定义角色。例如,此测试将使用用户名“admin”和角色“ROLE_USER”和“ROLE_ADMIN”进行调用。

@Test
@WithMockUser(用户名= “管理员”,角色= { “USER”, “ADMIN”})
public void getMessageWithMockUserCustomUser(){
String message = messageService.getMessage();
...
}

如果我们不希望该值自动以ROLE_为前缀,我们可以利用权限属性。例如,此测试将使用用户名“admin”和权限“USER”和“ADMIN”进行调用。

@Test
@WithMockUser(username =“admin”,authority = {“ADMIN”,“USER”})
public void getMessageWithMockUserCustomAuthorities(){
String message = messageService.getMessage();
...
}

当然,将注释放在每个测试方法上都可能有点乏味。相反,我们可以将注释放在类级别,每个测试将使用指定的用户。例如,以下用户将使用用户名“admin”,密码“password”以及角色“ROLE_USER”和“ROLE_ADMIN”来运行每个测试。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public class WithMockUserTests {}

@WithAnonymousUser
使用@WithAnonymousUser可以作为匿名用户运行。当您希望使用特定用户运行大多数测试时,这是非常方便的,但希望以匿名用户身份运行一些测试。例如,使用@WithMockUser和Anonymous作为匿名用户,将使用MockUser1和withMockUser2运行以下操作。

@RunWith(SpringJUnit4ClassRunner.class)
@WithMockUser
public class WithUserClassLevelAuthenticationTests {

    @Test
    public void withMockUser1() {
    }

    @Test
    public void withMockUser2() {
    }

    @Test
    @WithAnonymousUser
    public void anonymous() throws Exception {
        // override default to run as anonymous user
    }
}

@WithUserDetails
虽然@WithMockUser是一种非常方便的入门方式,但它在所有实例中都可能无效。例如,应用程序通常认为身份验证主体是特定类型的。这样做是为了使应用程序可以将主体引用为自定义类型并减少Spring Security的耦合。
自定义主体通常由自定义UserDetailsS​​ervice返回,返回一个实现了UserDetails和自定义类型的对象。对于这样的情况,使用自定义UserDetailsS​​ervice创建测试用户很有用。这正是@WithUserDetails所做的。
假设我们有一个以Bean显示的UserDetailsS​​ervice,将使用UsernamePasswordAuthenticationToken类型的身份验证和用户名为“user”的UserDetailsS​​ervice返回的主体来调用以下测试。

@Test
@WithUserDetails
public void getMessageWithUserDetails(){
String message = messageService.getMessage();
...
}

我们还可以自定义用户名,用于从UserDetailsS​​ervice查找用户。例如,该测试将使用用户名“customUsername”从UserDetailsS​​ervice返回的主体执行。

@Test
@WithUserDetails( “customUsername”)
public void getMessageWithUserDetailsCustomUsername(){
String message = messageService.getMessage();
...
}

我们还可以提供一个显式的bean名称来查找UserDetailsS​​ervice。例如,此测试将使用具有bean名称“myUserDetailsS​​ervice”的UserDetailsS​​ervice查找“customUsername”的用户名。

@Test
@WithUserDetails(value =“customUsername”,userDetailsS​​erviceBeanName =“myUserDetailsS​​ervice”)
public void getMessageWithUserDetailsS​​erviceBeanName(){
String message = messageService.getMessage();
...
}

像@WithMockUser一样,我们也可以将我们的注释放在类级别,以便每个测试使用相同的用户。但是,与@WithMockUser不同,@WithUserDetails要求用户存在。

@WithSecurityContext
我们已经看到,如果我们不使用自定义身份验证主体,那么@WithMockUser是一个很好的选择。接下来,我们发现@WithUserDetails将允许我们使用自定义的UserDetailsS​​ervice来创建我们的身份验证主体,但要求用户存在。我们现在将看到一个允许最大灵活性的选项。
我们可以创建自己的注释,使用@WithSecurityContext来创建我们想要的任何SecurityContext。例如,我们可能会创建一个名为@WithMockCustomU

@Retention(RetentionPolicy.RUNTIME) @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) public @interface WithMockCustomUser {

String username() default "rob";

String name() default "Rob Winch";  }

ckCustomUser用@WithSecurityContext注释注释。这就是Spring Security Test支持的信号,我们打算为测试创建一个SecurityContext。 @WithSecurityContext注释要求我们指定一个SecurityContextFactory,它将创建一个新的SecurityContext,给定我们的@WithMockCustomUser注释。您可以在下面找到我们的WithMockCustomUserSecurityContextFactory实现:

public class WithMockCustomUserSecurityContextFactory
    implements WithSecurityContextFactory<WithMockCustomUser> {
    @Override
    public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();

        CustomUserDetails principal =
            new CustomUserDetails(customUser.name(), customUser.username());
        Authentication auth =
            new UsernamePasswordAuthenticationToken(principal, "password", principal.getAuthorities());
        context.setAuthentication(auth);
        return context;
    }
}

我们现在可以使用我们的新注释来注释测试类或测试方法,Spring Security的WithSecurityContextTestExecutionListener将确保我们的SecurityContext被适当地填充。
当创建自己的WithSecurityContextFactory实现时,很高兴知道它们可以用标准的Spring注释进行注释。例如,WithUserDetailsS​​ecurityContextFactory使用@Autowired注释来获取UserDetailsS​​ervice:

final class WithUserDetailsSecurityContextFactory
    implements WithSecurityContextFactory<WithUserDetails> {

    private UserDetailsService userDetailsService;

    @Autowired
    public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public SecurityContext createSecurityContext(WithUserDetails withUser) {
        String username = withUser.value();
        Assert.hasLength(username, "value() must be non empty String");
        UserDetails principal = userDetailsService.loadUserByUsername(username);
        Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        return context;
    }
}

测试元注释
如果您经常在测试中重复使用同一用户,则不得不重复指定属性是不理想的。例如,如果有许多与管理用户相关的测试与用户名“admin”和角色ROLE_USER和ROLE_ADMIN您必须写:
@WithMockUser(username=”admin”,roles={“USER”,”ADMIN”})
我们可以使用元注释,而不是在任何地方重复这个。例如,我们可以创建一个名为WithMockAdmin的元注释:

@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value="rob",roles="ADMIN")
public @interface WithMockAdmin { 
}

现在我们可以使用@WithMockAdmin与更详细的@WithMockUser相同的方式。
元标注使用上述任何测试注释。例如,这意味着我们也可以为@WithUserDetails(“admin”)创建一个元注释。
Spring MVC测试集成
Spring Security提供与Spring MVC测试的全面集成
设置MockMvc和Spring Security
为了使用Spring Security与Spring MVC Test,需要添加Spring Security FilterChainProxy作为过滤器。还需要添加Spring Security的TestSecurityContextHolderPostProcessor来支持使用注释在Spring MVC测试中作为用户运行。这可以使用Spring Security的SecurityMockMvcConfigurers.springSecurity()完成。例如:
Spring Security的测试支持需要spring-test-4.1.3.RELEASE或更高版本。

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class CsrfShowcaseTests {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    @Before
    public void setup() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity()) 
                .build();
    }

...

SecurityMockMvcConfigurers.springSecurity()将执行我们需要将Spring Security与Spring MVC测试集成在一起的所有初始设置

SecurityMockMvcRequestPostProcessors*
Spring MVC Test提供了一个名为RequestPostProcessor的方便的界面,可用于修改请求。 Spring Security提供了许多可以使测试更容易的RequestPostProcessor实现。为了使用Spring Security的RequestPostProcessor实现,请确保使用以下静态导入:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors。*;
用CSRF保护测试
当测试任何不安全的HTTP方法并使用Spring Security的CSRF保护时,必须确保在请求中包含有效的CSRF令牌。要使用以下命令将有效的CSRF令牌指定为请求参数:
MVC
.perform(post(“/”).with(csrf()))
如果您喜欢,可以在标题中包含CSRF令牌:
MVC
.perform(post(“/”).with(csrf().asHeader()))
您还可以使用以下方法测试提供无效的CSRF令牌:
MVC
.perform(post(“/”).with(csrf().useInvalidToken()))
在Spring MVC测试中作为用户运行测试
通常希望以特定用户身份运行测试。填写用户有两种简单的方法:
●使用RequestPostProcessor在Spring MVC测试中作为用户运行
●在使用注释的Spring MVC测试中作为用户运行
作为用户在Spring MVC测试中使用RequestPostProcessor运行
有一些选项可用于将用户与当前的HttpServletRequest关联。例如,以用户名“user”,密码“password”和角色“ROLE_USER”作为用户(不需要存在)运行:
支持通过将用户关联到HttpServletRequest来进行。 要将请求与SecurityContextHolder相关联,您需要确保SecurityContextPersistenceFilter与MockMvc实例相关联。 这样做的几个方法是:

 调用apply(springSecurity())

将Spring Security的FilterChainProxy添加到MockMvc

使用MockMvcBuilders.standaloneSetup手动添加SecurityContextPersistenceFilter到MockMvc实例可能是有意义的
mvc
.perform(get(“/”).with(user(“user”)))
您可以轻松进行自定义。例如,以用户名“admin”,密码“pass”,角色“ROLE_USER”和“ROLE_ADMIN”作为用户(不需要存在)运行。
MVC
.perform(get(“/admin”).with(user(“admin”).password(“pass”).roles(“USER”,”ADMIN”)))
如果您有要使用的自定义UserDetails,您也可以轻松指定。例如,以下将使用指定的UserDetails(不需要存在)使用具有指定UserDetails的主体的UsernamePasswordAuthenticationToken运行:
MVC
.perform(get(“/”).with(user(userDetails)))
您可以使用以下方式作为匿名用户运行:
MVC
.perform(get(“/”).with(anonymous()))
如果您使用默认用户运行并希望以匿名用户身份执行一些请求,则此功能尤其有用。
如果您需要自定义身份验证(不需要存在),则可以使用以下命令:
MVC
.perform(get(“/”).with(authentication(authentication)))
您甚至可以使用以下方式自定义SecurityContext:
MVC
.perform(get(“/”).with(securityContext(securityContext)))
我们还可以通过使用MockMvcBuilders的默认请求来确保作为每个请求的特定用户运行。例如,以用户名“admin”,密码“password”和角色“ROLE_ADMIN”作为用户(不需要存在)运行:
mvc = MockMvcBuilders
.webAppContextSetup(context)
.defaultRequest(get(“/”).with(user(“user”).roles(“ADMIN”)))
.apply(springSecurity())
.build();
如果您发现在许多测试中使用相同的用户,建议您将用户移至方法。例如,您可以在您自己的名为CustomSecurityMockMvcRequestPostProcessors的类中指定以下内容:

public static RequestPostProcessor rob() {
    return user("rob").roles("ADMIN");
}

现在您可以在SecurityMockMvcRequestPostProcessors上执行静态导入,并在测试中使用它们:
import static sample.CustomSecurityMockMvcRequestPostProcessors。*;

MVC
.perform(get(“/”).with(rob()))
作为用户在Spring MVC测试中以注释方式运行
作为使用RequestPostProcessor创建用户的替代方法,您可以使用测试方法安全性中描述的注释。例如,以下用户将使用用户名“user”,密码“password”和角色“ROLE_USER”进行测试:

@Test
@WithMockUser
public void requestProtectedUrlWithUser() throws Exception {
mvc
        .perform(get("/"))
        ...
}

或者,以下将使用用户名“user”,密码“password”和角色“ROLE_ADMIN”进行测试:

@Test
@WithMockUser(roles="ADMIN")
public void requestProtectedUrlWithUser() throws Exception {
mvc
        .perform(get("/"))
        ...
}

测试HTTP基本身份验证
虽然一直可以使用HTTP Basic进行身份验证,但是记住标题名称,格式和编码值是有点乏味的。现在可以使用Spring Security的httpBasic RequestPostProcessor来完成。例如,下面的代码段:
MVC
.perform(get(“/”).with(httpBasic(“user”,”password”)))
将尝试使用HTTP Basic通过使用用户名“user”和密码“password”来验证用户,以确保在HTTP请求中填充以下标题:
Authorization: Basic dXNlcjpwYXNzd29yZA==
SecurityMockMvcRequestBuilders
Spring MVC Test还提供了一个RequestBuilder接口,可用于创建测试中使用的MockHttpServletRequest。 Spring Security提供了一些可用于使测试更容易的RequestBuilder实现。为了使用Spring Security的RequestBuilder实现,请确保使用以下静态导入:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;

测试表单验证
您可以使用Spring Security的测试支持轻松创建一个测试基于表单的身份验证的请求。例如,以下内容将使用用户名“user”,密码“password”和有效的CSRF令牌将“POST”提交给“/ login”
MVC
.perform(formLogin())
很容易定制请求。例如,以下内容将使用用户名“admin”,密码“pass”和有效的CSRF令牌将“/ auth”提交给“
MVC
.perform(formLogin(“/auth”).user(“admin”).password(“pass”))
我们还可以自定义包含用户名和密码的参数名称。例如,这是上述请求被修改为包括在HTTP参数“u”上的用户名和HTTP参数“p”上的密码。
MVC
.perform(formLogin(“/auth”).user(“u”,”admin”).password(“p”,”pass”))
测试注销
虽然使用标准的Spring MVC测试相当微不足道,但您可以使用Spring Security的测试支持使测试注销变得更简单。例如,以下将使用有效的CSRF令牌将POST提交到“/ logout”:
MVC
.perform(logout())
您还可以自定义要发布的URL。例如,下面的代码段将使用有效的CSRF令牌将POST提交给“/ signout”:
MVC
.perform(logout(“/signout”))

SecurityMockMvcResultMatchers
有时需要对请求作出各种安全相关的断言。为了满足这一需求,Spring Security Test支持实现Spring MVC Test的ResultMatcher接口。为了使用Spring Security的ResultMatcher实现,请确保使用以下静态导入:

import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;

未认证的断言
有时,声明没有与MockMvc调用的结果相关联的认证用户可能是有价值的。例如,您可能想要测试提交无效的用户名和密码,并验证没有用户进行身份验证。您可以使用Spring Security的测试支持轻松实现以下操作:
MVC
.perform(formLogin().password(“invalid”))
.andExpect(unauthenticated());
认证断言
通常,我们必须断言认证用户存在。例如,我们可能想要验证我们是否成功验证。我们可以使用以下代码片段验证基于表单的登录是否成功:
MVC
.perform(formLogin())
.andExpect(authenticated());
如果我们想要断言用户的角色,我们可以改进我们以前的代码,如下所示:
MVC
.perform(formLogin().user(“admin”))
.andExpect(authenticated().withRoles(“USER”,”ADMIN”));
或者,我们可以验证用户名:
MVC
.perform(formLogin().user(“admin”))
.andExpect(authenticated().withUsername(“admin”));
我们也可以组合断言:
MVC
.perform(formLogin().user(“admin”).roles(“USER”,”ADMIN”))
.andExpect(authenticated().withUsername(“admin”));

Web应用程序的安全性
大多数Spring Security的用户将使用在这使得HTTP和Servlet API的用户应用程序的框架。在这一部分中,我们将看看Spring Security提供身份验证和访问控制特性的应用程序的web层。我们会看看该命名空间的门面后面,看看哪些类和接口实际上是组装提供web层安全。在某些情况下需要使用传统的bean配置提供完全控制配置,所以我们也将看到怎样的情况下直接配置这些类的命名空间。
The Security Filter Chain
Spring Security的web基础设施是完全基于标准的servlet过滤器。它不使用servlet或任何其他基于servlet框架(比如Spring MVC)在内部,所以它与任何特定的web技术没有紧密的联系。它涉及到HttpServletRequest和HttpServletResponse并不在乎请求是否来自一个浏览器,一个web服务客户端,一个HttpInvoker或一个AJAX应用程序。
Spring Security维护过滤器链内部,每个过滤器都有一个特定的责任和过滤器中添加或删除的配置取决于哪些服务是必需的。过滤器的顺序很重要,因为它们之间有依赖关系。如果你一直使用命名空间配置,那么过滤器会为你自动配置和你不需要明确定义任何Spring bean但可能有时你想要完全控制安全过滤器链,因为您正在使用的功能中不支持在命名空间中,或你使用你自己的自定义版本的类。
DelegatingFilterProxy
当使用Servlet过滤器时,你显然需要声明他们的web.xml,否则将被servlet容器忽略。在Spring Security,过滤器类也都在Spring bean中定义应用上下文,从而能够利用Spring的丰富的依赖注入的设施和生命周期接口的优势。Spring的DelegatingFilterProxy提供web.xml和应用程序上下文之间的链接
当使用DelegatingFilterProxy,你会看到这样的事情在web.xml文件中:

<filter>
<filter-name>myFilterfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>

<filter-mapping>
<filter-name>myFilterfilter-name>
/*
filter-mapping>

请注意,该过滤器实际上是一个DelegatingFilterProxy,而不是类,会实现过滤器的逻辑。什么DelegatingFilterProxy所做的是通过向其从Spring应用程序上下文中的bean委托Filter的方法。这使得bean来从Spring Web应用程序上下文的生命周期支持和配置灵活性中受益。bean必须实现javax.servlet.Filter并且必须具有相同的名称,在filter-name元素。阅读DelegatingFilterProxy的Javadoc的更多信息
FilterChainProxy
Spring Security的网络基础设施,只能通过委托给FilterChainProxy的一个实例使用。安全过滤器不应该由自己来使用。理论上你可以声明每个弹簧安全过滤器bean,你需要在你的应用程序的上下文文件并添加相应的DelegatingFilterProxy到web.xml每个过滤器中,确保他们正确排序,但是这是很麻烦的并且如果你有很多的过滤器会很快弄乱了web.xml文件。FilterChainProxy让我们将一个条目添加到web.xml和完全处理应用程序上下文文件来管理我们的网络安全bean。它是使用DelegatingFilterProxy连接的,就像在上面的例子,随着filter-name设为”filterChainProxy”bean的名称。然后过滤器链中声明应用程序上下文相同的bean的名称。这里有一个例子:

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
<list>
<sec:filter-chain pattern="/restful/**" filters="
securityContextPersistenceFilterWithASCFalse,
basicAuthenticationFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
<sec:filter-chain pattern="/**" filters="
securityContextPersistenceFilterWithASCTrue,
formLoginFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
list>
constructor-arg>
bean>

命名空间元素filter-chain是用来方便设置应用程序中所需的安全过滤器链。 [2]它映射一个特定的URL模式的从filters元素指定的bean的名字建立了过滤器列表,并结合SecurityFilterChain类型的bean。该pattern属性需要一个Ant路径和最具体的URI应该首先出现。 [3] 在运行时,FilterChainProxy将定位相匹配的当前Web请求,并由filters属性指定的过滤器bean列表将被应用到该请求的第一个URI模式。过滤器会按照它们定义的顺序调用,所以你必须在这是适用于一个特定的URL过滤器链的完全控制。
您可能已经注意到,我们已宣布两个SecurityContextPersistenceFilter在过滤器链(ASC的简称allowSessionCreation,SecurityContextPersistenceFilter的属性)。因为web服务从来不会在请求jsessionid,创造HttpSession表示这样的用户代理是一种浪费。如果你这需要这最大的扩展能力高容量的应用程序,我们建议您使用上面的方法。对于较小的应用程序,使用SecurityContextPersistenceFilter(其默认allowSessionCreation为true)就足够了。
需要注意的是FilterChainProxy不会调用它配置了过滤器标准过滤器生命周期方法。我们建议你使用Spring的应用程序上下文的生命周期接口作为替代,就像你对任何其他的Spring bean一样。
当我们看到如何使用命名空间配置建立网络安全,我们使用了DelegatingFilterProxy和”springSecurityFilterChain”这个名字。您现在应该能够看到,这是由FilterChainProxy的名称创建的命名空间。
绕过过滤器链
你可以使用属性filters = “none”替代供应一个过滤器bean列表。这将完全忽略安全筛选器链中的请求模式。请注意,任何匹配此路径将没有任何身份验证或授权服务应用,将可自由访问。如果你想在一个请求中使用的SecurityContext内容的内容,那么它必须通过保安过滤器链。否则,SecurityContextHolder不会被填充且内容也将空。
Filter Ordering
过滤器链中定义的顺序是非常重要的。不论你使用的是哪一种过滤器,顺序应该如下:
● ChannelProcessingFilter,因为它可能需要重定向到其他协议。
● SecurityContextPersistenceFilter,所以SecurityContext可在SecurityContextHolder在web请求的开始设立,并且当web请求结束时SecurityContext任何改变可以被复制到HttpSession(准备下一个使用Web请求)
● ConcurrentSessionFilter,因为它使用了SecurityContextHolder功能和需要更新SessionRegistry以反映反映主要正在处理的请求。
● 认证处理机制 - UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等 - 使得SecurityContextHolder可以被修饰以包含有效的Authentication请求令牌
● SecurityContextHolderAwareRequestFilter,如果你使用它来安装一个Spring Security意识HttpServletRequestWrapper到你的servlet容器
● JaasApiIntegrationFilter,如果JaasAuthenticationToken是在SecurityContextHolder这将处理FRememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, and the request presents a cookie that enables remember-me services to take place, a suitable remembered Authentication object will be put thereilterChain作为Subject在JaasAuthenticationToken
● RememberMeAuthenticationFilter,所以如果早期的认证处理机制没有更新SecurityContextHolder并且请求给出一个Cookie使remember-me服务发生,一个合适的记忆Authentication对象将被放在那里
● AnonymousAuthenticationFilter,这样如果之前的验证执行机制没有更新SecurityContextHolder,一个匿名Authentication对象将被放在那里
● ExceptionTranslationFilter,捕获任何Spring安全异常,以便响应可以返回一个HTTP错误或适当的AuthenticationEntryPoint可以启动
● FilterSecurityInterceptor,保护网络的URI,当访问被拒绝引发异常
Request Matching and HttpFirewall
Spring Security有几个方面,你已经定义模式对传入的请求,以决定该请求应如何处理测试。这发生在FilterChainProxy决定的请求应通过传递该过滤器链,也当FilterSecurityInterceptor决定哪些安全约束适用于请求。要了解什么机制和针对你定义的模式进行测试时使用什么URL值是非常重要的。
Servlet规范定义了HttpServletRequest其中有几个属性是通过getter方法访问,以及我们可能要匹配。这些都是contextPath,servletPath,pathInfo和queryString。 Spring Security唯一感兴趣的仅仅是在确保应用程序中的路径,所以contextPath被忽略。不幸的是,servlet规范并没有定义到底什么是servletPath和pathInfo的值将包含特定请求URI。例如,一个URL的每个路径段可包含参数, 定义在 RFC 2396 [4]. 该规范并没有明确说明是否这些都应该被包含在`servletPath和pathInfo的价值观和行为的不同servlet容器之间变化。有一种危险,当一个应用程序被部署在一个容器中,而不是从这些值中的路径参数,攻击者可以将它们添加到请求的URL,以意外导致模式匹配成功或失败。 [5]. 在传入的URL的其他变化也可能。它可能包含路径遍历序列(如/../)或多个斜杠(//),这也可能导致匹配失败。一些容器标准化这些之前执行servlet映射,但其他人没有。为了防止类似这些问题,FilterChainProxy采用了HttpFirewall战略,检查和包装的要求。未规范化请求默认自动拒绝和路径参数和复制斜线匹配的目的被删除。 [6]. 因此,至关重要的一个FilterChainProxy用于管理安全过滤器链。需要注意的是servletPath和pathInfo值由容器解码,所以你的应用程序不应该有任何有效的路径包含分号,这些部分将被删除匹配的目的。
正如上面提到的,默认的策略是使用Ant风格的路径进行匹配,这可能是大多数用户的最佳选择。该策略在使用Spring的AntPathMatcher执行对级联servletPath和pathInfo的格局不区分大小写的匹配类AntPathRequestMatcher实现,忽视了queryString。
如果由于某种原因,你需要一个更强大的匹配策略,您可以使用正则表达式。然后RegexRequestMatcher战略的实现。看到这个类的Javadoc获取更多信息。
在实践中我们建议你使用方法安全服务层,控制您的应用程序访问,并不完全依赖于在Web应用程序级别定义的安全约束的使用。URL改变,很难考虑所有可能的应用程序的URL可能支持和如何处理请求。你应该尝试限制自己使用一些简单的容易理解的路径。总是试图用”deny-by-default”的方法,全方位通配符( / or ) 定义和拒绝访问。
安全在服务层定义更健壮且难以绕过,所以你应该利用Spring Security的方法安全选项。
与其它过滤器-基于框架使用
如果您使用的过滤器也基于其他一些框架,那么你需要确保Spring Security过滤器是第一位的。这使得SecurityContextHolder到时由其他过滤器被填充以供使用。例子是使用SiteMesh的来装饰您的网页或类似Wicket的Web框架,它使用一个过滤器来处理其请求。
高级的命名空间配置
正如我们在命名空间章节前面所看到的,它可以使用多个http元素来定义不同的URL模式来进行不同的安全配置。每个元素创建内部FilterChainProxy内的过滤器链和应该映射的URL模式。这些元素将声明它们的添加顺序,所以最具体的模式必须再次先声明。这里的另一个例子,与上面有一个类似的情况,在应用程序支持一个无国籍的RESTful API和一个正常的网络应用,用户可以登录使用的一种形式。


<http pattern="/restful/**" create-session="stateless">
<intercept-url pattern='/**' access="hasRole('REMOTE')" />
<http-basic />
http>


<http pattern="/login.htm*" security="none"/>


<http>
<intercept-url pattern='/**' access="hasRole('USER')" />
<form-login login-page='/login.htm' default-target-url="/home.htm"/>
<logout />
http>

核心安全过滤器
总是会有一些关键的过滤器在web应用程序中使用Spring Security,所以我们来看看这些和他们的支持类和接口。我们不会覆盖所有功能,所以如果你想获得完整的信息一定要看看它们的Javadoc。
FilterSecurityInterceptor
我们已经看到了 FilterSecurityInterceptor简要讨论访问控制 ,我们已经与其中元素相结合,在内部配置的命名空间中使用它。现在,我们将看到如何明确将其FilterChainProxy其配置为使用,其配套过滤器ExceptionTranslationFilter一起。典型的配置示例如下所示:

id="filterSecurityInterceptor"
    class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource">
    
    "/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
    "/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
    
property>

FilterSecurityInterceptor负责处理HTTP资源的安全性。它需要一个AuthenticationManager和AccessDecisionManager参考。它也有提供适用于不同的HTTP URL请求的配置属性。回头参考技术介绍中这些原始的讨论。
在FilterSecurityInterceptor配置属性可以两种方式进行配置。上面显示的第一个,使用命名空间元素。这类似于元素的命名空间,但 的子元素只使用pattern和 access的属性。使用逗号分隔不同的配置属性,适用于每个HTTP URL。第二个选择是编写自己的‘SecurityMetadataSource’,但这超出了本文的范围。不管采用哪种方式,在SecurityMetadataSource负责返回一个List包含所有用一个安全的HTTP URL相关联的配置属性。
应当指出的是,FilterSecurityInterceptor.setSecurityMetadataSource()方法实际上是FilterInvocationSecurityMetadataSource的一个实例。这是一个标记接口,它的子类是SecurityMetadataSource。它只是表示SecurityMetadataSource明白FilterInvocation。在简单的利益,我们将继续参考FilterInvocationSecurityMetadataSource作为SecurityMetadataSource,因为区别是无关紧要大多数用户。为了简单起见,我们将继续参考FilterInvocationSecurityMetadataSource作为SecurityMetadataSource,因为对于大多数用户区别是无关紧要。
通过命名空间的语法创建的SecurityMetadataSource获得通过匹配在配置的pattern属性请求的URL特定FilterInvocation配置属性。这表现在,它确实为命名空间配置相同的方式。默认的是处理所有表达式作为Apache Ant的路径和正则表达式还支持更复杂的情况。request-matcher属性用于指定模式的类型被使用。它是不可能的相同的定义内混合表达式语法。作为一个例子,使用正则表达式,而不是Ant paths的先前的配置将按如下表示:

id="filterInvocationInterceptor"
    class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="runAsManager" ref="runAsManager"/>
<property name="securityMetadataSource">
    "regex">
    "\A/secure/super/.*\Z" access="ROLE_WE_DONT_HAVE"/>
    "\A/secure/.*\" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
    

模式总是按照它们被定义的顺序进行。因此,重要的是,更具体的模式被定义在列表中比不太具体的模式更高。这是反映在我们的例子上面,在更具体的/secure/super/模式似乎比/secure/模式更高。如果它们被逆转,/secure/模式会一直匹配,并且/secure/super/模式将永远不会被评估。
ExceptionTranslationFilter
该ExceptionTranslationFilter在FilterSecurityInterceptor安全过滤器堆栈的上面。它没有做任何实际的安全执法本身,而是处理由安全拦截器抛出的异常,并提供合适的HTTP响应。

id="exceptionTranslationFilter"
class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler"/>


id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>


id="accessDeniedHandler"
    class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.htm"/>

AuthenticationEntryPoint
如果用户请求一个安全HTTP资源这个AuthenticationEntryPoint将被调用,但他们不被认证。适当的AuthenticationException或AccessDeniedException将被安全拦截器进一步拆毁了调用堆栈,触发入口点的commence方法。这确实呈现给用户的适当的反应,使认证可以开始工作。我们这里使用的是LoginUrlAuthenticationEntryPoint,这将请求重定向到一个不同的URL(一般是一个登录页面)。使用将取决于你想要的认证机制的实际实现在应用程序中使用。
AccessDeniedHandler
如果用户已经通过身份验证,他们试图访问受保护的资源会发生什么?在正常使用情况下,这不应该发生,因为应用程序的工作流程应该仅限于用户访问的操作。例如,HTML链接到一个管理页面可能隐藏唉一个没有管理员角色的用户中。你不能依赖隐藏链接的安全,总是有一个可能性,用户只会直接输入URL,以绕过限制。或者,他们可能会修改一个RESTful URL来改变一些参数值。您的应用程序必须对这些方案进行保护,或者它绝对会是不安全的。您通常会使用简单的网络层安全性约束适用于基本的网址,并使用更具体的方法,基于安全上的服务层接口真正明确什么是允许的。
如果一个AccessDeniedException被抛出并且用户已经被认证,那么这意味着一个操作已经尝试了它们不具有足够的权限。在这种情况下,ExceptionTranslationFilter将调用第二策略,AccessDeniedHandler。默认情况下,AccessDeniedHandlerImpl被使用,这只是发送一个403(禁止)响应于客户端。此外,还可以配置明确的实例(如在上面的例子),并设置一个错误页面的URL,它会请求转发 [7]. 这可以是一个简单的“拒绝访问”页上,如一个JSP,或者它可以是更复杂的处理程序,如一个MVC的控制器。当然,你可以自己实现接口,并使用自己的实现。
它也可以提供当你自定义你的应用程序时使用命名空间来配置AccessDeniedHandler。详见 命名空间附录 了解更多详情。
SavedRequest s and the RequestCache Interface
ExceptionTranslationFilter另一个责任是调用AuthenticationEntryPoint之前保存当前的请求。这允许使用已经验证后要恢复的请求(看到之前的概述 web 认证).一个典型示例是在用户登录表单,然后重定向到默认的原始URL SavedRequestAwareAuthenticationSuccessHandler(见 下方).
该RequestCache封装用于存储和检索HttpServletRequest实例所需的功能。默认HttpSessionRequestCache被使用,其中该请求存储在HttpSession。该RequestCacheFilter具有实际恢复从当用户被重定向到原始URL缓存中保存的请求工作。
在正常情况下,你不需要修改任何此功能,但保存的请求处理是一个“尽力而为”的做法,有可能是其默认的配置是无法处理的情况。使用这些接口,使得它完全可插入从Spring Security 3.0起。
SecurityContextPersistenceFilter
我们涵盖所有重要的过滤器在技术概述这一章,所以你可能想在这一点上重新阅读该部分。让我们先来看看,你会如何配置它与一个FilterChainProxy使用。一个基本的配置只需要bean本身。

public interface SecurityContextRepository {

SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);

void saveContext(SecurityContext context, HttpServletRequest request,
        HttpServletResponse response);
}

该HttpRequestResponseHolder只是一个容器传入的请求和响应对象,允许实现替换这些包装类。返回的内容将被传递给过滤器链。
默认实现HttpSessionSecurityContextRepository,存储安全上下文作为HttpSession的属性 [8]. 对于此实现最重要的配置参数是allowSessionCreation属性,它默认为true,从而使类来创建一个会话如果它需要一个存储身份验证的用户的安全上下文(它不会创建一个除非认证发生和安全上下文的内容改变了)。如果你不希望创建一个会话,则可以将此属性设置为false:

<bean id="securityContextPersistenceFilter"
    class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
<property name='securityContextRepository'>
    <bean class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
    <property name='allowSessionCreation' value='false' />
    bean>
property>
bean>

或者你可以提供 NullSecurityContextRepository的一个实例, 一个 null object 的实现, 这将防止安全上下文被存储,即使已经在会话期间创建一个该实例请求。
UsernamePasswordAuthenticationFilter
我们现在看到的三个主要的过滤器总是存在在一个Spring Security web配置。这些也都是这是由命名空间元素自动创建,并且不能被替代被取代的三种。现在唯一缺少的是一个实际的认证机制,而这将允许用户进行身份验证。该过滤器是最常用的身份验证过滤器,也是最常用的一种认证过滤器 [9]. 另外,它还提供了元素的命名空间的实现。有三个阶段需要进行配置。
● 配置一个LoginUrlAuthenticationEntryPoint登录页面的URL,就像我们上面,并设置ExceptionTranslationFilter。
● 实现登录页面(使用JSP或MVC控制器)。
● 配置一个UsernamePasswordAuthenticationFilter应用程序上下文的实例
● 将过滤器bean添加到您的过滤器链代理(确保你要注意顺序)。
登录表单包含username和password输入字段,并发布到由过滤器(默认情况下这是/login)监测的URL。基本的过滤器配置看起来是这样的:

id="authenticationFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>

认证成功和失败的应用程序流程
该过滤器调用配置AuthenticationManager处理每个认证请求。继成功认证或认证失败的目的地是由AuthenticationSuccessHandler和AuthenticationFailureHandler策略接口分别控制。过滤器属性允许你设置这些,所以你可以完全自定义行为 [10]. 一些标准的实现,如SimpleUrlAuthenticationSuccessHandler,SavedRequestAwareAuthenticationSuccessHandler,SimpleUrlAuthenticationFailureHandler和ExceptionMappingAuthenticationFailureHandler提供。看一看这些类的Javadoc和AbstractAuthenticationProcessingFilter来获取它们是如何工作的概述和所支持的特性。
如果认证成功,将所得Authentication对象将被放置到SecurityContextHolder。配置的AuthenticationSuccessHandler将被重定向或用户转发给适当的目的地。默认情况下使用SavedRequestAwareAuthenticationSuccessHandler,这意味着用户将被重定向到他们要求的原始目的地之前,他们被要求登录。
ExceptionTranslationFilter缓存原始请求用户。当用户进行身份验证时,这种缓存的请求处理程序利用请求获取原始URL和重定向到它。然后原始请求被重建和使用作为替代。
如果身份验证失败,则将调用AuthenticationFailureHandler配置。
Servlet API的集成
本节描述如何与Servlet API集成Spring Security。 servletapi-xml示例应用程序演示了使用这些方法。
Servlet 2.5+ 集成
HttpServletRequest.getRemoteUser()
HttpServletRequest.getRemoteUser() 将返回SecurityContextHolder.getContext().getAuthentication().getName()的结果。 这通常是当前的用户名。这可能是有用的,如果你想在应用程序中显示当前用户名。此外,检查如果这是空可以用来表示如果一个用户已经通过身份验证或者是匿名的。知道用户是否通过身份验证可以用于确定特定UI元素应该显示(即注销链接应该只显示如果用户身份验证)。
HttpServletRequest.getUserPrincipal()
HttpServletRequest.getUserPrincipal()将返回SecurityContextHolder.getContext().getAuthentication()的结果。这意味着它是一个 Authentication 通常是 UsernamePasswordAuthenticationToken的一个实例使用基于用户名和密码的身份验证。这可能是有用的,如果你需要更多关于用户的信息。例如,您可能已经创建了一个自定义UserDetailsService,返回一个自定义的UserDetails包含您的用户的姓名。你可以用以下获得这些信息:
Authentication auth = httpServletRequest.getUserPrincipal();
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal();
String firstName = userDetails.getFirstName();
String lastName = userDetails.getLastName();
应该指出的是,它通常是坏的做法,在你的应用程序执行这么多的逻辑。相反,应该集中它减少任何Spring Security和Servlet API的耦合。
HttpServletRequest.isUserInRole(String)
The HttpServletRequest.isUserInRole(String) 将确定 SecurityContextHolder.getContext().getAuthentication().getAuthorities() 包含一个 GrantedAuthority 角色传入的 isUserInRole(String)。 一般用户不应通过”ROLE_” 前缀到这个方法,因为它是自动添加的。例如,如果你想要确定当前用户有权力”ROLE_ADMIN”,你可以使用以下:
boolean isAdmin = httpServletRequest.isUserInRole(“ADMIN”);
这可能是有用的确定应该显示特定的UI组件。例如,您可能只显示管理链接如果当前用户是管理员。
Servlet 3+ 集成
以下部分将介绍Servlet 3集成Spring Security的方法。
HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)
The HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)方法可用于确保用户身份验证。如果他们没有身份验证,配置AuthenticationEntryPoint将用于请求用户进行身份验证(即重定向到登录页面)。
HttpServletRequest.login(String,String)
The HttpServletRequest.login(String,String) 方法可用于验证用户与当前AuthenticationManager。例如,下面将尝试验证用户名 “user”和密码”password”:

try {
httpServletRequest.login("user","password");
} catch(ServletException e) {
// fail to authenticate
}

没有必要捕获ServletException如果你想让Spring Security来处理失败的身份验证。
HttpServletRequest.logout()
The HttpServletRequest.logout() 方法可以用于记录当前用户。
通常这意味着SecurityContextHolder将被清除,HttpSession将失效,任何“记住我”身份验证将清理干净,等。然而,LogoutHandler配置实现将取决于你的Spring Security配置。需要注意是,在HttpServletRequest.logout()被调用,你要还负责编写一个响应。通常这将涉及一个重定向到欢迎页面。
AsyncContext.start(Runnable)
The AsynchContext.start(Runnable) 的方法,确保您的凭据将传播到新的线程。使用 Spring Security的并发支持Spring Security覆盖AsyncContext.start(可运行),以确保当前SecurityContext处理可运行时使用。例如,以下将输出当前用户的身份验证:

final AsyncContext async = httpServletRequest.startAsync();
async.start(new Runnable() {
    public void run() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        try {
            final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse();
            asyncResponse.setStatus(HttpServletResponse.SC_OK);
            asyncResponse.getWriter().write(String.valueOf(authentication));
            async.complete();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
});

Async Servlet Support
如果你使用的是基于Java的配置,你已经准备好了。如果你使用XML配置中,有一些是必要的更新。第一步是确保你更新你的web.xml使用至少3.0模式如下所示:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

web-app>
接下来你需要确保你的springSecurityFilterChain设置处理异步请求。
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>
    org.springframework.web.filter.DelegatingFilterProxy
filter-class>
<async-supported>trueasync-supported>
filter>
<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
<url-pattern>/*url-pattern>
<dispatcher>REQUESTdispatcher>
<dispatcher>ASYNCdispatcher>
filter-mapping>

就是这样!现在的Spring Security将确保您的SecurityContext是在异步请求传播了。
那么它是怎样工作的?如果你是不是真的感兴趣,请随时跳过这一节的其余部分,否则就继续阅读。这是内置在Servlet规范,但有一点调整,Spring Security确保事情妥善处理异步请求。Spring Security 3.2之前,securitycontextholder的securitycontext自动保存只要httpservletresponse承诺。这可能会导致在异步环境下的问题。例如,考虑以下:

httpServletRequest.startAsync();
new Thread("AsyncThread") {
    @Override
    public void run() {
        try {
            // Do work
            TimeUnit.SECONDS.sleep(1);

            // Write to and commit the httpServletResponse
            httpServletResponse.getOutputStream().flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}.start();

问题是,这个线程是未知的Spring Security,所以SecurityContext中不会传播到它。这意味着当我们提交HttpServletResponse没有SecuriytContext。当Spring Security的自动保存SecurityContext支持HttpServletResponse就会失去我们的登录用户。
自从3.2版本开始,Spring Security是足够聪明,不再自动保存SecurityContext,一旦遵守协议 HttpServletResponse的HttpServletRequest.startAsync()将尽快被调用。
Servlet 3.1+ 集成
以下部分描述了Servlet3.1与Spring Security集成的方法。
HttpServletRequest#changeSessionId()
The HttpServletRequest.changeSessionId() 是默认的方法防止 固定会话 损害Servlet 3.1或更高的版本。
Basic和Digest认证
Basic和digest认证是在web应用中流行的替代身份验证机制。基本身份验证通常是使用无状态的客户对每个请求通过他们的凭证。这是相当常见的结合,其中一个应用程序通过两个基于浏览器的用户界面,并作为Web服务使用基于表单的身份验证中使用它。但是,基本身份验证发送密码以纯文本,所以应该只在真正加密传输层可以使用,例如HTTPS。
BasicAuthenticationFilter
BasicAuthenticationFilter负责处理HTTP头部中的基本认证证书。这可以用于验证由Spring远程协议(如Hessian和Burlap)的呼叫以及正常的浏览器的用户代理(如Firefox和Internet Explorer)。 HTTP基本认证的标准是由RFC 1945,第11条规定,以及BasicAuthenticationFilter符合这个RFC。基本身份验证是一个有吸引力的认证方法,因为它是非常广泛的,部署在用户代理和实施是非常简单的(它只是一个用户名的Base64编码:密码,在HTTP头中指定)。
Configuration
为了实现HTTP基本身份验证,您需要添加一个BasicAuthenticationFilter到你的过滤器链。应用程序上下文应包含BasicAuthenticationFilter及其所需的合作者:

id="basicAuthenticationFilter"
class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>


id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
<property name="realmName" value="Name Of Your Realm"/>

配置的AuthenticationManager处理每个认证请求。如果认证失败,配置AuthenticationEntryPoint将被用来重试认证过程。通常你会在组合使用过滤器BasicAuthenticationEntryPoint,它会返回一个合适的头部重试HTTP基本身份验证的401响应。如果认证成功,将所得Authentication对象将和平常一样被放入SecurityContextHolder。
如果认证事件成功,或者认证不需要执行,因为HTTP头部没有包含支持的认证请求,过滤器链将继续正常工作。过滤器链中断的唯一情况是,如果认证失败了,AuthDigestAuthenticationFilter能够消化处理HTTP头部中的认证证书。摘要式身份验证试图解决许多基本身份验证的弱点,特别是保证凭据从不通过线路明文形式发送。许多用户支持摘要式身份验证,包括Firefox和Internet Explorer。标准管理HTTP摘要认证由RFC 2617定义,更新摘要式身份验证的一个早期版本由RFC 2069标准规定。大多数用户代理实现RFC 2617。Spring Security的DigestAuthenticationFilter兼容“auth”质量认证的保护(qop)规定的RFC 2617,这也与RFC 2069提供向后兼容性。摘要式身份验证是一个更有吸引力的选择,如果您需要使用未加密的HTTP(即没有TLS / HTTPS)并希望最大化安全的身份验证过程。事实上WebDAV协议摘要式身份验证是一个强制性的要求,指出由RFC 2518 17.1节。 enticationEntryPoint被调用。
DigestAuthenticationFilter
DigestAuthenticationFilter能够消化处理HTTP头部中的认证证书。摘要式身份验证试图解决许多基本身份验证的弱点,特别是保证凭据从不通过线路明文形式发送。许多用户支持摘要式身份验证,包括Firefox和Internet Explorer。标准管理HTTP摘要认证由RFC 2617定义,更新摘要式身份验证的一个早期版本由RFC 2069标准规定。大多数用户代理实现RFC 2617。Spring Security的DigestAuthenticationFilter兼容“auth”质量认证的保护(qop)规定的RFC 2617,这也与RFC 2069提供向后兼容性。摘要式身份验证是一个更有吸引力的选择,如果您需要使用未加密的HTTP(即没有TLS / HTTPS)并希望最大化安全的身份验证过程。事实上WebDAV协议摘要式身份验证是一个强制性的要求,指出由RFC 2518 17.1节可见。
你不应该在现代应用中使用摘要,因为它被认为是不安全的。 最明显的问题是,你必须把您的密码以明文,加密,或一个MD5格式。 所有这些存储格式被认为是不安全的。 相反,你应该使用一种自适应的哈希密码(即BCrypt,PBKDF2,SCrypt等)。
摘要式身份验证的核心是一个”nonce”。这是一个服务器生成的值。Spring Security nonce采用以下格式:
base64(expirationTime + “:” + md5Hex(expirationTime + “:” + key))
expirationTime: The date and time when the nonce expires, expressed in milliseconds
key: A private key to prevent modification of the nonce token
DigestAuthenticatonEntryPoint有一个属性指定用于生成nonce令牌的key,以及nonceValiditySeconds属性确定过期时间(默认300,等于5分钟)。只要nonce是有效的,摘要就会通过串联字符串包括用户名,密码,随机数,被请求的URI,一个客户端产生的随机数(仅该用户代理每个请求生成一个随机值),域名等,然后执行MD5哈希值。如果他们不同意一个包含值(如密码),服务器和用户代理执行此计算消化,就会导致不同的哈希码。在Spring Security实现中,如果服务器生成的nonce已经过期(但是摘要还是有效),在DigestAuthenticationEntryPoint将发送一个”stale=true”头。这告诉用户代理没有必要打扰用户(如用户名和密码等是正确的),而只是采用了全新的随机数再试一次。
对于DigestAuthenticationEntryPoint的参数nonceValiditySeconds而言一个适当的值取决于您的应用程序。非常安全的应用程序应该注意一个拦截认证头可以用来模拟本体,直到expirationTime中包含的特定场合。这是关键的原则,选择适当的设置,但它是不寻常的,非常安全的应用程序不能运行在第一个实例的TLS/HTTPS。
因为摘要式身份验证的更复杂的实现,经常有用户代理的问题。例如,IE不能在同一个会话的请求提出了一个“opaque”令牌。因此Spring Security封装了所有状态信息进入到“nonce”令牌中。在我们的测试中,Spring Security的实现与FireFox和ie能可靠地工作,正确处理nonce超时等。
Configuration
既然我们已经回顾了理论,让我们看看如何使用它。为了实现HTTP摘要认证,必须在过滤器链定义DigestAuthenticationFilter。这个应用上下文将需要定义DigestAuthenticationFilter及其所需的合作者:

id="digestFilter" class=
    "org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="authenticationEntryPoint" ref="digestEntryPoint"/>
<property name="userCache" ref="userCache"/>


id="digestEntryPoint" class=
    "org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">
<property name="realmName" value="Contacts Realm via Digest Authentication"/>
<property name="key" value="acegi"/>
<property name="nonceValiditySeconds" value="10"/>

配置UserDetailsService是必要的因为DigestAuthenticationFilter必须有一个用户的明文密码直接访问。如果您正在使用您的DAO中的编码密码,摘要身份验证将无法工作 [11]. 这个DAO的协作者,与UserCache一起,通常直接与DaoAuthenticationProvider共享。该authenticationEntryPoint属性必须是DigestAuthenticationEntryPoint,使DigestAuthenticationFilter能够获得正确的realmName和key再进行摘要计算。
像BasicAuthenticationFilter,如果认证成功,Authentication请求令牌将被放置到了SecurityContextHolder。如果认证事件成功,或者认证不需要执行,因为HTTP头部没有包含摘要认证请求,过滤器链将继续正常。过滤器链中断的唯一情况是,如果认证失败了AuthenticationEntryPoint被调用,如前一段讨论。
摘要式身份验证的RFC提供了一系列附加功能,以进一步提高安全性。例如,随机数可以在每次请求被改变。尽管如此,Spring Security的实施旨在最大限度地减少执行(和用户代理出现不兼容)的复杂性,并避免保存服务器端的状态。你被邀请审查RFC 2617如果你想更详细地探索这些功能。据我们所知,Spring Security的实现中遵守了RFC的最低标准。
Remember-Me Authentication
Overview
Remember-me或persistent-login身份验证是指网站能够记住一个主体的身份之间的会话。这通常是通过发送cookie给浏览器,以及在未来的会话中发现的cookie,并进行自动登录发生完成的。Spring Security提供了这些操作发生的必要的挂钩,并有两个具体的remember-me实现。其中一个使用散列来保护基于cookie标记的安全性,另一个使用了数据库或其他持久化存储机制来保存生成的标记。
注意,所有实现都需要一个UserDetailsService。如果您正在使用身份验证提供者不使用UserDetailsService(例如,LDAP提供者),那么它不会工作,除非你也有一个UserDetailsService应用程序上下文中的bean。
Simple Hash-Based Token Approach
这种方法使用散列来完成remember-me策略。本质上一个cookie发送到浏览器交互验证成功后,使用的cookie组成结构如下:
base64(username + “:” + expirationTime + “:” +
md5Hex(username + “:” + expirationTime + “:” password + “:” + key))

username: As identifiable to the UserDetailsService
password: That matches the one in the retrieved UserDetails
expirationTime: The date and time when the remember-me token expires, expressed in milliseconds
key: A private key to prevent modification of the remember-me token
因此,remember-me的令牌在指定的期间是有效的,提供的用户名,密码和密钥不会改变。值得注意的是,这有一个潜在的安全问题,任何用户代理在remember-me令牌到期之前捕获到它,令牌将是可用的。这与摘要式身份验证问题是同样的问题。如果本人知道令牌已被抓获,他们可以轻松地更改他们的密码,并立即注销所有的remember-me标记。如果需要更显著的安全性,你应该使用在下一节中描述的方法。另外记得remember-me服务根本不应该被使用。
如果你熟悉在namespace configuration命名空间配置这一章中讨论的主题,你可以启用remember-me认证只需添加元素:


...
"myAppKey"/>

UserDetailsService一般会自动选择。如果你有一个以上的应用环境,你需要指定哪一个应该使用user-service-ref属性,其中的值在你的UserDetailsService bean的名称中设置。
Persistent Token Approach
这种方法是基于文章 http://jaspan.com/improved_persistent_login_cookie_best_practice 有一些小的修改 [12]. 若要使用命名空间配置的这种方法,你应该提供一个数据源引用:


...
source-ref="someDataSource"/>

数据库应该包含一个persistent_logins表,使用以下SQL创建(或同等的):
create table persistent_logins (username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
Remember-Me的接口和实现
Remember-me是用于 UsernamePasswordAuthenticationFilter,并通过AbstractAuthenticationProcessingFilter超类的钩子实现。 它也被运用在BasicAuthenticationFilter. 钩子会在合适的时候调用一个具体RememberMeServices。 该接口看起来像这样:
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);

void loginFail(HttpServletRequest request, HttpServletResponse response);

void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication);
请参考JavaDocs获得什么方法都做了更充分的讨论,不过注意在这个阶段,AbstractAuthenticationProcessingFilter仅调用loginFail()和loginSuccess()方法。当SecurityContextHolder 不包含 Authentication时autoLogin()方法被RememberMeAuthenticationFilter调用。因此,此接口提供了基本的remember-me的实现与认证相关的事件的充分通知,和代表的执行情况时,一个候选Web请求可能包含一个cookie并希望被记住。这种设计允许任何数目的remember-me实现策略。我们已经看到上面Spring Security提供两种实现方法。我们来看看这些。
TokenBasedRememberMeServices
这个实现支持 Simple Hash-Based Token Approach中描述的简单方法。TokenBasedRememberMeServices生成一个RememberMeAuthenticationToken,由RememberMeAuthenticationProvider处理。一个key认证提供者和TokenBasedRememberMeServices之间共享。此外,TokenBasedRememberMeServices需要一个UserDetailsService可以实现检索比较用户名和密码的目的,并生成RememberMeAuthenticationToken包含正确GrantedAuthority的一个UserDetailsService。如果用户提供无效的cookie,应该应用一些注销命令。TokenBasedRememberMeServices还实现了Spring Security的logouthandler 接口,所有可以用LogoutFilter自动清除cookie。
在应用程序环境方面,remember-me服务需要的bean类如下:

id="rememberMeFilter" class=
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="theAuthenticationManager" />


id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>


id="rememberMeAuthenticationProvider" class=
"org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="springRocks"/>

不要忘记添加你的RememberMeServices实现UsernamePasswordAuthenticationFilter.setRememberMeServices()的属性,包括RememberMeAuthenticationProvider在AuthenticationManager.setProviders()中的列表,并添加RememberMeAuthenticationFilter到你的FilterChainProxy(一般在你的UsernamePasswordAuthenticationFilter之后)。
PersistentTokenBasedRememberMeServices
这个类可以以同样的方式作为TokenBasedRememberMeServices被使用,但它另外需要与PersistentTokenRepository存储令牌进行配置。有两种标准实现。
● InMemoryTokenRepositoryImpl 这是只用来测试。
● JdbcTokenRepositoryImpl 其存储在数据库中的标记。
数据库模式如上面所描述 Persistent Token Approach.
Cross Site Request Forgery (CSRF)
本节讨论Spring Security’s Cross Site Request Forgery (CSRF) 支持。
CSRF攻击
在我们讨论Spring Security如何保护应用程序不受CSRF攻击之前,我们将解释什么是CSRF攻击。让我们来看一个具体的例子来更好地理解。
假设你的银行网站提供允许从当前登录的用户转移钱转到另一个银行账户的形式。例如,HTTP请求可能看起来像:
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876
现在假装你身份验证你的银行网站,然后再不注销,请访问一个恶意的网站。恶意的网站包含以下形式的HTML页面:

"https://bank.example.com/transfer" method="post"> type="hidden" name="amount" value="100.00"/> type="hidden" name="routingNumber" value="evilsRoutingNumber"/> type="hidden" name="account" value="evilsAccountNumber"/> type="submit" value="Win Money!"/>

你想赢钱,所以你点击submit按钮。在这个过程中,你无意中转移$100到恶意用户。这是因为,虽然恶意的网站不能看到你的cookies,与银行相关的cookies仍然与请求一起发送。
最糟糕的是,这整个过程可能已经使用JavaScript自动化。这意味着你甚至没有需要点击的按钮。那么我们该如何保护自己免受此类攻击?
同步器标记模式
问题是,从银行网站上的HTTP请求,和从恶意的网站的要求是完全一样的。这就意味着没有办法拒绝来自恶意的网站的请求,允许请求来自银行的网站。为了抵御CSRF攻击我们需要确保请求中有一些恶意的网站是无法提供的。
一种解决方案是使用 同步器标记模式. 这个解决方案是保证每个请求除了需要我们的会话cookie,还用随机生成的令牌作为HTTP参数。提交一个请求时,服务器必须查找参数和比较它的期望值和实际值的请求。如果值不匹配,请求失败。
我们可以轻松的预期,只要更新每个HTTP请求的令牌。这样做可以安全地自同源策略保证了恶意的网站无法读取响应。此外,我们不希望包括HTTP GET随机令牌,因为这可能会导致标记被泄露。
让我们来看看我们的例子将如何改变。假定随机产生的标记存在于一个HTTP参数name_csrf。例如,要转账的要求是这样的:
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=
你会注意到,我们添加了_csrf参数的随机值。现在,恶意网站将无法猜测_csrf参数(必须提供明确的恶意网站),当服务器将实际的令牌与预期的令牌进行比较时,传输将失败。
何时使用CSRF保护
什么时候应该使用CSRF保护?我们的建议是使用CSRF保护,可以通过浏览器处理普通用户的任何请求。如果你只是创建一个非浏览器客户端使用的服务,你可能会想要禁用CSRF保护。
CSRF保护和JSON
一个常见的问题是”我是否需要保护JSON请求的javascript ?”简短的答案是,这取决于你。但是,你必须非常小心,因为有CSRF攻击可以影响JSON请求。例如,一个恶意用户可以使用以下格式,创建一个 CSRF和JSON使用以下form:

"https://bank.example.com/transfer" method="post" enctype="text/plain"> type='hidden'> type="submit" value="Win Money!"/>
这将产生以下JSON结构 { "amount": 100, "routingNumber": "evilsRoutingNumber", "account": "evilsAccountNumber", "ignore_me": "=test" }

如果一个应用程序没有验证内容类型,那么它会接触到这种攻击。根据设置,验证内容类型的Spring MVC应用程序仍然可以利用更新URL后缀结尾”.json”如下所示:

"https://bank.example.com/transfer.json" method="post" enctype="text/plain"> type='hidden'> type="submit" value="Win Money!"/>

CSRF和无状态的浏览器应用程序
如果我的应用程序是无状态的呢?这并不意味着你是受保护的。事实上,如果用户对于一个给定的请求不需要在web浏览器中执行任何操作,他们可能仍然容易受到CSRF攻击。
例如,考虑一个应用程序使用一个定制的cookie,其中包含所有的声明进行身份验证,而不是JSESSIONID。当CSRF是由自定义cookie与在该JSESSIONID cookie在前面的例子中相同的方式发送的,请求被发送。
使用基本身份验证的用户也容易受到CSRF攻击,因为浏览器会自动包括以同样的方式,在我们前面的例子中该JSESSIONID的cookie会发送任何请求的用户名密码。
Using Spring Security CSRF Protection
那么,什么是必要的,使用Spring Security来保护我们的网站不受CSRF攻击的步骤是什么?使用Spring Security的CSRF保护的步骤如下所述:
● 使用适当的HTTP动词
● 配置CSRF保护
● 包括CSRF令牌
使用适当的HTTP动词
防止CSRF攻击的第一步是确保你的网站使用适当的HTTP动词。具体来说,前Spring Security的CSRF支持可以使用,你需要确定你的应用程序使用PATCH,POST, PUT, and/or DELETE对于任何修改状态。
这不是一个Spring安全限制的支持,而是进行适当的预防CSRF的一般要求。原因是包括在一个HTTP GET私人信息可能导致的信息泄露。看到 RFC 2616 Section 15.1.3在URI的敏感信息编码 一般的指导对于敏感信息使用POST而不是GET。
配置CSRF保护
下一步是要包括你的应用程序中的Spring Security的CSRF保护。有些框架处理无效CSRF无效用户的会话令牌,而这将导致 其自身的问题。相反,在默认情况下Spring Security的CSRF保护将产生一个HTTP 403访问被拒绝。这可以通过配置AccessDeniedHandler以不同方式处理。
Spring Security 4.0,CSRF保护与XML配置默认启用。如果你想禁用CSRF保护,下面可以看到相应的XML配置。

<http>
    
    <csrf disabled="true"/>
http>

CSRF保护默认情况下使用Java配置启用。如果您想禁用CSRF,下面可以看到相应的Java配置。请参考Javadoc中的CSRF()在CSRF保护是如何自定义配置的。

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .csrf().disable();
}
}

包括CSRF令牌
Form Submissions
最后一步是确保您包括CSRF令牌在所有补丁,POST,PUT和DELETE方法。一个方法是使用_csrf请求属性来获取当前CsrfToken。用一个JSP这样做的一个示例如下所示:

"logoutUrl" value="/logout"/>
"${logoutUrl}" method="post"> type="submit" value="Log out" /> type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

一个更容易的方法是从Spring Security的JSP标签库使用CSRF输入标签 。
如果你正在使用Spring MVC的标签或 Thymeleaf 2.1+ 和使用 @EnableWebSecurity, CsrfToken是自动包括 (使用CsrfRequestDataValueProcessor).
Ajax和JSON请求
如果使用JSON,那么它是不可能在HTTP参数提交CSRF令牌。相反,你可以提交HTTP报头中的令牌。一个典型的模式是将包括您的meta标签内的CSRF令牌。用一个JSP一个例子如下所示:

<html>
<head>
    <meta name="_csrf" content="${_csrf.token}"/>
    
    <meta name="_csrf_header" content="${_csrf.headerName}"/>
    
head>

而不是手动创建meta标签,您可以从Spring Security JSP标记库中使用简单的csrfMetaTags标签。
然后,您可以包含所有Ajax请求中的令牌。如果你是使用jQuery,这将通过以下:

$(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
    xhr.setRequestHeader(header, token);
});
});

作为一种替代的jQuery,我们建议使用 cujoJS’s rest.js. The rest.js模块提供先进的支持以RESTful方式处理HTTP请求和响应。核心能力是能够说明链接所需的HTTP客户端添加行为拦截器客户端。

var client = rest.chain(csrf, {
token: $("meta[name='_csrf']").attr("content"),
name: $("meta[name='_csrf_header']").attr("content")
});

所配置的客户端可以与需要一个到CSRF保护资源的请求的应用程序共享任何组件。rest.js和jQuery之间有一个显著的不同是只有配置客户端的请求将包含CSRF令牌,vs jQuery将包括所有请求令牌。请求接收令牌能力的范围有助于防止泄露CSRF令牌给第三方。请参阅 rest.js 参考文档获取更多关于rest.js的信息。
CookieCsrfTokenRepository
可能有些情况下,用户想要坚持CSRF Token在cookie中。 默认情况下CookieCsrfTokenRepository将编写一个名为 XSRF-TOKEN的cookie和从头部命名 X-XSRF-TOKEN中读取或HTTP参数 _csrf。 这些默认值来自 AngularJS
你可以使用下面的XML配置CookieCsrfTokenRepository:

<http>
    
    <csrf token-repository-ref="tokenRepository"/>
http>
<b:bean id="tokenRepository"
    class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
    p:cookieHttpOnly="false"/>

示例显式地设置cookieHttpOnly=false. 这是必要的,以允许JavaScript(即AngularJS)读取它。 如果你不需要使用JavaScript直接读取cookie的能力,,建议省略cookieHttpOnly=false来提高安全性。
你可以配置CookieCsrfTokenRepository在Java配置中使用:

@EnableWebSecurity
public class WebSecurityConfig extends
        WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    }
}

示例显式地设置cookieHttpOnly=false. 这是必要的,允许JavaScript(例如AngularJS)读取它。 如果你不需要使用JavaScript直接读取cookie的能力,建议省略 cookieHttpOnly=false (通过使用new CookieCsrfTokenRepository()代替) 提高安全性.
CSRF警告
在实现CSRF时有几个注意事项.
Timeouts
一个问题是,预期CSRF令牌存储在HttpSession中,所以一旦HttpSession到期你的配置将收到一个InvalidCsrfTokenException AccessDeniedHandler。如果您使用的是默认的AccessDeniedHandler,浏览器将会得到一个HTTP 403并显示错误消息。
有人可能会问,为什么预期CsrfToken没有存储在默认的cookie。这是因为在该首部(即,指定的Cookie)可以被另一个域设置公知的漏洞。这是一样的道理Ruby on Rails no longer skips CSRF checks when the header X-Requested-With is present. 见 这个webappsec.org线程 关于如何执行的细节漏洞。另一个缺点是,通过除去状态(即超时)丢失强行终止该令牌,损失它的能力。
一个简单的方法,有一些JavaScript可以减轻一个活跃的用户体验超时,让用户知道他们的会话即将到期。用户可以点击一个按钮继续,并刷新会话。
另外,指定一个自定义的AccessDeniedHandler允许你以任何你喜欢的方式处理InvalidCsrfTokenException。 有关如何自定义AccessDeniedHandler的例子参考提供的链接为 xml 和 Java configuration.
最后,应用程序可以配置为使用 CookieCsrfTokenRepository这将不会过期。 正如前面提到的,这不是安全的使用一个会话,但在许多情况下已经足够好。
Logging In
为了防止 forging登录请求 在登录表单中应该也要防止CSRF攻击。因为CsrfToken存储在HttpSession中,这意味着一个HttpSession将尽快创建CsrfToken令牌属性进行访问。 虽然这听起来很糟糕,一个RESTful / stateless的体系结构对于实现实际的安全是必要的。如果没有的状态,我们什么都没有,如果令牌被攻破,我们可以做的。实事求是地讲,在CSRF令牌的尺寸非常小,应该对我们的架构可以忽略不计的影响。如果没有状态,如果令牌被攻破,我们没有什么可以做的。实际上,CSRF令牌的规模和体系结构很小应该对我们的架构有一个微不足道的影响。
一种保护登录常用技术是使用一个javascript函数在表单提交之前获得一个有效的CSRF令牌。通过这样做,没有必要去想会话超时因为表单提交前有创建会话(在上一节中讨论) (假设 CookieCsrfTokenRepository 没有配置而不是), 因此,用户当他想提交用户名/密码可以停留在登录页面上为了达到这个目标,你可以采取Spring Security提供的CsrfTokenArgumentResolver的优势,并像它在这里描述暴露的端点。
Logging Out
添加CSRF将更新LogoutFilter只使用HTTP POST。这确保了注销需要CSRF令牌和一个恶意的用户不能强制注销用户。
一种方法是使用一种形式的日志。如果你真的想要一个链接,你可以(即也许隐藏表单)使用JavaScript,让链接执行POST。对于禁止使用JavaScript的浏览器,您可以选择有链接的用户注销确认页面执行POST。
如果你真的想使用HTTP GET注销你可以这样做,但请记住这一般不推荐。例如,下面的Java配置将执行任意的HTTP方法请求URL /logout执行注销:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
    }
}

Multipart (文件上传)
有两个选项来使用CSRF保护multipart/form-data.每个选项都有其权衡。
● Placing MultipartFilter before Spring Security
● Include CSRF token in action
之前你和multipart集成Spring Security的CSRF保护文件上传,确保没有CSRF保护你可以第一时间上传。使用multipart表单与Spring的更多信息可以在 17.10 Spring’s multipart (文件上传) 支持 的Spring参考和 MultipartFilter javadoc.
Placing MultipartFilter before Spring Security
第一个选项是确保MultipartFilter在Spring Security过滤器之前指定。Spring Security过滤器之前指定MultipartFilter意味着没有授权调用MultipartFilter这意味着任何人都可以服务器上上传临时文件。不过,只有授权的用户可以将文件提交由您的应用程序处理。总的来说,这是推荐的方法,因为临时文件上传在大多数服务器上应该只有一个微不足道的影响。
为了确保MultipartFilter是Spring Security的过滤器,在Java配置前指定,用户可以重写beforespringsecurityfilterchain如下所示:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        insertFilters(servletContext, new MultipartFilter());
    }
}

为了确保MultipartFilter是Spring Security的过滤器,在XML配置前指定,用户可以确定MultipartFilter的元素在springSecurityFilterChain前在web.xml中配置如下所示:

<filter>
    <filter-name>MultipartFilterfilter-name>
    <filter-class>org.springframework.web.multipart.support.MultipartFilterfilter-class>
filter>
<filter>
    <filter-name>springSecurityFilterChainfilter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
    <filter-name>MultipartFilterfilter-name>
    /*
filter-mapping>
<filter-mapping>
    <filter-name>springSecurityFilterChainfilter-name>
    /*
filter-mapping>

Include CSRF token in action
如果允许未经授权的用户上传temporariy文件是不能接受的,另一种是将Spring Security过滤后的MultipartFilter和CSRF作为查询参数包含在表单的动作属性。在jsp示例如下所示

"./upload?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">

这种方法的缺点是,查询参数可以被泄露。更普遍的是,最佳的方法是将敏感数据在身体或头部,以确保不泄露。更多信息可以在 RFC 2616 Section 15.1.3 编码URI的敏感信息中找到。
HiddenHttpMethodFilter
该HiddenHttpMethodFilter应放在Spring Security的过滤器之前。一般来说这是事实,但它可能能够对防止CSRF攻击有更多的影响。
请注意,HiddenHttpMethodFilter只覆盖一个POST HTTP方法,所以这实际上是不可能造成任何实际问题。但是,它仍然是最好的做法,以确保它被放置在Spring Security过滤器之前。
重写默认值
Spring Security的目标是提供默认值,保护用户免受攻击。这并不意味着你被迫接受所有的默认设置。
例如,您可以提供一个自定义CsrfTokenRepository覆盖其中CSRF Token的存储方式。
您还可以指定一个自定义RequestMatcher确定哪些请求受CSRF保护(即也许你不在乎注销是利用)。简而言之,如果Spring Security CSRF保护没有做出你想要的,你可以自定义行为。 请参阅 将添加元素文档的细节如何自定义使用XML和CsrfConfigurer javadoc,了解如何使用Java配置将这些自定义设置。
CORS
Spring框架提供了 CORS第一级支持。 CORS必须在Spring Security之前处理因为预检要求将不包含任何cookie(即JSESSIONID)。 如果第一个请求不包含任何cookie和Spring Security,将决定用户的请求没有被验证(因为有请求没有cookies),并拒绝它。
最简单的方法就是确保CORS首先是使用CorsFilter处理。 用户可以把CorsFilter和Spring Security 提供一个 CorsConfigurationSource使用如下:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // by default uses a Bean by the name of corsConfigurationSource
            .cors().and()
            ...
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

或在XML中


    source-ref="corsSource"/>
    ...

"corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">
    ...

如果你正在使用Spring MVC的CORS支持,你可以省略指定CorsConfigurationSource和Spring Security将利用CORS配置提供给Spring MVC。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // if Spring MVC is on classpath and no CorsConfigurationSource is provided,
            // Spring Security will use CORS configuration provided to Spring MVC
            .cors().and()
            ...
    }
}

或在XML中

<http>
    
    <cors />
    ...
http>

安全HTTP响应头
本节讨论Spring Security支持将各种安全头添加到响应。
默认的安全头
Spring Security允许用户轻松地注入的默认安全标头来帮助保护他们的应用程序。Spring Security默认是包括以下头部:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security只有添加HTTPS请求
有关这些标题的更多详细信息,请参见相应的章节:
● Cache Control
● Content Type Options
● HTTP Strict Transport Security
● X-Frame-Options
● X-XSS-Protection
而这些头文件被认为是最佳方法,应该注意的是,并不是所有的客户端利用头部,所以鼓励进行额外的测试。
您可以自定义特定的头文件。 例如,假设希望你的HTTP响应头部为以下的样子:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
具体而言,你希望自定义所有的默认标题与下列:
● X-Frame-Options允许来自同一个域中的任何请求
● HTTP Strict Transport Security (HSTS) 将不会被添加到响应
你可以很容易地用下面的Java配置做到这一点:

@EnableWebSecurity
public class WebSecurityConfig extends
        WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // ...
            .headers()
                .frameOptions().sameOrigin()
                .httpStrictTransportSecurity().disable();
    }
}

另外,如果你正在使用Spring Security的XML配置,你可以使用下面的:

<http>
    

    <headers>
        <frame-options policy="SAMEORIGIN" />
        <hsts disable="true"/>
    headers>
http>

如果你不想要添加默认值,需要明确控制应该使用什么,您可以禁用默认值。一个例子提供了Java和基于XML的配置:
如果您正在使用Spring安全的Java配置以下只会增加 Cache Control.

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        // do not use any default headers unless explicitly listed
        .defaultsDisabled()
        .cacheControl();
}
}

下面的XML只会增加Cache Control.

<http>
    

    <headers defaults-disabled="true">
        <cache-control/>
    headers>
http>

如果有必要,你可以禁用所有的HTTP安全响应头下面的Java配置:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers().disable();
}
}

如果有必要,你可以禁用所有的HTTP安全响应头下面的XML配置:

<http>
    

    <headers disabled="true" />
http>

缓存控制
在过去的Spring Security要求你提供自己的缓存控制您的web应用程序。这似乎是合理的,但是浏览器缓存已经进化到包括缓存安全连接。这意味着用户可以查看经过身份验证的页面,注销,然后恶意用户可以使用浏览器历史记录查看缓存页面。为了帮助缓解Spring Security增加了对高速缓存控制的支持,这将插入以下头部作为你的回应。
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
简单的添加<头部 没有子元素的元素会自动添加缓存控制和不少其他保护措施。 不过,如果你只想要缓存控制,你可以使用Spring Security的XML命名空间与 元素和headers@defaults-disabled 属性。

<http>
    

    <headers defaults-disable="true">
        <cache-control />
    headers>
http>

同样,您可以在Java配置中启用缓存控制类似以下:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .defaultsDisabled()
        .cacheControl();
}
}

如果你真的想要缓存特定的反应,您的应用程序可以选择性地调用 HttpServletResponse.setHeader(String,String) 覆盖头部Spring Security的设置。为了保证CSS,JavaScript之类的东西是用的并且图像正确缓存。
当使用Spring Web MVC,这通常是在你的配置中。例如,下面的配置设置将确保缓存头你所有的资源:

@EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
            .addResourceHandler("/resources/**")
            .addResourceLocations("/resources/")
            .setCachePeriod(31556926);
    }

    // ...
}

内容类型选项
历史上的浏览器,包括Internet Explorer,试图想请求的内容类型使用 content sniffing。这就使得浏览器通过猜测来改善用户体验的内容类型没有指定内容类型的资源。例如,如果一个浏览器遇到一个JavaScript文件,该文件没有指定的内容类型,它会猜内容类型,然后执行它。
有许多额外的一个人应该做的事情(即只显示文档在不同的领域,确保设置内容类型头,清理文件等等)时,从而允许内容上传。然而,这些措施的范围之外的Spring Security提供什么。同样重要的是指出禁用content sniffing的时候,你必须要指定内容类型的东西才能正常工作。
content sniffing的问题是,这允许恶意用户使用polyglots(即一个文件,是作为多种内容类型有效)来执行XSS攻击。例如,某些网站可能会允许用户提交一个有效的PostScript文档到网站,并查看它。恶意用户可能会创建一个 postscript文件,这也是一个有效的JavaScript文件 并用它执行XSS攻击。
通过添加以下content sniffing可以禁用我们的响应头:
X-Content-Type-Options: nosniff
就像缓存控制元件,nosniff指令添加默认情况下使用元素时,没有子元素。 然而,如果你想要更多的控制,添加头文件可以使用 元素和headers@defaults-disabled属性,如下所示:

<http>
    

    <headers defaults-disabled="true">
        <content-type-options />
    headers>
http>

X-Content-Type-Options头默认情况下使用Spring Security添加Java配置。如果你想要更多的控制头,您可以显式地指定以下内容类型选项:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .defaultsDisabled()
        .contentTypeOptions();
}
}

HTTP Strict Transport Security (HSTS)
当你输入你的银行的网站,你输入mybank.example.com 或者你输入 https://mybank.example.com? 如果您省略https协议,你可能容易受到 中间人攻击。即使网站执行重定向到 https://mybank.example.com 恶意用户能够拦截最初的HTTP请求和操作响应(即重定向到 https://mibank.example.com 和窃取他们的凭证)。
很多用户忽略了https协议,这就是为什么 HTTPHTTP严格传输安全(HSTS) 已创建。一旦mybank.example.com 添加为 HSTS host,浏览器可以提前知道任何请求 mybank.example.com 应该解读为 https://mybank.example.com.这大大降低了发生中间人攻击的可能性。
按照 RFC6797, HSTS头仅注入到HTTPS响应。为了让浏览器承认头,浏览器必须先信任CA签署的用于建立连接的SSL证书(不只是SSL证书)。
一个网站被标记为HSTS主机有主机预装到浏览器的一个方法。另一种是将”Strict-Transport-Security”头添加到响应。例如,以下将指示浏览器把域作为一年的HSTS主机(一年有大约31536000秒):
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
可选includeSubDomains指令指示Spring安全子域(即secure.mybank.example.com)也应该被视为一个 HSTS域。
至于其它头部,Spring Security的默认添加HSTS。您可以自定义HSTS头部与元素如下所示:

<http>
    

    <headers>
        <hsts
            include-subdomains="true"
            max-age-seconds="31536000" />
    headers>
http>

类似地,您可以只启用HSTS头与Java配置:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .httpStrictTransportSecurity()
            .includeSubdomains(true)
            .maxAgeSeconds(31536000);
}
}

HTTP Public Key Pinning (HPKP)
HTTP Public Key Pinning (HPKP) 是一个安全特性,它讲述了一个web客户端关联特定加密公钥与一个特定的web服务器以防止中间人(MITM)攻击和伪造的证书。
为了确保在TLS会话中使用服务器的公钥的真实性,这个公钥被包装成一个X。509证书通常由一个证书颁发机构(CA)签名。Web客户端如浏览器信任了很多这些CA,这都可以创建任意域名证书。 如果攻击者能够破坏一个CA,他们可以执行各种TLS连接MITM攻击。 HPKP可以规避HTTPS协议这一威胁,告诉客户端公钥属于一个特定的web服务器。 HPKP是在首次使用(TOFU)技术信托。第一次web服务器经由该公钥属于它的特殊HTTP头告诉客户端,客户端存储此信息的给定时间段。 当客户端访问服务器,它预计包含公钥证书的指纹通过HPKP已知。如果服务器提供一个未知的公共密钥,客户端应呈现警告给用户。
因为用户代理需要验证对SSL证书链的链销,HPKP头只是注入HTTPS反应。
为你的网站启用这个特性很简单,当在HTTPS访问你的网站时返回Public-Key-Pins HTTP头。 例如,以下将指导用户代理只报告密码验证失败的给定URI(via the report-uri directive) 2 引脚:
Public-Key-Pins-Report-Only: max-age=5184000 ; pin-sha256=”d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=” ; pin-sha256=”E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=” ; report-uri=”http://example.net/pkp-report” ; includeSubDomains
一个 pin 验证失败 report 是一个标准的JSON结构,可以捕获通过web应用程序的API或HPKP主办的公开报告服务, 比如, REPORT-URI.
可选的includeSubDomains指令指示浏览器也与给定引脚验证子域。
相对于其他的头部,Spring Security没有默认添加HPKP。你可以定制HPKP头与 元素如下所示:

<http>
    

    <headers>
        <hpkp
            include-subdomains="true"
            report-uri="http://example.net/pkp-report">
            <pins>
                    <pin algorithm="sha256">d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=pin>
                    <pin algorithm="sha256">E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=pin>
            pins>
        hpkp>
    headers>
http>

同样,您可以启用Java配置HPKP头:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
                http
                // ...
                .headers()
                        .httpPublicKeyPinning()
                                .includeSubdomains(true)
                                .reportUri("http://example.net/pkp-report")
                                .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
        }
}

X-Frame-Options
允许给你的网站添加框架可能是一个安全问题。例如,使用巧妙的CSS样式用户可能会被欺骗点击的东西,他们不打算 (video demo)。 例如,登录到他们的银行用户可能会点击一个按钮授予其他用户访问。这种攻击被称为 Clickjacking.
另一种现代的方式来处理点击劫持是使用 [headers-content-security-policy].
有很多方法来缓解点击劫持攻击。例如,为了保护您可以使用传统的点击劫持的攻击的浏览器 frame breaking code. 虽然并不完美,但断码的框架是可以为传统的浏览器做的最好的。
解决点击劫持更先进的方法是使用 X-Frame-Options 头:
X-Frame-Options: DENY
X-Frame-Options响应头指示浏览器阻止任何网站,这个头在响应中被呈现在一个框架中。 默认情况下,Spring Security禁用iframe内呈现。
你可以定制X-Frame-Options和 frame-options 元素。 例如,以下将指示Spring Security用 “X-Frame-Options: SAMEORIGIN” 允许iframes在同一个域:

<http>
    

    <headers>
        <frame-options
        policy="SAMEORIGIN" />
    headers>
http>

同样,您可以自定义框架选项在Java配置中使用相同的起源:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .frameOptions()
            .sameOrigin();
}
}

X-XSS-Protection
有些浏览器已经内置支持过滤掉 reflected XSS attacks。这决不是完整的证明,但协助XSS的保护。
过滤通常是默认启用,所以添加头部通常只是确保它被启用和指示浏览器检测到XSS攻击时该做什么。例如,过滤器可能试图改变的内容至少入侵方式仍然呈现一切。有时,这种类型的替代可以成为 XSS 漏洞本身。相反,它是最好的阻止内容,而不是试图修复它。要做到这一点,我们可以添加以下头部:
X-XSS-Protection: 1; mode=block
在默认情况下已经包括了这个头。然而,我们可以自定义它,如果我们想。 例如:

<http>
    

    <headers>
        <xss-protection block="false"/>
    headers>
http>

同样,您可以在Java配置中自定义XSS保护如下:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .xssProtection()
            .block(false);
}
}

Content Security Policy (CSP)
Content Security Policy (CSP)是一个web应用程序可以利用的机制来减轻内容注入漏洞,如跨站点脚本(XSS)。 CSP是一种声明性政策,为web应用程序提供了一个工具的作者声明,并最终通知客户端(用户代理)对web应用程序的来源将加载资源。
内容安全策略不是为了解决所有的内容注入漏洞。相反,CSP可以利用,以帮助减少因内容注入攻击的危害。作为防御的第一道防线,Web应用程序的作者应该验证它们的输入和编码它们的输出。
web应用程序可以通过使用CSP使用下列其中一个HTTP头的响应:
● Content-Security-Policy
● Content-Security-Policy-Report-Only
每个这些头被用作一种机制,以提供一个 security policy 到客户端。 安全策略包含了一组 security policy directives (例如, script-src 和 object-src), 每个负责宣布限制为特定的资源表示形式。
例如,Web应用程序可以声明,预计加载特定,可信来源的脚本,通过再响应以下头部:
Content-Security-Policy: script-src https://trustedscripts.example.com
试图加载脚本以外另一个来源是什么中声明script-src指令将被用户代理。 此外,如果 report-uri 指令在安全政策声明,然后违反将由用户代理来声明的URL进行报告.
例如,如果一个web应用程序违反了安全政策声明,下面的响应头将指导用户代理发送违反政策的报告中指定的URL report-uri 指令。
Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/
Violation reports 是标准JSON结构,可以捕获通过web应用程序的API或公开托管CSP违反报告服务,比如, REPORT-URI.
Content-Security-Policy-Report-Only头提供了web应用程序的作者的能力和管理员监控安全策略,而不是执行它们。 这个头通常用在当试验and/or开发一个网站的安全策略。 当一个政策被认为是有效的,它可以执行使用 Content-Security-Policy 头字段。
鉴于以下响应头,政策声明脚本可能加载两种可能的来源。
Content-Security-Policy-Report-Only: script-src ‘self’ https://trustedscripts.example.com; report-uri /csp-report-endpoint/
如果违反了这一政策,试图从 evil.加载脚本, 用户代理将发送一个违反报告宣布 _report-uri 指令指定的URL,但仍然允许违反资源负载。
Configuring Content Security Policy
重要的是要注意 Spring Security 不添加内容默认安全策略。 web应用程序的作者必须声明安全策略执行and/or对受保护的资源进行监测。
例如,下面的安全策略:
script-src ‘self’ https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/
你可以使用XML配置启用CSP头与

<http>
    

    <headers>
        <content-security-policy
            policy-directives="script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/" />
    headers>
http>

要启用CSP ‘report-only’头部, 配置元素如下所示:

<http>
    

    <headers>
        <content-security-policy
            policy-directives="script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/"
            report-only="true" />
    headers>
http>

类似地,您可以使用Java配置启用CSP头,如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/");
}
}

要启用CSP ‘report-only’ 头部, 提供以下Java配置:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
        .reportOnly();
}
}

Additional Resources
内容安全策略应用到一个web应用程序通常是一个不简单的事情。 以下资源可以为你的网站发展有效的安全策略提供进一步的援助。
An Introduction to Content Security Policy
CSP Guide - Mozilla Developer Network
W3C Candidate Recommendation
Custom Headers
Spring安全机制,使它更加方便的将共同安全头添加到您的应用程序。并且,它还提供了钩子,能够添加自定义头部。
Static Headers
也许有些时候你希望注入自定义的安全标头不支持您的应用程序的。例如,下面的自定义安全头:
X-Custom-Security-Header: header-value
当使用XML命名空间,这些头部可以添加到响应使用

元素如下所示:

<http>
    

    <headers>
        <header name="X-Custom-Security-Header" value="header-value"/>
    headers>
http>

同样,头部可以在Java配置中添加到响应如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header","header-value"));
}
}

Headers Writer
当命名空间或Java配置不支持你想要的头部你可以自定义一个HeadersWriter 实例甚至提供一个自定义实现的 HeadersWriter。
让我们看看一个例子使用一个自定义的XFrameOptionsHeaderWriter的实例。也许你想允许框架内容有着相同的起源。这很容易支持通过设置 policy 属性 “SAMEORIGIN”, 但让我们看看一个更明确的例子使用 ref 属性。

<http>
    

    <headers>
        <header ref="frameOptionsWriter"/>
    headers>
http>

<beans:bean id="frameOptionsWriter"
    class="org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter"
    c:frameOptionsMode="SAMEORIGIN"/>

我们还可以限制框架的内容为与Java配置相同的起源:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN));
}
}

DelegatingRequestMatcherHeaderWriter
有时你可能想要只对某些请求写头部。例如,也许你想只保护你的登录页面不被攻击,你可以使用 DelegatingRequestMatcherHeaderWriter 。 当使用XML命名空间配置, 这可以如下所示:

<http>
    

    <headers>
        <frame-options disabled="true"/>
        <header ref="headerWriter"/>
    headers>
http>
<beans:bean id="headerWriter"
    class="org.springframework.security.web.header.writers.DelegatingRequestMatcherHeaderWriter">
    <beans:constructor-arg>
        <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"
            c:pattern="/login"/>
    beans:constructor-arg>
    <beans:constructor-arg>
        <beans:bean
            class="org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter"/>
    beans:constructor-arg>
beans:bean>

我们还可以使用java配置防止框架的内容到登录页面:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    RequestMatcher matcher = new AntPathRequestMatcher("/login");
    DelegatingRequestMatcherHeaderWriter headerWriter =
        new DelegatingRequestMatcherHeaderWriter(matcher,new XFrameOptionsHeaderWriter());
    http
    // ...
    .headers()
        .frameOptions().disabled()
        .addHeaderWriter(headerWriter);
}
}

会话管理
HTTP会话相关的功能性是通过SessionManagementFilter和SessionAuthenticationStrategy接口结合来处理,筛选出一个代表.典型用法包括会话固定保护攻击预防,检测到会话超时并且限制有多少会话经过身份验证的用户可以同时打开。
会话管理过滤器
SessionManagementFilter检测 SecurityContextRepository的内容违背了现在SecurityContextHolder的内容来决定是否在当前请求用户已经通过身份验证,通常由一个非交互式的身份验证机制,如验证或记脚注:[身份验证机制验证后进行重定向(例如form-login)将不会被SessionManagementFilter检测到,过滤器将不调用身份验证请求,会话管理功能必须在这些情况下分别处理].如果存储库包含一个安全文档,过滤器什么都不做,如果这没有这么做,局部线程SecurityContext包含了一个(不是匿名)Authentication对象.
如果用户当前没有经过身份验证,过滤器将检查是否一个无效的请求会话ID(例如由于超时),并将调用InvalidSessionStrategy的配置,如果一个被设置了.最通常的行为是重定向到一个固定的URL,这是将实现SimpleRedirectInvalidSessionStrategy进行实施.无效的会话时后者也会使用当通过命名空间配置.as described earlier.
会话的身份验证策略
SessionAuthenticationStrategy通过SessionManagementFilter 和AbstractAuthenticationProcessingFilter被利用,所以如果你利用了一个定制的form-login例如,你将需要把它注入到这两个,在这种情况下,一个典型的配置,结合命名空间和自定义的beans可能看起来像这样 :

<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
"myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    "sessionAuthenticationStrategy" ref="sas" />
    ...


"sas" class=
"org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />

注意默认的使用,SessionFixationProtectionStrategy可能会导致问题,如果你是存储在session中的bean实施HttpSessionBindingListener,包括Spring session-scoped beans,看这个类中的javadoc寻找更多信息.
并发控制
Spring Security能防止主体同时访问同一应用程序的多个指定的次数,许多软件公司利用这一强制许可.虽然网络管理员喜欢这个功能,因为它有助于防止人们共享登录名.例如你能停止”Batman”用户从两个不同的会话登录到Web应用程序 ,您可以终止他们的以前的登录,或者当他们尝试再次登录时,你可以报告一个错误 ,预防第二次登陆,请注意,如果您使用的第二种方法,一个没有显式退出的用户(例如谁刚刚关闭了浏览器)将无法再次登录,直到他们的原始会话过期.
并发控制是由命名空间支持的,所以请检查早期的命名空间章节的最简单的配置。虽然有时你需要自定义的东西。
该实现使用了一个SessionAuthenticationStrategy的专门的版本,叫做ConcurrentSessionControlAuthenticationStrategy.
以前并发的身份验证检查通过ProviderManager制作,由ConcurrentSessionController注射,后者将检查用户是否试图超过允许的会话数.尽管如此,这种方法需要一个HTTP会话提前被创建.这是不受欢迎的,在Spring Security 3, 用户首先经过AuthenticationManager进行身份验证,而且一旦他们成功地通过身份验证,就会创建一个会话,并检查是否允许他们有另一个会话打开.
要使用并发会话支持,您需要添加以下 web.xml:


    
    org.springframework.security.web.session.HttpSessionEventPublisher
    

除此之外,你将需要添加ConcurrentSessionFilter 到你的FilterChainProxy.ConcurrentSessionFilter要求两个属性,sessionRegistry一般是指一个SessionRegistryImpl的实例,expiredUrl指向页面时显示会话已过期的页面,一个配置利用命名空间去创建FilterChainProxy和不符合要求的beans如下:

<http>
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />

<session-management session-authentication-strategy-ref="sas"/>
</http>
id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
property name="sessionRegistry" ref="sessionRegistry" />
property name="expiredUrl" value="/session-expired.htm" />


id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
property name="sessionAuthenticationStrategy" ref="sas" />
property name="authenticationManager" ref="authenticationManager" />


id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">

    list>
    class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
        ref="sessionRegistry"/>
        property name="maximumSessions" value="1" />
        property name="exceptionIfMaximumExceeded" value="true" />
    
    class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
    
    class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
        ref="sessionRegistry"/>
    
    list>



id="sessionRegistry"
    class="org.springframework.security.core.session.SessionRegistryImpl" />

每一次HttpSession开始或者结束,添加一个监听到web.xml 导致一个ApplicationEvent被发布到Spring ApplicationContext.这很关键,因为当一个会话结束时这个允许SessionRegistryImpl被通知,即使他们退出了另一个会话或它超时.
查询sessionregistry目前身份验证的用户和他们的会话
建立并发控制通过命名空间或使用bean具有有益的副作用,为您提供一个参考的sessionregistry 可以直接应用在在您的应用程序.因此,即使你不想限制用户会话的数量,它可能值得建立基础设施.你可以建立maximumSession财产到- 1允许无限的会话.如果你使用的命名空间,您可以利用session-registry-alias属性为内部创建SessionRegistry的别名设置.提供一个参考,你可以注射到你自己的bean.
getAllPrincipals()方法为您提供当前身份验证的用户的列表.您可以列出用户会话通过呼叫getAllSessions(Object principal, boolean includeExpiredSessions)方法,返回SessionInformation对象列表.您也可以终止用户会话通过在SessionInformation实例中呼叫expireNow(),当用户返回到应用程序时,他们将被阻止进行。您可能会发现这些方法在一个管理应用程序中是有用的,例如.看看javadoc的更多信息
匿名身份验证
概述
采取 “deny-by-default”在您显式指定什么是允许和禁止一切,它通常被认为是良好的安全实践.定义什么是未经身份验证的用户访问的是一个类似的情况,特别是用于Web应用程序.许多网站要求用户必须对其他比一些网址的任何东西进行身份验证(例如,主界面和登录页面).在这种情况下,它是最简单的定义访问这些特定的网址的访问配置属性,而不是为每一个安全的资源.不同的,在默认情况下role_something要求很好说,但有时候也有例外.例如一个应用的登陆,注销,和主页面.你也可以从过滤器链完全忽略这些页面,从而绕过访问控制检查,但这可能是不受欢迎的其他原因,特别是如果经过身份验证的用户的页面的行为会有所不同.
这就是我们所说的匿名身份验证,请注意,没有真正的概念区别用户”anonymously authenticated”和未经过身份验证的用户.Spring Security的匿名身份验证只给您一个更加方便的方式来配置您的访问控制属性.如getCallerPrincipal调用servlet API,仍将返回null,尽管实际上SecurityContextHolder中的一个匿名认证对象.
还有其他的匿名身份验证的情况下是有用的,例如,当一个审计拦截器查询SecurityContextHolder来识别哪个主要是负责一个给定的操作.类可以创建更强劲,如果他们知道SecurityContextHolder总是包含一个“身份验证”对象,而且从不null.
配置
匿名认证提供支持自动使用HTTP配置Spring Security 3时可定制(或禁用)使用元元素.您不需要配置这里所描述的beans,除非你使用传统的bean配置.
三个类 一起提供匿名身份验证功能.AnonymousAuthenticationToken 是一个实现 Authentication ,商店的GrantedAuthority 适用于匿名委托,有相应的 anonymousauthenticationprovider ,这是链接到 providermanager ,以至于AnonymousAuthenticationToken能接受.最后,有一个anonymousauthenticationfilter ,这是把正常的认证机制后,自动添加一个 anonymousauthenticationtoken 的 securitycontextholder,如果没有现有的身份验证在那里举行.筛选器和身份验证提供程序的定义如下 :

id="anonymousAuthFilter"
    class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="foobar"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>


id="anonymousAuthenticationProvider"
    class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>

在筛选器和身份验证提供程序之间共享“key”,使前者所创建的符号被后者的脚注所接受[ key属性的使用不应被视为在这里提供任何真正的安全性.它仅仅是一个簿记锻炼, 如果你是共享一个providermanager 包含 anonymousauthenticationprovider ,在这样的情形下,它是可能的认证客户端构建Authentication对象(如RMI调用),然后,一个恶意的客户端可以提交一个anonymousauthenticationtoken ,它创造了自己 (选择的用户名和权限列表 ).如果key是人或能被发现,然后代表将被匿名者接受,这不是正常使用的问题,但如果你使用RMI你最好使用一个定制的没有匿名的providermanager 提供者而不是分享你使用HTTP认证机制. ]. userattribute 表达的形式 usernameintheauthenticationtoken,授予权威,这是相同的语法使用等号后的InMemoryDaoImpl的`userMap”属性.
如前所述,匿名身份验证的好处是,所有URI模式可以安全应用。例如

<bean id="filterSecurityInterceptor"
    class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="securityMetadata">
    <security:filter-security-metadata-source>
    <security:intercept-url pattern='/index.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
    <security:intercept-url pattern='/hello.htm' access='ROLE_ANONYMOUS,ROLE_USER'/>
    <security:intercept-url pattern='/logoff.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
    <security:intercept-url pattern='/login.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
    <security:intercept-url pattern='/**' access='ROLE_USER'/>
    security:filter-security-metadata-source>" +
property>
bean>

验证信任解析器
排在匿名身份验证讨论是AuthenticationTrustResolver界面,与相应的AuthenticationTrustResolverImpl实现.这个接口提供了一个isAnonymous(Authentication)的方法,感兴趣的类可以考虑这种特殊类型的身份验证状态.ExceptionTranslationFilter使用这个接口在处理AccessDeniedException,如果抛出AccessDeniedException ,和一个匿名类型的身份验证,而不是扔403(禁止)的反应,过滤器将开始authenticationentrypoint 所以可以验证正确.这是一个必要的区别,否则,负责将永远被视为"authenticated",并没有得到一个机会通过表格登录,基本、摘要或其他一些正常的身份验证机制。你会经常看到 role_anonymous 属性在上述拦截器配置更换 is_authenticated_anonymously ,这实际上是一样的定义访问控制,这是一个例子,使用的 authenticatedvoter 我们将看到在authorization chapter.它使用一个 authenticationtrustresolver 处理这个特定的配置属性和访问权限授予匿名用户.它使用一个 authenticationtrustresolver 处理这个特定的配置属性和访问权限授予匿名用户. authenticatedvoter 方法更强大,因为它允许你区分匿名,请记住我和完全身份验证的用户.如果你不需要这个功能,然后你可以坚持ROLE_ANONYMOUS,这将是由Spring Security的标准rolevoter.
WebSocket Security
Spring Security 4增加了对Spring的WebSocket支持的支持。 本节介绍如何使用Spring Security的WebSocket支持。
您可以在samples / javaconfig / chat中找到WebSocket安全性的完整工作示例。
直接JSR-356支持
Spring Security不提供直接的JSR-356支持,因为这样做将无济于事。 这是因为格式是未知的,所以有一点Spring可以做到,以确保一个未知的格式。 此外,JSR-356不提供拦截信息的方式,因此安全性将相当有侵害性。
WebSocket配置
Spring Security 4.0通过Spring Messaging抽象引入了对WebSockets的授权支持。 要使用Java Configuration配置授权,只需扩展AbstractSecurityWebSocketMessageBrokerConfigurer并配置MessageSecurityMetadataSourceRegistry。 例如:

@Configuration
public class WebSocketSecurityConfig
      extends AbstractSecurityWebSocketMessageBrokerConfigurer {  

    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
                .simpDestMatchers("/user/*").authenticated() 
    }
}

这将确保:
任何入站CONNECT消息都需要一个有效的CSRF令牌来强制执行相同的源策略SecurityContextHolder在任何入站请求的simpUser头属性中填充用户。我们的消息需要适当的授权。 具体来说,以“/ user /”开头的入站邮件将需要ROLE_USER。 有关授权的其他详细信息,请参见WebSocket授权
Spring Security还提供XML命名空间支持来保护WebSockets。 类似的基于XML的配置如下所示:

<websocket-message-broker>  

    <intercept-message pattern="/user/**" access="hasRole('USER')" />
</websocket-message-broker>

这将确保:
任何入站CONNECT消息都需要一个有效的CSRF令牌来强制执行相同的起始策略SecurityContextHolder在任何入站请求的simpUser头属性中填充用户。我们的消息需要正确的授权。具体来说,以“/ user /”开头的入站邮件将需要ROLE_USER。有关授权的其他详细信息,请参见WebSocket授权
WebSocket认证
在创建WebSocket连接时,WebSockets会重复使用与HTTP请求中相同的身份验证信息。这意味着HttpServletRequest上的校长将被转交给WebSockets。如果您使用Spring Security,HttpServletRequest上的Principal将自动被覆盖。
更具体地说,为了确保用户对WebSocket应用程序进行了身份验证,所有必要的是确保您设置Spring Security以验证基于HTTP的Web应用程序。
WebSocket授权
Spring Security 4.0通过Spring Messaging抽象引入了对WebSockets的授权支持。要使用Java Configuration配置授权,只需扩展AbstractSecurityWebSocketMessageBrokerConfigurer并配置MessageSecurityMetadataSourceRegistry。例如

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
                .nullDestMatcher().authenticated() 
                .simpSubscribeDestMatchers("/user/queue/errors").permitAll() 
                .simpDestMatchers("/app/**").hasRole("USER") 
                .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") 
                .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() 
                .anyMessage().denyAll(); 

    }
}

这将确保:
任何没有目的地的消息(即消息类型的MESSAGE或SUBSCRIBE)将要求用户进行身份验证任何人都可以订阅/ user / queue / errors具有以“/ app /”开始的目的地的任何消息将要求用户 要使用SUBSCRIBE类型的“/ user /”或“/ topic / friends /”开头的角色ROLE_USERAny消息将要求ROLE_USERAny类型为MESSAGE或SUBSCRIBE的其他消息被拒绝。 由于6我们不需要这个步骤,但它说明了如何匹配特定的消息类型。任何其他消息被拒绝。 这是一个好主意,以确保不会错过任何消息。
Spring Security还提供XML命名空间支持来保护WebSockets。 类似的基于XML的配置如下所示:

<websocket-message-broker>

    <intercept-message type="CONNECT" access="permitAll" />
    <intercept-message type="UNSUBSCRIBE" access="permitAll" />
    <intercept-message type="DISCONNECT" access="permitAll" />

    <intercept-message pattern="/user/queue/errors" type="SUBSCRIBE" access="permitAll" /> 
    <intercept-message pattern="/app/**" access="hasRole('USER')" />      


    <intercept-message pattern="/user/**" access="hasRole('USER')" />
    <intercept-message pattern="/topic/friends/*" access="hasRole('USER')" />


    <intercept-message type="MESSAGE" access="denyAll" />
    <intercept-message type="SUBSCRIBE" access="denyAll" />

    <intercept-message pattern="/**" access="denyAll" /> 
</websocket-message-broker>

这将确保:
任何类型CONNECT,UNSUBSCRIBE或DISCONNECT的消息将要求用户进行身份验证任何人都可以订阅/ user / queue / errors具有以“/ app /”开头的目的地的任何消息将要求用户具有角色ROLE_USERAny消息以“SUBSCRIBE”类型的“/ user /”或“/ topic / friends /”开头将要求ROLE_USER任何类型为MESSAGE或SUBSCRIBE的其他消息被拒绝。由于6我们不需要这个步骤,但它说明了如何匹配特定的消息类型。任何其他消息与目的地被拒绝。这是一个好主意,以确保不会错过任何消息。
WebSocket授权说明
为了正确保护您的应用程序,了解Spring的WebSocket支持非常重要。
消息类型的WebSocket授权
了解SUBSCRIBE和MESSAGE类型的消息之间的区别以及它在Spring中的工作是非常重要的。
考虑聊天应用程序
●系统可以通过“/ topic / system / notifications”的目的地向所有用户发送MESSAGE通知
●客户端可以通过SUBSCRIBE接收“/ topic / system / notifications”的通知。
虽然我们希望客户端能够订阅“/ topic / system / notifications”,但我们不希望使其能够向该目的地发送MESSAGE。如果我们允许向“/主题/系统/通知”发送MESSAGE,则客户端可以直接向该端点发送消息并假冒系统。
通常,应用程序通常拒绝发送到以代理前缀(即“/ topic /”或“/ queue /”)开始的消息的任何MESSAGE。
目的地WebSocket授权
了解目的地如何转变也很重要。
考虑聊天应用程序
●用户可以通过向“/ app / chat”的目的地发送消息来向特定用户发送消息。
●应用程序看到消息,确保“从”属性被指定为当前用户(我们不能信任客户端)。
●然后应用程序使用SimpMessageSendingOperations.convertAndSendToUser(“toUser”,“/ queue / messages”,消息))将消息发送给收件人。
●消息变成“/ queue / user / messages- ”的目的地
使用上面的应用程序,我们希望允许我们的客户端收听被转换成“/ queue / user / messages- ”的“/ user / queue”。但是,我们不希望客户端能够听“/ queue / *”,因为这将允许客户端查看每个用户的消息。
通常,应用程序通常拒绝发送到以代理前缀(即“/ topic /”或“/ queue /”)开头的消息的任何SUBSCRIBE。当然,我们可能会提供例外情况
出站邮件
Spring包含一个标题为“消息流”的部分,描述消息如何流过系统。请注意,Spring Security仅保护clientInboundChannel。 Spring Security不会尝试保护clientOutboundChannel。
最重要的原因是性能。对于每一个进入的消息,通常会有更多的消息出来。我们鼓励保护终端的订阅,而不是保护出站邮件。
执行同源政策
重要的是要强调浏览器不会对WebSocket连接执行同源策略。这是一个非常重要的考虑。
为什么同源
请考虑以下情况。用户访问bank.com并对其帐户进行身份验证。同一用户在浏览器中打开另一个标签,并访问evil.com。同源政策确保evil.com无法读取或写入数据到bank.com。
对于WebSockets,“同源”策略不适用。事实上,除非bank.com明确禁止,否则evil.com可以代表用户读写数据。这意味着用户可以通过websocket(即转账)来做任何事情,evil.com可以代表该用户。
由于SockJS试图模拟WebSockets,它也绕过了同源策略。这意味着开发人员需要在使用SockJS时将其应用程序从外部域保护起来。
Spring WebSocket允许的来源
幸运的是,由于Spring 4.1.5 Spring的WebSocket和SockJS支持限制了对当前域的访问。 Spring Security增加了一个额外的保护层,以提供深度的防御。
将CSRF添加到Stomp标题
默认情况下,Spring Security要求在任何CONNECT消息类型中使用CSRF令牌。这确保只有具有访问CSRF令牌的站点可以连接。由于只有相同的来源可以访问CSRF令牌,因此外部域不允许进行连接。
通常,我们需要将CSRF令牌包含在HTTP头或HTTP参数中。但是,SockJS不允许这些选项。相反,我们必须在Stomp标题中包含令牌
应用程序可以通过访问名为_csrf的请求属性来获取CSRF令牌。例如,以下内容将允许在JSP中访问CsrfToken:

var headerName = "${_csrf.headerName}";
var token = "${_csrf.token}";
If you are using static HTML, you can expose the CsrfToken on a REST endpoint. For example, the following would expose the CsrfToken on the URL /csrf
@RestController
public class CsrfController {

    @RequestMapping("/csrf")
    public CsrfToken csrf(CsrfToken token) {
        return token;
    }
}

javascript可以对端点进行REST调用,并使用响应来填充headerName和令牌。
我们现在可以在Stomp客户端中包含令牌。 例如:

...
var headers = {};
headers[headerName] = token;
stompClient.connect(headers, function(frame) {
  ...

}

在WebSockets中禁用CSRF
如果要允许其他域访问您的站点,可以禁用Spring Security的保护。 例如,在Java Configuration中,您可以使用以下内容:

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    ...

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}

使用SockJS
SockJS提供后备传输来支持旧版浏览器。 在使用后备选项时,我们需要放松一些安全约束,以允许SockJS与Spring Security一起工作。
SockJS和框架选项
SockJS可以使用利用iframe的传输。 默认情况下,Spring Security将拒绝该网站被框架以防止Clickjacking攻击。 为了允许基于SockJS框架的传输工作,我们需要配置Spring Security以允许相同的来源来构建内容。
您可以使用frame-options元素自定义X-Frame-Options。 例如,以下内容将指示Spring Security使用“X-Frame-Options:SAMEORIGIN”,它允许同一个域内的iframe:

<http>
    

    <headers>
        <frame-options
          policy="SAMEORIGIN" />
    headers>
http>

同样,您可以使用以下命令自定义框架选项以在Java配置中使用相同的起始位置:

@EnableWebSecurity
public class WebSecurityConfig extends
   WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      // ...
      .headers()
        .frameOptions()
            .sameOrigin();
  }
}

SockJS&放松CSRF
SockJS在CONNECT消息上使用POST,用于任何基于HTTP的传输。 通常,我们需要将CSRF令牌包含在HTTP头或HTTP参数中。 但是,SockJS不允许这些选项。 相反,我们必须将令牌包含在Stomp头中,如将CSRF添加到Stomp头文中所述。
这也意味着我们需要通过Web层来放松我们的CSRF保护。 具体来说,我们要禁用我们的连接URL的CSRF保护。 我们不想为每个网址禁用CSRF保护。 否则我们的网站将容易受到CSRF攻击。
我们可以通过提供CSRF RequestMatcher轻松实现。 我们的Java配置非常简单。 例如,如果我们的脚注端点是“/ chat”,我们可以使用以下配置禁用仅以“/ chat /”开头的URL的CSRF保护:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig
    extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            .csrf()
                // ignore our stomp endpoints since they are protected using Stomp headers
                .ignoringAntMatchers("/chat/**")
                .and()
            .headers()
                // allow same origin to frame our site to support iframe SockJS
                .frameOptions().sameOrigin()
                .and()
            .authorizeRequests()

            ...

如果我们使用基于XML的配置,我们可以使用csrf @ request-matcher-ref。 例如:

...>
    "csrfMatcher"/>

    
        "SAMEORIGIN"/>
    

    ...
<b:bean id="csrfMatcher"
    class="AndRequestMatcher">
    <b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>
    <b:constructor-arg>
        <b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
          <b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
            <b:constructor-arg value="/chat/**"/>
          b:bean>
        b:bean>
    b:constructor-arg>
b:bean>

授权
在Spring Security的高级授权功能是其受欢迎的最有说服力的原因之一,无论您如何选择如何进行身份验证-是否使用提供的机制和提供程序的Spring Security,或整合与一个容器或其他non-Spring Security 认证机构-你会发现授权服务可以在您的应用程序中使用一个一致的和简单的方式.
在这一部分我们将探讨不同的abstractsecurityinterceptor实现,在第一部分介绍.然后,我们将继续探索如何通过使用域访问控制列表微调授权.
授权体系结构
授权
正如我们在<<技术授予权,技术综述 >>所看到,所有的Authentication实现存储的列表GrantedAuthority对象.这些代表已被授予主要的的当局. GrantedAuthority对象是由authenticationManager 插入到Authentication 对象,然后读取 AccessDecisionManager做出判断.
GrantedAuthority是一个只有一个方法的接口
String getAuthority();
这个方法允许AccessDecisionManager 来判断得到一个精确的String表示的GrantedAuthority .通过返回一个表示作为一个String,一个GrantedAuthority 可以很容易的通过AccessDecisionManager 来read,如果一个GrantedAuthority 不能精确地表示为一个String,GrantedAuthority 将会被认为是"complex"和getAuthority() 必须返回为null.
“complex” GrantedAuthority的一个将一个应用于不同客户帐户号码的操作和权限阈值的列表的实现例子.代表这个复杂的GrantedAuthority 作为String将是相当困难的,作为一个结果,getauthority() 方法应该返回null.这将对任何accessDecisionManager 表明它需要明确的支持 GrantedAuthority实施以了解其内容.
Spring Security包括一个具体的GrantedAuthority 实施, grantedauthorityimpl .这允许用户指定的任何String转换成一种 GrantedAuthority .所有的 AuthenticationProvider 的包括与安全架构使用 grantedauthorityimpl填充Authentication对象.
Pre-Invocation处理
正如我们在<<安全对象、技术综述>>章节中也看到过的,Spring Security,提供拦截控制访问安全对象如方法调用或Web请求。是否允许进行调用前调用的决定是由
AccessDecisionManager 作出判断.
访问决策管理器
accessDecisionManager 被 abstractsecurityinterceptor 和负责制定最终的访问控制决策.`AccessDecisionManager接口包含三种方法:
void decide(Authentication authentication, Object secureObject,
Collection attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);
AccessDecisionManager的decide方法传递了它所需要的所有相关信息,以作出授权决策.尤其,通过安全的“对象”,使这些参数包含在实际的安全对象调用中进行检查.例如,让我们假设安全对象是一个MethodInvocation 资料,这将是很容易实现MethodInvocation对于任何Customer论点.然后执行某种安全逻辑判断、来确保AccessDecisionManager 主允许对客户操作.如果访问被拒绝并抛出AccessDeniedException 我们的预期就实现了.
如果accessDecisionManager 可以处理通过 configattribute ,supports(ConfigAttribute)方法由AbstractSecurityInterceptor 在决定启动时候命名. supports(Class) 方法被安全拦截器实现,确保配置accessDecisionManager支持类型的安全对象的被拦截.
Voting-Based访问决策管理器实现
同时,用户可以实现自己的AccessDecisionManager判断、控制授权的所有方面,Spring Security包括几个基于投票的accessDecisionManager实现. Voting Decision Manager说明相关类.

Figure 2. Voting Decision Manager
使用这种方法,一系列的accessdecisionvoter 实现调查授权决策. accessDecisionManager 然后决定是否抛出 accessdeniedexception基于其选票的评估。
AccessDecisionVoter接口包含三种方法:
int vote(Authentication authentication, Object object, Collection attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);
具体返回一个int,可能反映在accessdecisionvoter 的静态字段 access_abstain , access_denied 和 access_granted .如果对授权决策没有意见一个投票的实施将返回 access_abstain . 如果真的有一个观点,它必须返回 access_denied 或 access_granted.
这里三个具体的AccessDecisionManager提供了符合投票的 Spring Security,consensusbased 实施将授予或拒绝基于非共识的弃权票的访问.提供的属性来控制在一个平等的投票活动的行为,或者如果所有的选票都弃权. affirmativebased 实施将会授权访问如果你个或者更多的ACCESS_GRANTED 投票被收到(即使投票被忽略,也至少有一个投票).像“ConsensusBased”实现,有一个参数,控制行为如果所有选民弃权.UnanimousBased的提供者预计一致ACCESS_GRANTED的选票以授予访问权限,忽略了自制。它会拒绝访问如果有任何“ACCESS_DENIED”投票.像其他实现,有一个参数,控制行为如果所有选民弃权.
可以实现自定义accessDecisionManager 计算选票不同。例如,从一个特定的 accessdecisionvoter可能会得到额外的加权投票,而否认从一个特定的选民投票可能有否决权的影响.
角色选民
最常用的由Spring Security提供的AccessDecisionVoter 是简单的RoleVoter,如果用户已被分配该角色,将配置属性视为简单的角色名称和投票授予访问权限.
如果ConfigAttribute 带着前缀ROLE_开始将会进行投票,它将投票授予访问权限,如果有GrantedAuthority 它返回一个String表示(通过getauthority() 方法)恰好等于一个或多个从前缀 role_ 的 configattributes .如果没有从 role_ 精确匹配任何 configattribute , rolevoter 会投票拒绝访问。如果 role_ 没有开始 configattribute,选民将投弃权票。
经过身份验证的选民
另一个选民,我们从 AuthenticatedVoter看到,可以用来区分匿名,fully-authenticated,记得我通过身份验证的用户,许多网站允许某些有限的访问在记得我认证,但是需要用户确认他们的身份登录的完全访问.当我们使用属性IS_AUTHENTICATED_ANONYMOUSLY授予匿名访问,这个属性是由“AuthenticatedVoter”进行处理。有关更多信息,请参见这个类的Javadoc.
定制的选民
显然,您还可以实现自定义“AccessDecisionVoter”,你可以把任何你想要访问控制逻辑.这可能是特定于应用程序(业务逻辑相关)或可能实现一些安全管理逻辑,例如你会发现 blog article 在Spring web网站描述如何使用实时选民拒绝访问用户的账户被暂停.
调用处理
而“AccessDecisionManager”是由“AbstractSecurityInterceptor”在继续之前调用安全对象调用.某些应用程序需要一种方法修改对象的实际安全返回的对象调用.同时您可以很容易地实现自己的AOP实现这一担忧, Spring Security提供了一个方便的钩,几个集成ACL的功能的具体实现.
After Invocation Implementation说明了Spring Security的AfterInvocationManager 以及这个具体实现.

Figure 3. After Invocation Implementation
像Spring Security的其他部分,AfterInvocationManager 有一个具体的实现,AfterInvocationProviderManager调查AfterInvocationProvider的表,每一个AfterInvocationProvider允许修改返回对象或抛出AccessDeniedException.的确多个提供者可以修改对象,如之前的供应商的结果传递给下一个列表中.
请注意,如果您正在使用AfterInvocationManager,你仍然需要配置属性,使MethodSecurityInterceptor的AccessDecisionManager允许一个操作.如果您使用的是典型的Spring Security包括“AccessDecisionManager”实现,没有配置属性定义为一个特定的安全方法调用将导致每个“AccessDecisionVoter”投弃权票,相反,如果AccessDecisionManager 性能 “allow IfAllAbstainDecisions”是false就会抛出一个 AccessDeniedException,你可以通过设置“allowIfAllAbstainDecisions”避免这种潜在的问题改变为true(虽然这是一般不推荐),或者(ii)只是确保至少有一个配置属性,一个“AccessDecisionVoter”将投票授权访问,后者(推荐)的方法通常是通过“ROLE_USER”或“ROLE_AUTHENTICATED”配置属性.
层次的角色
它是一个共同的要求,一个特定的应用程序中的角色应该自动”include”其他角色,例如,在一个应用程序中有一个”admin” 和”user”的角色的概念,你可能希望一个管理员能够尽一切正常的用户可以。要做到这一点,你可以确保所有的管理用户也被分配到”user”的角色.另外,您可以修改每一个访问约束,这需要”user”的角色,还包括”admin”的作用.这可能会变得相当复杂,如果你有很多不同的角色在你的应用程序.
使用角色层次结构允许您配置哪些角色(或主管)应包括其他角色(或主管部门).一个额外的of Spring Security’s RoleVoter的版本,RoleHierarchyVoter配置了一个rolehierarchy,从它获得所有的”reachable authorities”,用户被分配。一个典型的配置可能看起来像这样:

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
    <constructor-arg ref="roleHierarchy" />
bean>
<bean id="roleHierarchy"
        class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
    <property name="hierarchy">
        <value>
            ROLE_ADMIN > ROLE_STAFF
            ROLE_STAFF > ROLE_USER
            ROLE_USER > ROLE_GUEST
        value>
    property>
bean>

在这里,我们有四个角色在一个层次结构 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST.通过身份验证的用户ROLE_ADMIN, 要表现得好像他们所有的四种角色在安全约束的评价与判断,要表现得好像他们所有的四种角色在安全约束的评价与判断,AccessDecisionManager 配置上述RoleHierarchyVoter.>符号可以被认为是意义的”includes”.
角色层次结构提供了一个方便的方法简化了您的应用程序的访问控制配置数据和/或减少您需要分配给用户的权限数.对于更复杂的要求,您可能希望定义您的应用程序所需的特定访问权限和被分配给用户的角色之间的逻辑映射,在加载用户信息时将两者翻译成两者之间的关系.
安全对象的实现
AOP联盟(方法调用)安全拦截器
Spring Security 2.0之前,确保MethodInvocation资料需要相当多的配置.现在方法安全的推荐方法是使用< >.这种方法的安全基础设施beans是为您自动配置的,所以您不需要知道实现类的情况。我们将提供一个在这里涉及的类的快速概述。
方法在执行安全使用methodsecurityinterceptor ,这是一个固定的MethodInvocation .根据配置的方法,一个拦截可能是特定的一个单一的bean或多个beans之间的共享.拦截器使用methodsecuritymetadatasource 实例获取配置属性,适用于一个特定的方法调用.MapBasedMethodSecurityMetadataSource用于存储配置属性的键控的方法名称(可以使用通配符),将在内部使用时,这些属性定义在应用程序的上下文中使用的拦截或元素。
当然你可以使用一个Spring AOP的代理机制配置一个methodsecurityiterceptor直接应用程序上下文中:

id="bankManagerSecurity" class=
    "org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
    
    "com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
    "com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
    
property>

AspectJ(连接点)安全拦截器
AspectJ的安全拦截器是AOP联盟安全拦截器在上一节讨论非常相似。事实上,我们将只讨论在这一部分的差异.
AspectJ拦截器被命名为 AspectJSecurityInterceptor.不像AOP联盟安全拦截器,它依赖于Spring应用程序上下文编织的安全拦截器通过代理,AspectJSecurityInterceptor是基于AspectJ编译器.不会罕见的在同一个程序中使用的两种安全拦截器,与AspectJSecurityInterceptor用于域对象实例安全,AOP联盟methodsecurityinterceptor用于服务层安全.
让我们首先考虑的是如何AspectJSecurityInterceptor配置在Spring应用程序上下文:

id="bankManagerSecurity" class=
    "org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
    
    "com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
    "com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
    
property>

正如你所看到的,除了类名称,AspectJSecurityInterceptor是完全一样的AOP联盟安全拦截器.事实上,两个拦截器可以共享相同的securitymetadatasource ,作为 securitymetadatasource 作品java.lang.reflect.Method而不是AOP库类。当然,你访问的决定获得有关特定AOP库调用(即MethodInvocation 或JoinPoint),这样可以使访问的决定时,考虑的范围之外的标准(如方法的参数).
下次你需要定义一个AspectJ aspect.例如:

package org.springframework.security.samples.aspectj;

import org.springframework.security.access.intercept.aspectj.AspectJSecurityInterceptor;
import org.springframework.security.access.intercept.aspectj.AspectJCallback;
import org.springframework.beans.factory.InitializingBean;

public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {

    private AspectJSecurityInterceptor securityInterceptor;

    pointcut domainObjectInstanceExecution(): target(PersistableEntity)
        && execution(public * *(..)) && !within(DomainObjectInstanceSecurityAspect);

    Object around(): domainObjectInstanceExecution() {
        if (this.securityInterceptor == null) {
            return proceed();
        }

        AspectJCallback callback = new AspectJCallback() {
            public Object proceedWithObject() {
                return proceed();
            }
        };

        return this.securityInterceptor.invoke(thisJoinPoint, callback);
    }

    public AspectJSecurityInterceptor getSecurityInterceptor() {
        return securityInterceptor;
    }

    public void setSecurityInterceptor(AspectJSecurityInterceptor securityInterceptor) {
        this.securityInterceptor = securityInterceptor;
    }

    public void afterPropertiesSet() throws Exception {
        if (this.securityInterceptor == null)
            throw new IllegalArgumentException("securityInterceptor required");
        }
    }
}

在上面的例子中,安全拦截器将被应用到每一个实例persistableentity ,这是一个抽象类没有显示(你可以使用任何其他类或pointcut表达你喜欢).对于那些好奇的人,aspectjcallback 是因为 proceed();声明具有特殊的意义只有在 around() 体。当它想要的目标继续,AspectJSecurityInterceptor 调用这个匿名aspectjcallback类.
你需要配置Spring 负载方面和连接AspectJSecurityInterceptor。一个实现这一的bean声明如下所示:

id="domainObjectInstanceSecurityAspect"
    class="security.samples.aspectj.DomainObjectInstanceSecurityAspect"
    factory-method="aspectOf">
<property name="securityInterceptor" ref="bankManagerSecurity"/>

是这么回事!现在你可以从你的应用程序中的任何地方创建你的豆子,使用任何你认为合适的方式(eg new Person();)他们会拥有安全拦截器的应用
表达式的访问控制
Spring Security 3.0介绍了使用的能力,作为一个授权机制,除了简单的使用配置属性和访问决策的选民,以前见过的使用弹簧的表达。基于表达式的访问控制是建立在相同的架构,但允许复杂的布尔逻辑被封装在一个单一的表达.
概述
Spring Security 使用Spring EL来支持,你应该看看如何,如果你有兴趣在更深入了解主题。表达式是用”root object” 评估的,作为评价上下文的一部分。Spring Security使用特定的类用于Web和方法安全作为根对象,以提供内置表达式和访问当前主体的值等.
常见的内置的表达式
表达根对象的基类是securityexpressionroot.这提供了一些常见的表达式,可应用在网络和方法安全性.
表达描述hasRole([role])如果当前主体具有指定的角色,则返回true.默认情况下,如果提供的角色不从’ROLE_’这里提供,这将增加。这可以通过修改defaultroleprefix 在 defaultwebsecurityexpressionhandler配置.hasAnyRole([role1,role2])如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true,默认情况下,如果提供的角色不从 ‘ROLE_’,这将增加。这可以通过修改defaultroleprefix 在 defaultwebsecurityexpressionhandler定制.hasAuthority([authority])如果当前的主体具有指定的权限,则返回 true.hasAnyAuthority([authority1,authority2])如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true.principal允许直接访问表示当前用户的主对象authentication允许直接访问从SecurityContext得出当前的Authentication对象permitAll总是评估为truedenyAll总是评估为falseisAnonymous()如果当前的主体是一个匿名用户,则返回true.isRememberMe()如果当前的主体是一个匿名用户,则返回trueisAuthenticated()如果用户不是匿名的,则返回 trueisFullyAuthenticated()如果用户不是一个匿名的或是一个记住我的用户返回truehasPermission(Object target, Object permission)如果用户已访问给定权限的提供的目标,则返回true,例如hasPermission(domainObject, ‘read’)hasPermission(Object targetId, String targetType, Object permission)如果用户已访问给定权限的提供的目标,则返回true,例如hasPermission(1, ‘com.example.domain.Message’, ‘read’)
Web Security Expressions
使用表达式来保护个人网址,首先需要设置“use-expressions”属性的< http >为true.Spring Security预期的“访问”属性的< intercept-url >元素包含Spring EL表达式。一个布尔表达式应该评估,定义是否应该允许访问. 例如:


    "/admin*"
        access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
    ...

这里我们定义了应用程序的“admin”区域(定义的URL模式)只能提供给用户授予机关“admin”,其IP地址匹配本地子网.在前一节中我们已经看到内置hasRole表达式。表达“hasIpAddress”是另一个内置的表达式是特定于网络安全.这由WebSecurityExpressionRoot下定义,一个实例时用作表达式根对象评估web访问表达式。这个对象也直接暴露的HttpServletRequest对象的名字“请求”,这样你就可以直接调用请求在一个表达式。如果正在使用表情,“WebExpressionVoter”将被添加到“AccessDecisionManager”所使用的名称空间. 如果你不使用名称空间和想使用表情,你必须添加一个配置.
在网络安全bean表达式
如果你想扩展表达式可用,您可以很容易地操作任何你暴露的Spring Bean。例如,assumming你有一个Bean的名称“webSecurity”包含以下方法

public class WebSecurity {
        public boolean check(Authentication authentication, HttpServletRequest request) {
                ...
        }
}

你可以参考使用方法:


    "/user/**"
        access="@webSecurity.check(authentication,request)"/>
    ...

或在Java配置

http
        .authorizeRequests()
                .antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
                ...

Path Variables in Web Security Expressions
有时在一个URL它很好能够参考路径变量. 例如,考虑一个RESTful应用程序从URL路径的格式查找用户id/user/{userId}.
你可以很容易地将参考路径变量的模式.例如,如果你有一个Bean的名称“webSecurity”包含以下:

public class WebSecurity {
        public boolean checkUserId(Authentication authentication, int id) {
                ...
        }
}

你可以参考使用方法:


    "/user/{userId}/**"
        access="@webSecurity.checkUserId(authentication,#userId)"/>
    ...

或在Java配置

http
        .authorizeRequests()
                .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
            ...

在这两个配置相匹配的url将通过path变量(和)转换成checkUserId方法.
例如,如果这个URLs是 /user/123/resource,id是123.
Method Security Expressions
方法安全性是一个更复杂的比一个简单的规则允许或拒绝,Spring Security 3.0介绍了一些新的注释,以便全面支持表达式的使用.
@Pre and @Post Annotations
有四个属性注释支持表达式允许pre和post-invocation授权检查并提交支持过滤收集参数或返回值.他们是@PreAuthorize, @PreFilter, @PostAuthorize and @PostFilter. 它们的使用是通过“global-method-security”名称空间的元素:

<global-method-security pre-post-annotations="enabled"/>

使用@PreAuthorize和@PostAuthorize访问控制,最明显的是有用的注释是“@PreAuthorize”决定是否可以被调用方法。例如(从”Contacts”示例应用程序)
@PreAuthorize(“hasRole(‘USER’)”)
public void create(Contact contact);
这意味着用户与角色”ROLE_USER”才会允许访问.显然同样的事情可以很容易地通过使用传统的配置和一个简单的配置属性所需的角色:
@PreAuthorize(“hasPermission(#contact, ‘admin’)”)
public void deletePermission(Contact contact, Sid recipient, Permission permission);
这里我们使用一个方法参数的表达式来决定当前用户是否有”admin”允许给定的接触。内置的hasPermission()表达式是通过应用程序上下文链接到Spring Security ACL模块,see below,你可以访问任何变量名称作为方法参数的表达式.
有很多方式Spring Security可以解决方法参数。Spring Security使用DefaultSecurityParameterNameDiscoverer发现参数名称。默认情况下,下列选项尝试方法作为一个整体.
● 如果Spring Security的@P注释存在一个参数的方法,将使用价值。这是使用JDK JDK 8之前有用的接口,编译不包含任何有关参数名称的信息。例如:

import org.springframework.security.access.method.P;

...

@PreAuthorize("#c.name == authentication.name")
public void doSomething(@P("c") Contact contact);

在幕后使用使用“AnnotationParameterNameDiscoverer”可实现自定义支持value属性指定的任何注释.
● 如果Spring Data’的@Param注释存在至少一个参数的方法,将使用价值。这是使用JDK JDK 8之前有用的接口,编译不包含任何有关参数名称的信息。例如:

import org.springframework.data.repository.query.Param;

...

@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);

在幕后使用使用“AnnotationParameterNameDiscoverer”可实现自定义支持value属性指定的任何注释.
● 如果JDK 8是用来编译源参数的参数和使用Spring 4+,那么标准JDK反射API用于发现参数名称。这包含两类和接口工作.
● 最后,如果代码编译与调试符号,参数名称将被发现使用调试符号。这不会为接口工作,因为他们没有调试信息参数名称。为接口,必须使用注释或JDK 8的方法.
任何Spring-EL功能可在表达,所以你也可以访问属性参数。举个例子,如果你想要一个特定方法只允许一个用户访问的用户名匹配的接触,你可以写

@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);

我们访问另一个内置的表情, authentication,也就是 Authentication存储在安全上下文。您也可以直接访问它的principal属性,使用表达式principal。值往往会是一个“UserDetails”实例,所以你可能会使用一个表达式principal.username或“principal.enabled”.
一般,您可能希望执行访问控制检查方法调用之后。这可以通过使用@PostAuthorize注释.访问一个方法的返回值,使用内置的名字“returnObject”的表示
过滤用@PreFilter and @PostFilter
正如你可能已经知道,Spring Security支持集合和数组的过滤,这可以通过使用表达式。这是最常见的一个方法的返回值上执行。例如

@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List getAll();

当用@PostFilter 注释,Spring Security遍历返回的集合并删除任何元素提供的表达式是错误的。“filterObject”指的是当前的对象的集合。你还可以过滤方法调用之前,使用“@PreFilter”,尽管这是一个不太常见的需求。语法是相同的,但是如果有一个以上的论证它是一个集合类型然后你必须选择一个的名字使用“filterTarget”属性的注释.
注意,过滤显然是不能代替调优数据检索查询。如果你是过滤大收藏和删除的条目,那么这可能是低效的.
内置的表达式
有一些内置的表达式具体方法安全,我们已经看到在上面使用。filterTarget 和returnValue值是很简单,但使用hasPermission()的表达式权证更仔细的观察.
许可评估者接口
hasPermission()的表达式是委托给一个实例的PermissionEvaluator.它旨在表达系统和Spring Security的ACL系统之间的桥梁,允许您指定授权约束域对象,基于抽象的权限.没有显式的依赖ACL模块,所以你可以互换,如果需要另一个实现。接口有两个方法:

boolean hasPermission(Authentication authentication, Object targetDomainObject,
                            Object permission);

boolean hasPermission(Authentication authentication, Serializable targetId,
                            String targetType, Object permission);

这直接映射到可用的版本的表情,除了那第一个参数( Authentication 的对象)是不提供的。首先是在域对象的情况下,使用的访问控制,已经加载。然后表达式将返回true,如果当前用户拥有该对象的批准。第二个版本是用于装载情况下,对象不是,但是它的标识符。域对象的抽象的”type”说明符也是必需的,允许加载正确的ACL权限。这历来是对象的Java类,但是这不是必须的,只要符合权限如何加载就可以.
使用“hasPermission()的表情,必须在您的应用程序上下文配置一个PermissionEvaluator”.这看起来像这样:

<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<bean id="expressionHandler" class=
"org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    <property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</bean>

在“myPermissionEvaluator”是实现“PermissionEvaluator”bean。通常这将从ACL实现模块叫做“AclPermissionEvaluator”。见”Contacts”示例应用程序配置更多的细节.
方法安全性元注释
你可以使用元数据注释方法安全性提高代码的可读性。
如果你发现你是在代码库重复相同的复杂表达式。尤其方便
例如,考虑以下几点

@PreAuthorize("#contact.name == authentication.name")
Instead of repeating this everywhere, we can create a meta annotation that can be used instead.
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}

元注释可用于任何Spring安全方法的安全注释.
为了保持符合jsr - 250规范的注释不支持元注释.
额外的话题
在本部分中,我们将介绍功能,这需要一个前一章节的知识以及一些更高级的和不常使用的功能框架。
域对象的安全(acl)
概述
复杂的应用需要的往往不是简单的得到在web请求或方法调用级别来定义访问权限。而是,安全决议需要包括谁(认证),其中(的MethodInvocation)和什么(一些领域对象)。换句话说,授权决策还需要考虑一个方法调用的实际域对象实例的主题。
想象你正在设计申请宠物诊所。将会有两个主要基于spring的应用程序的用户组:宠物诊所的工作人员,以及宠物诊所的客户。员工将获得的所有数据,而你的客户只能看到自己的客户记录。使它更有趣,你的客户可以让其他用户看到他们的客户记录,例如”puppy preschool”的导师或当地的”Pony Club”的总统。使用Spring安全为基础,有几种方法可以使用:
● 写下你的业务方法执行的安全。你可以咨询一个集合内的Customer域对象实例来确定哪些用户可以访问。通过使用SecurityContextHolder.getContext().getAuthentication(),你将能够访问Authentication的对象.
● 写一个 AccessDecisionVoter执行的安全GrantedAuthority[]存储在Authentication对象。这将意味着你的AuthenticationManager需要填充的Authentication自定义GrantedAuthority[]代表每一个Customer访问域对象实例.
● 写一个AccessDecisionVoter 执行安全和直接打开目标客户的域对象。这将意味着你的选民需要访问一个DAO,允许它来检索Customer 的对象。它将访问的Customer 对象的集合的批准用户和做出适当的决定.
每一个这些方法是完全合法的。然而,第一对授权检查你的业务代码。主要问题包括增强的困难的单元测试和重用会更困难的Customer授权逻辑。获得“GrantedAuthority[]的从Authentication对象也很好,但不会扩展到大量的Customer。如果用户可以访问5000Customer(不可能在这种情况下,但试想一下,如果它是一个受欢迎的兽医大小马俱乐部!)所需的内存消耗和时间来构造对象将是不受欢迎的Authentication。最后的方法,直接从外部代码打开Customer,可能是最好的三个。实现关注点分离,不滥用内存或CPU周期,但它仍然是低效的,“AccessDecisionVoter”和最终的商业方法本身将执行调用DAO负责检索AccessDecisionVoter对象。两个访问每个方法调用显然是不可取的。此外,每个方法列出你需要编写自己的访问控制列表(ACL)从头持久性和业务逻辑.
幸运的是,还有另一个选择,我们下面会讲到.
关键概念
Spring Security的ACL服务运送spring-security-acl-xxx.jar.您需要将这个JAR添加到类路径中使用Spring安全域对象实例的安全功能.
Spring Security的域对象实例的安全能力中心的概念一个访问控制列表(ACL)。每个域对象实例系统中有自己的ACL,和ACL记录的细节,谁能和不能使用域对象。有鉴于此,Spring Security提供三个主要ACL-related功能应用程序:
● 一种有效地检索ACL条目你所有的域对象(和修改ACL).
● 确保给定的方式主要是允许的工作与你的对象,之前被称为方法.
● 确保给定的方式主要是允许使用对象(或者他们返回),后被称为方法.
第一个要点,Spring Security ACL的的一个主要功能模块提供了一个高性能的方式检索ACL。这个ACL库能力是极其重要的,因为每一个域对象实例系统中可能有多个访问控制条目,并且每个ACL可能继承其他树形结构中的ACL(这是开箱即用的支持由Spring Security,非常常用)。Spring Security的ACL能力都是被仔细设计以提供高性能检索ACL,加上可插入的缓存,deadlock-minimizing数据库更新、独立于ORM框架(我们直接使用JDBC),适当的封装,和透明的数据库更新.
给定数据库ACL的操作模块的核心,让我们探索使用四个主要表的默认实现。下面表的大小在一个典型的Spring Security ACL部署,最后列出的表行:
● ACL_SID允许我们唯一地标识系统中的任何本金或权威(“SID” 代表”security identity”)。唯一列ID、文本表示的SID,国旗表明是否文本表示是指主体名称或GrantedAuthority.因此,有一个为每个独特的主体或GrantedAuthority行。的上下文中使用时获得许可,SID通常称为”recipient”.
● 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)。我们这一点,因为我们发现有时人们错误地认为他们需要一点对于每一个可能的域对象,这并非如此.
现在我们已经提供了一个基本的概述ACL系统做什么,看起来在一个表结构,让我们探索的关键接口。关键接口:
● Acl:每一个域对象都有且只有一个Acl的对象,内部持有AccessControlEntry的年代以及知道的Acl的所有者。Acl不直接引用到域对象,而是一个ObjectIdentity.Acl的存储在ACL_OBJECT_IDENTITY表.
● AccessControlEntry:一个Acl拥有多个“AccessControlEntry”年代,通常缩写为ace框架。每个ACE是指一个特定的元组的“许可”,“Sid”和“Acl”。ACE还可以授予或non-granting和包含审计设置。ACE ACL_ENTRY表中存储。
● Permission:权限代表一个特定不变的位元遮罩,为钻头提供了便利的函数屏蔽和输出信息。上面给出的基本权限(字节0到4)中包含“BasePermission”类。
● Sid:ACL模块需要指校长和“GrantedAuthority[]的年代。提供了一个间接层的Sid的界面,这是一种“安全标识”的缩写。常见的类包括“PrincipalSid”(代表校长在一个“身份验证”对象)和“GrantedAuthoritySid”。安全身份信息存储在ACL_SID表。
● ObjectIdentity:每个域对象内部ACL表示模块由一个“ObjectIdentity”。默认实现叫做“ObjectIdentityImpl”。
● AclService:检索Acl的适用于一个给定的“ObjectIdentity”。包括实现(JdbcAclService),检索操作委托给一个“LookupStrategy”。“LookupStrategy”为检索ACL信息提供了一个高度优化的策略,使用“(BasicLookupStrategy”批处理检索)和支持自定义实现利用物化视图,分级查询和performance-centric相似,non-ANSI SQL功能。
● MutableAclService:允许提出了修改Acl的持久性。这并不是最重要的如果你不希望使用这个接口。
请注意我们的开箱即用的AclService和相关数据库类都使用ANSI SQL.这是主要的数据库.在写这篇文章的时候,系统已经成功测试了使用超音速SQL,PostgreSQL,Microsoft SQL Server和Oracle.
两个样本船与演示Spring Security ACL模块。第一个是联系人样本,另一个是文档管理系统(DMS)样本。我们建议采取一看这些例子.
开始
要开始使用Spring Security ACL的功能,你需要你的ACL信息存储在某个地方。这需要实例化的DataSource使用Spring。DataSource然后注入JdbcMutableAclService和BasicLookupStrategy 实例。后者提供高性能的ACL检索功能,和前提供增变基因功能。指的一个样本船与Spring Security配置的一个示例。您还需要用四个ACL-specific填充数据库表中列出的最后一部分(参见ACL样本的适当的SQL语句).
一旦您创建了所需的模式和实例化JdbcMutableAclService,接下来将需要确保您的域模型支持互操作性的Spring Security ACL包。希望ObjectIdentityImpl 将是足够的,因为它提供了大量的方法可以使用它。大部分人都有包含 public Serializable getId()的方法。如果返回类型是长,或兼容长(例如int),你会发现你不需要提供进一步的考虑ObjectIdentity问题。许多地方的ACL模块依赖长标识符。如果你不使用长(或int,字节等),有一个非常好的机会你需要重新实现的类。我们不打算支持非long标识符在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);

在上面的示例中,我们检索ACL的44号”Foo” 域对象标识符。我们添加一个ACE,然后名叫”Samantha”能”administer”的对象。代码片段相对明显,除了insertAce方法。insertAce方法的第一个参数是确定在什么位置Acl新条目将被插入。在上面的示例中,我们只是把新的现有ACE。最后一个参数是一个布尔值指示是否允许或拒绝。大部分时间它将授予(真实的),但如果是否认(假),实际上是被屏蔽的权限.
Spring Security并不提供任何特殊的集成自动创建、更新或删除acl DAO或存储库操作的一部分。相反,您需要编写代码如上图所示为你单独的域对象.值得考虑使用AOP在服务层与服务层自动把ACL信息操作.我们发现这在过去的一个相当有效的方法.
一旦你使用上述技术将一些ACL信息存储在数据库中,下一步是实际使用ACL信息作为授权决策逻辑的一部分。这里有许多选择。您可以编写自己的AccessDecisionVoter或AfterInvocationProvider 分别触发一个方法调用之前或之后。这些课程将使用AclService来检索相关的ACL,然后调用的ACL。iAcl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)决定是否授予或拒绝许可。或者,您可以使用我们的AclEntryVoter, AclEntryAfterInvocationProvider 或 AclEntryAfterInvocationCollectionFilteringProvider类。所有这些类提供一个declarative-based方法评估ACL信息在运行时,释放你需要编写任何代码。请参阅示例应用程序来学习如何使用这些类。
Pre-Authentication场景
在有些情况下,您希望使用Spring安全授权,但是用户已经被一些外部系统可靠地验证之前访问应用程序。我们称这种情况为 “pre-authenticated”场景。例子包括 X.509,Siteminder和身份验证的Java EE容器的应用程序正在运行。当使用pre-authentication,Spring Security
*识别用户的请求
*为用户获得当局
细节将取决于外部身份验证机制。用户可能会被他们的证书信息的X.509,或通过一个HTTP请求头Siteminder的情况。如果依靠容器身份验证,用户将被调用getUserPrincipal() 的方法传入的HTTP请求。在某些情况下,外部机制可能为用户提供角色/权威信息但在其他当局必须获得一个单独的源,如UserDetailsService.
Pre-Authentication框架类
因为大多数pre-authentication机制遵循相同的模式,Spring Security一组类,提供一个内部框架实现pre-authenticated身份验证提供者。这个删除复制和允许添加新的实现结构化的方式,无需写一切从头开始。你不需要知道这些类,如果希望使用类似于X.509 authentication,因为它已经有了一个名称空间配置选项,简单的使用和开始使用。如果你需要使用显式的bean配置或计划编写自己的实现提供的实现如何工作的理解将是有用的。你会发现类“org.springframework.security.web.authentication.preauth”。我们在适当的地方提供一个大纲你应该咨询Javadoc和源.
抽象的预认证处理过滤器
这个类将检查的当前内容安全上下文,如果空,它将尝试从HTTP请求中提取用户信息并提交AuthenticationManager,子类覆盖以下方法来获得这些信息:
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);

protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
把这些后,过滤器将包含返回的数据创建一个PreAuthenticatedAuthenticationToken并提交身份验证。由”authentication”,我们或许真的只是意味着进一步的处理负荷用户的部门,而不是按照标准的Spring Security验证架构。
像其他Spring安全身份验证过滤器,pre-authentication过滤器有一个authenticationDetailsSource属性,默认情况下将创建一个WebAuthenticationDetails 对象来存储更多的信息,比如会话标识符和原始IP地址在 Authentication 对象的属性details。在这种情况下,用户角色信息可从pre-authentication获取机制,数据也存储在这个属性,实现GrantedAuthoritiesContainer接口的细节。这使的身份验证提供者阅读部门外部分配给用户。接下来我们将看一个具体的例子.
基于J2ee的前验证Web身份验证源细节
如果过滤器配置了一个authenticationDetailsSource这类的一个实例,权威的信息是通过调用isUserInRole(String role)的一组预先确定的方法为每一个”mappable roles”配置的类这些来自一个MappableAttributesRetriever.可能的实现包括硬编码应用程序上下文中的一个列表和阅读的角色信息在web.xml文件.pre-authentication示例应用程序使用了后一种方法.
有一个额外的阶段(或属性)的角色被映射到Spring Security GrantedAuthority对象使用一个配置Attributes2GrantedAuthoritiesMapper。默认只会添加通常具备ROLE_前缀的名字,但它让你完全控制行为
前验证身份验证提供者
re-authenticated提供者有更多比为用户负载的UserDetails对象。它通过委托给一个AuthenticationUserDetailsService.后者是类似于标准UserDetailsService但以一个 Authentication对象而不是用户名:
public interface AuthenticationUserDetailsService {
UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
}
这个接口可能也其他用途,但pre-authentication它允许访问官方包装在“身份验证”对象,正如我们在前一节中看到的。PreAuthenticatedGrantedAuthoritiesUserDetailsService这类,或者,它可能代表一个标准的UserDetailsService 通过UserDetailsByNameServiceWrapper 实现.
Http403禁止入口点
AuthenticationEntryPoint是讨论的technical overview 一章。通常它负责启动未经过身份验证的用户的身份验证过程(当他们试图访问受保护的资源),但是在pre-authenticated情况下不适用。你只会配置 ExceptionTranslationFilter与这个类的一个实例,如果你不使用pre-authentication结合其他身份验证机制。它将被称为AbstractPreAuthenticatedProcessingFilter如果用户被拒绝的结果在一个空的身份验证。它总是返回一个“403”错误.
具体实现
X.509认证被 own chapter覆盖.下面我们来看看一些类,它们提供支持其他pre-authenticated场景.
请求头身份验证(Siteminder)
设置特定的HTTP请求。一个众所周知的例子是Siteminder,通过用户名在一个标题叫SM_USER 。这种机制是类 RequestHeaderAuthenticationFilter支持,只是从标题中提取用户名。它默认使用的名称SM_USER 作为标题名称。看到更多的细节的Javadoc.
注意,当使用这样的一个系统,框架执行任何身份验证检查和extremely重要外部系统的正确配置和保护所有访问应用程序。如果攻击者能够伪造原始请求的头文件没有被发现之后,他们可以选择任何他们希望的用户名.
Siteminder示例配置
一个典型的配置使用这个过滤器看起来像这样:

<security:http>

<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>

我们认为这里security namespace是用于配置。还假定您已经添加了一个UserDetailsService(称为”userDetailsService”)到您的配置加载用户的角色.
Java EE容器认证
J2eePreAuthenticatedProcessingFilter将从 userPrincipal属性中提取 HttpServletRequest.使用这个过滤器通常会结合使用Java EE角色如上所述在< < j2ee-preauth-details > >表示.
有一个示例应用程序代码中使用这种方法,从github得到的代码从github,如果你对这些文件感兴趣你可以看下,代码是在samples/xml/preauth目录中.
LDAP 身份验证
概述
LDAP作为一个中央存储库对用户信息和身份验证服务是常用的组织.它也可以用于存储应用程序用户的角色信息.这里有一些不同的场景对于如何配置LDAP服务器,因此Spring Security LDAP提供者是完全可配置的,它使用单独的策略为身份验证和角色接口检索,并提供默认的实现,可以配置为处理各种情况.在使用之前你应该熟悉Spring Security LDAP.以下链接提供了一个很好的介绍涉及的概念和使用OpenLDAP免费的LDAP服务器建立一个目录指南http://www.zytrax.com/books/ldap/[http://www.zytrax.com/books/ldap/]. 一些熟悉的JNDI api用于访问LDAP从Java也可能是有用的。我们在LDAP不使用任何第三方LDAP库(Mozilla,JLDAP等等),但Spring 的广泛使用是由LDAP,如果你打算添加您自己的定制,对这方面有所了解对你的项目可能是有用的.
使用LDAP身份验证时,重要的是要确保你正确配置LDAP连接池。如果你不熟悉如何做到这一点,你可以参考 Java LDAP documentation.
使用LDAP Spring Security
在Spring的LDAP身份验证安全大致可以分为以下几个阶段
● 获得独特的LDAP”Distinguished Name”,或DN,登录名。这通常意味着执行搜索的目录,除非用户名的具体映射DNs是提前知道。所以用户可能输入名称登录”joe”,但实际LDAP名称用于验证将完整的DN,如uid=joe,ou=users,dc=spring,dc=io.
● 验证用户,通过”binding”,用户操作的用户的密码与密码属性执行远程”compare” 目录条目的DN.
● 加载当局为用户的列表.
唯一的例外是当LDAP目录只是被用于检索用户信息并在本地对其进行身份验证.这个不可能设置有限的读访问属性目录,如用户密码.
下面,我们将看看一些配置场景。完整的可用配置选项的信息,请查阅安全模式名称空间(信息应该在XML编辑器中可用).
配置LDAP服务器
你需要做的第一件事是配置的服务器身份验证应该发生。这是通过使用 的元素从安全名称空间。这可以配置为指向外部LDAP服务器,使用的url属性:

<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />

Using an Embedded Test Server
元素也可以用来创建一个嵌入式服务器,它可以是非常有用的进行测试和演示。在这种情况下,你没有使用它的url属性:

<ldap-server root="dc=springframework,dc=org"/>

这里我们指定目录是”dc=springframework,dc=org”,这是默认的.这种方式,使用名称空间解析器将创建一个嵌入式Apache目录服务器的类路径和扫描任何LDIF文件,它将尝试加载到服务器。你可以定制这种行为使用ldif的属性,它定义了一个ldif资源加载:

<ldap-server ldif="classpath:users.ldif" />

这使它更容易与LDAP同步,因为它可以方便工作与外部服务器。它还将用户从复杂bean配置需要隔离一个Apache连接目录服务器。使用普通的Spring bean配置将会更加混乱。你必须要有必要的Apache Directory依赖性jar用于您的应用程序使用。如LDAP示例应用程序。
使用绑定验证
这是最常见的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使用user-search-filter 属性的值作为一个过滤器。用户登录名是代替过滤器的参数名称,所以它将搜索条目uid属性等于用户名。如果user-search-base 并不提供,从根搜索.
加载机构
当局是如何从组加载在LDAP目录中由以下属性控制
● group-search-base.定义了目录树下的一部分应该执行哪一组搜索。
● group-role-attribute.属性包含的名称定义的权限组条目。默认为cn
● 组搜索过滤器。过滤器用于搜索组成员。默认是 uniqueMember={0},对应于groupOfUniqueNames的LDAP类脚注:(注意,这是不同于缺省配置底层DefaultLdapAuthoritiesPopulator使用member={0}。]。在这种情况下,替换参数是用户的专有名称。可以使用参数{1}如果你想过滤的登录名.
因此,如果我们使用以下配置

<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"
        group-search-base="ou=groups" />

和经过验证的成功作为用户”ben”,随后的加载下当局将执行搜索的目录条目的ou=groups,dc=springframework,dc=org,寻找条目包含的属性uniqueMember价值uid=ben,ou=people,dc=springframework,dc=org。默认的权限名称前缀ROLE_ 前缀。你可以改变这个使用 role-prefix属性。如果你不想要任何前缀,使用role-prefix=”none”.加载机构的更多信息,请参阅DefaultLdapAuthoritiesPopulator类的Javadoc.
实现类
上面的名称空间配置选项我们使用易于使用,更比使用Spring bean简洁明确。有些情况下,您可能需要了解如何配置Spring Security LDAP直接在您的应用程序上下文。您可能希望定制的一些类的行为,例如。如果你使用名称空间配置,那么你可以跳过这一节和下一个.
LDAP provider,LdapAuthenticationProvider,实际上并不做太多工作本身,而是代表其他两个bean,一个LdapAuthenticator 和一个LdapAuthoritiesPopulator分别负责验证用户和检索用户的组GrantedAuthority.
Ldap身份验证实现
authenticator还负责检索所需的用户属性。这是因为权限的属性可能取决于正在使用的身份验证类型。例如,如果绑定用户,与用户可能需要阅读的权限有关.
目前有两种身份验证策略提供Spring安全:
*直接向LDAP服务器的身份验证(“bind” 身份验证)。
*密码比较,用户提供的密码是与一个存储在存储库中。这可以通过检索密码属性的值和检查本地或通过执行LDAP”compare”操作,提供的密码在哪里传递到服务器进行比较和真正的密码值是没有检索到.
常用功能
之前可以验证一个用户(通过策略),专有名称(DN)必须从登录名获得提供给应用程序.这可以通过简单的模式匹配(通过设置setUserDnPatterns数组属性)或通过设置userSearch属性.对于DN模式匹配方法,格式是使用一个标准的Java模式,将登录名代替参数{0}.他应该相对于DN模式,配置SpringSecurityContextSource 将绑定到部分(参见connecting to the LDAP server 更多这方面的信息).ldap://monkeymachine.co.uk/dc=springframework,dc=org和有一个模式uid={0},ou=greatapes,”gorilla” 的登录名将会映射到一个DNuid=gorilla,ou=greatapes,dc=springframework,dc=org. 每个配置的DN模式将尝试直到找到一个匹配,有关使用搜索的信息,看到部分下面的search objects,结合这两种方法也可以使用——模式首先会检查,如果没有找到匹配DN,将使用搜索.
绑定认证者
org.springframework.security.ldap BindAuthenticator实现身份验证绑定验证策略。它只是试图将用户绑定.
身份验证密码比较
PasswordComparisonAuthenticator实现了密码比较验证策略.
连接到LDAP服务器
上面讨论的bean必须能够连接到服务器。他们都必须提供一个SpringSecurityContextSource这是SpringLDAP的ContextSource的延伸。除非你有特殊要求,您通常会配置一个DefaultSpringSecurityContextSource bean,可以配置LDAP服务器的URL和可选的”manager”的用户的用户名和密码,使用时将默认绑定到服务器(而不是匿名绑定)。更多信息,读取这个类的Javadoc和SpringLDAP的AbstractContextSource”.
LDAP搜索对象
通常需要一个比DN-matching定位目录中的用户条目更复杂的策略,这可以封装在一个LdapUserSearch实例,可以提供身份验证实现.例如,让他们来定位用户.提供的实现是FilterBasedLdapUserSearch.
滤波器基于Ldap用户搜索
这个bean使用LDAP目录中的过滤器匹配用户对象。Javadoc的过程解释相应的搜索方法 JDK DirContext class.作为解释,搜索筛选器可以提供参数。这个类,唯一有效的参数是{0}将取代用户的登录名.
Ldap当局填充器
验证用户成功后,LdapAuthenticationProvider 将试图通过调用配置LdapAuthoritiesPopulator bean加载一组当局用户.DefaultLdapAuthoritiesPopulator 是一个实现加载当局通过搜索目录组的用户成员(通常这些将groupOfNames”或groupOfUniqueNames加入目录中的条目)详细内容,请参阅这个类的Javadoc中它是如何工作的.
如果你只想使用LDAP身份验证,但加载当局从不同来源(比如数据库),那么您可以提供自己的实现这个接口和注入.
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=peoplevalue>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>

这将设置提供程序访问LDAP服务器URLldap://monkeymachine:389/dc=springframework,dc=org.身份验证将由试图结合DNuid=,ou=people,dc=springframework,dc=org.成功的身份验证之后,角色分配给用户通过搜索下的DNou=groups,dc=springframework,dc=org用默认的过滤器(member=).角色名称将从每一个”ou”属性开始匹配.
配置一个用户搜索对象,使用过滤器(uid=) 的使用而不是DN-pattern(或补充),您将配置以下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属性.authenticator将调用搜索对象来获得正确的用户作为该用户的DN之前绑定.
LDAP Attributes and Customized UserDetails
身份验证使用LdapAuthenticationProvider 的最终结果是一样的一个正常的Spring安全身份验证使用标准的UserDetailsService界面。创建一个UserDetails”对象并返回存储在Authentication”对象。作为UserDetailsService使用,一个常见需求是能够定制这个实现和添加额外的属性。当使用LDAP,这些通常会从用户条目属性。UserDetails对象的创建是由提供者的UserDetailsContextMapper策略,负责从LDAP上下文映射用户对象和数据:

public interface UserDetailsContextMapper {

UserDetails mapUserFromContext(DirContextOperations ctx, String username,
        Collection authorities);

void mapUserToContext(UserDetails user, DirContextAdapter ctx);
}

唯一重要的是第一个方法进行身份验证。如果您提供该接口的一个实现,它注入LdapAuthenticationProvider,你能够控制如何创建UserDetails对象。Spring 的第一个参数是一个实例LDAP的DirContextOperations可以让你接触的LDAP属性加载在身份验证。 username参数是用于验证和最后一个参数是集当局为用户加载的配置 LdapAuthoritiesPopulator.
上下文数据加载略有不同的方式取决于您正在使用的身份验证类型。 BindAuthenticator,返回的上下文绑定操作将被用于读取属性,否则数据将从配置读取使用标准的背景下获得ContextSource(当配置搜索来定位用户,这将是搜索返回的数据对象).
活动目录的认证
活动目录支持自己的标准身份验证选项,和正常的使用模式不适合太明显与标准LdapAuthenticationProvider.通常执行身份验证使用域用户名(user@domain),而不是使用LDAP专有名称.为了更简单,Spring Security 3.1有一个身份验证提供者是一个典型的定制活动目录设置.
Active Directory Ldap身份验证提供者
配置ActiveDirectoryLdapAuthenticationProvider非常简单。你只需要提供域名和LDAP服务器的URL提供地址脚注:[还可以获得使用DNS查找服务器的IP地址。当前不支持,但是希望在以后的版本可以实现)。一个例子配置会看起来像这样:

<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 DirectoryuserPrincipalName,即 [email protected].用户的目录条目将被定位,并可能返回的属性中使用自定义创建的UserDetails对象(UserDetailsContextMapper可以被注入为此,如上所述)。所有与目录发生交互用户的身份。没有一个”manager”用户的概念.
默认情况下,用户当局正在从memberOf 获得用户输入的属性值。政府再分配给用户可以使用被定制UserDetailsContextMapper。你也可以注入一个GrantedAuthoritiesMapper提供者实例来控制政府最终在 Authentication对象.
活动目录错误代码
默认情况下,一个失败的结果将导致一个标准的Spring SecurityBadCredentialsException,如果你设置的属性convertSubErrorCodesToExceptions 是true, 异常消息将解析试图提取活性Directory-specific错误代码,提高一个更具体的异常。检查类Javadoc的更多信息。
JSP 标签库
Spring Security有自己的标签库提供基本支持访问安全信息并在jsp应用安全约束.
宣布Taglib
要使用的任何标签,必须有安全JSP 标签库:

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

授权标签
这个标签是用来确定是否应评估其内容。在Spring Security 3.0中,可以以两种方式使用脚注:[遗留的选项从Spring Security 2.0也支持,但不推荐.].第一种方法使用一个web-security expression,access 属性中指定的标签,表达式求值将委托给SecurityExpressionHandler 中定义的应用程序(你应该启用web表达式的名称空间配置,以确保这个服务是可用的).

<sec:authorize access="hasRole('supervisor')">

此内容将只对用户可见的”supervisor”权威的< tt > GrantedAuthority < / tt >。

sec:authorize>

在与Spring Security PermissionEvaluator共同使用时,标签也可以用来检查权限.例如:

<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')">

作为一个请求属性命名的”domain”,这个内容只会看到读写权限对象用户.

sec:authorize>

一个常见的需求是只显示一个特定的链接,如果用户实际上是允许点击它。我们如何能提前确定事情是否会被允许吗?这个标签也可以在另一种操作模式,允许您定义一个特定的URL属性。如果允许用户调用的URL,然后标记的身体将被评估,否则它将被忽略。所以你可

s content will only be visible to users who are authorized to send requests to the “/admin” URL.

sec:authorize>

在你的程序内使用这个标签也必须的一个实例WebInvocationPrivilegeEvaluator,如果您正在使用名称空间,将自动注册。这是一 个DefaultWebInvocationPrivilegeEvaluator的实例,它创建了一个虚拟网络提供的URL请求,并调用安全拦截器请求是否会成功或失败,这允许您代表您定义的访问控制设置中使用intercept-url声明的名称空间配置省去了重复的信息(如所需的角色)在您的jsp.这种方法还可以加上一个method属性,提供HTTP方法,更具体的匹配.
布尔结果评估标签(是否允许或拒绝访问)通过设置var属性变量名称可以存储在一个页面上下文范围变量,避免复制和重新评估在页面中的其他点.
禁用标记授权进行测试
隐藏链接在页面不防止未经授权的用户访问URL。他们可以直接在浏览器中类型,例如,作为您的测试过程的一部分,您可能想要显示隐藏领域为了检查环节是安全的后端。如果你设置系统属性 spring.security.disableUISecurity 为 true,authorize的标签仍然会跑但不会隐藏其内容,默认情况下它也会包含…​ 标签。这允许你显示”hidden”与一个特定的CSS样式等内容不同的背景颜色。尝试运行此属性启用的”tutorial”示例应用程序,例如.
你也可以设置spring.security.securedUIPrefix和spring.security.securedUISuffix属性,如果你想改变周围的文字从默认的跨度标签(或使用空字符串完全删除).如果你想改变周围文本从默认“跨度”标签(或使用空字符串完全删除它).
身份验证标记
这个标签允许访问当前存储在安全内 Authentication对象,它直接在JSP中 呈现一个属性对象.所以,例如,如果principal属性的Authentication是Spring Security的UserDetails的对象的一个实例,之后用` 将会显示当前用户的名称.
当然,这种事情没有必要使用JSP标记,有些人喜欢保持尽可能少的逻辑视图.您可以在你的MVC控制器访问Authentication对象(通过调用SecurityContextHolder.getContext().getAuthentication())并将数据直接添加到您的模型渲染的视图.
The accesscontrollist Tag
这个标记只在使用Spring Security的ACL模块,它检查一个以逗号分隔的所需权限指定的域对象.如果当前用户所有的权限,然后标记的内容将被评估。如果他们不这样做,它将被忽略.例如

"1,2" domainObject="${someObject}">

这将显示用户在给定对象上的所有权限所代表的值“1”或“2”.

sec:accesscontrollist>

在程序内容中,权限被传递到“PermissionFactory”中定义中定义,将它们转换为ACLPermission的实例,所以他们支持任何格式工厂,他们不一定是整数,字符串可以像READ或WRITE.如果没有找到PermissionFactory ,将使用DefaultPermissionFactory的一个实例.程序中的“AclService”将被用于加载Acl的实例提供对象.Acl的将调用所需的权限,以检查是否都是合法的.
这个标签还支持var属性,以同样的方式authorize 的标签.
csrfInput标签
如果启用了CSRF保护,这个标签插入一个隐藏表单字段的正确名称和值CSRF保护令牌。如果没有启用CSRF保护,这个标签输出.对于任何的 标签使用,通常Spring Security自动插入一个CSRF表单字段.但如果由于某种原因你不能使用,你可以使用更换.
你应该把这个标签在一个HTML 形式的块,通常地方其他输入字段.不要将这个标签放置在一个Spring ,block—Spring Security会自动处理 Spring.

"post" action="/do/something"> Name:
"text" name="name" /> ...

csrfMetaTags标签
如果启用了CSRF保护,这个标签插入元素标记包含CSRF保护令牌表单字段和标题名称和CSRF保护令牌的值。这些元素标记可用于使用CSRF保护在JavaScript应用程序.
你应该把csrfMetaTags在一个 HTML ,通常地方其他元标记。一旦你使用这个标签,你可以使用JavaScript访问表单字段的名称,标题名称.JQuery中使用这个例子使这项任务变得更加简单.



    
        CSRF Protected JavaScript Page
        "description" content="This is the description for this page" />
        
        
                    
                    

你可能感兴趣的:(Spring Security 参考手册)