Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架。 凭借对命令式和响应式应用程序的一流支持,它是用于保护基于Spring的应用程序的实际标准。
前言
1.先决条件
Spring Security需要Java 8或更高版本的运行时环境。
由于Spring Security旨在以独立的方式运行,因此您无需在Java Runtime Environment中放置任何特殊的配置文件。 特别是,您无需配置特殊的Java身份验证和授权服务(JAAS)策略文件,也无需将Spring Security放置在公共类路径位置。
同样,如果使用EJB容器或Servlet容器,则无需在任何地方放置任何特殊的配置文件,也无需在服务器类加载器中包含Spring Security。 所有必需的文件都包含在您的应用程序中。
这种设计提供了最大的部署时间灵活性,因为您可以将目标工件(JAR,WAR或EAR)从一个系统复制到另一个系统,并且可以立即使用。
2.Spring Security社区
欢迎来到Spring Security社区! 本节讨论如何充分利用我们庞大的社区。
2.1. 获得帮助
如果您需要有关Spring Security的帮助,我们将在这里为您提供帮助。 以下是获得帮助的一些最佳方法:
-
通读本文档。
-
尝试我们许多 示例应用程序之一 。
-
使用 标签 在 https://stackoverflow.com 上 提问
spring-security
。 -
在 https://github.com/spring-projects/spring-security/issues 报告错误和增强请求
2.2. 参与其中
我们欢迎您参与Spring Security项目。 有很多贡献的方法,包括回答有关StackOverflow的问题,编写新代码,改进现有代码,协助编写文档,开发示例或教程,报告错误或仅提出建议。 有关更多信息,请参见我们的 贡献 文档。
2.3. 源代码
您可以在GitHub上找到Spring Security的源代码, 网址 为 https://github.com/spring-projects/spring-security/
2.4. Apache 2许可证
Spring Security是在 Apache 2.0许可 下发行的开源软件 。
2.5. 社交媒体
您可以 在Twitter上 关注 @SpringSecurity 和 Spring Security团队 ,以获取最新消息。 您还可以关注 @SpringCentral 以了解整个Spring产品组合的最新信息。
3. Spring Security 5.2的新功能
Spring Security 5.2提供了许多新功能。 以下是该版本的重点内容。
3.1. Servlet
-
在HTTP安全DSL中 添加了 嵌套的构建器 支持
-
OAuth 2.0客户端
-
引入 OAuth2AuthorizedClientManager / OAuth2AuthorizedClientProvider
-
添加了 AuthorizedClientServiceOAuth2AuthorizedClientManager ,它能够在HttpServletRequest上下文之外进行操作
-
PKCE对 公共客户的支持
-
支持 资源所有者密码凭证 授予
-
支持 通过NimbusJwtDecoder 使用 对称密钥 进行ID令牌验证
-
将 随机数 添加 到OpenID Connect身份验证请求中
-
OpenID Connect RP启动的注销
-
更新的 文档
-
-
OAuth 2.0资源服务器
3.3. 核心
-
引入 RSocket 支持
-
引入 SAML服务提供商 支持
-
为方法参数 引入 @CurrentSecurityContext
-
将 密钥资料 转换 为密钥实例
-
支持 Clear-Site-Data 标头
-
添加了 nohttp 来构建
-
JDK 12 支持
-
支持 消息表达式中的 路径变量
-
配置类是无代理的,并且支持 proxyBeanMethods = false
-
支持不同 BCrypt编码 之间的升级
-
支持不同 SCrypt编码 之间的升级
4.获得Spring Security
本节讨论了有关获取Spring Security二进制文件所需的所有知识。 有关 如何获取 源代码的信息 , 请参见 源代码。
4.1. 发行编号
Spring Security版本的格式为MAJOR.MINOR.PATCH,使得:
-
主要版本可能包含重大更改。 通常,这样做是为了提供改进的安全性以匹配现代安全性实践。
-
MINOR版本包含增强功能,但被视为被动更新
-
PATCH级别应该是完全兼容的,向前和向后兼容,但可能存在修正错误的更改除外。
4.2. 与Maven结合使用
与大多数开源项目一样,Spring Security将其依赖项部署为Maven工件。 本节中的主题提供有关使用Maven时如何使用Spring Security的详细信息。
4.2.1. Maven的春季靴
Spring Boot提供了一个
spring-boot-starter-security
启动程序,
该
启动程序将与Spring Security相关的依赖项聚合在一起。
使用启动器的最简单且首选的方法是
通过IDE集成(
Eclipse
,
IntelliJ
,
NetBeans
)或通过
https://start.spring.io
使用
Spring Initializr
。
另外,您可以手动添加启动器,如以下示例所示:
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
由于Spring Boot提供了Maven BOM来管理依赖版本,因此您无需指定版本。 如果您希望覆盖Spring Security版本,可以通过提供Maven属性来实现,如以下示例所示:
<properties>
<!-- ... -->
<spring-security.version>5.2.0.RELEASE</spring-security.version>
</dependencies>
由于Spring Security仅在主要版本中进行重大更改,因此可以将较新版本的Spring Security与Spring Boot一起使用是安全的。 但是,有时您可能还需要更新Spring Framework的版本。 您可以通过添加Maven属性来执行此操作,如以下示例所示:
<properties>
<!-- ... -->
<spring.version>5.2.0.RELEASE</spring.version>
</dependencies>
如果您使用其他功能(例如LDAP,OpenID和其他功能),则还需要包括适当的 项目模块 。
4.2.2. 没有Spring Boot的Maven
当您在没有Spring Boot的情况下使用Spring Security时,首选方法是使用Spring Security的BOM,以确保在整个项目中使用一致的Spring Security版本。 以下示例显示了如何执行此操作:
<dependencyManagement>
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>5.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
最小的Spring Security Maven依赖关系集通常如下所示:
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
</dependencies>
如果您使用其他功能(例如LDAP,OpenID和其他功能),则还需要包括适当的 项目模块 。
Spring Security是基于Spring Framework 5.2.0.RELEASE构建的,但通常应与任何较新版本的Spring Framework 5.x一起使用。
Spring Security的可传递依赖项解决了Spring Framework 5.2.0.RELEASE的事实,这可能会引起奇怪的类路径问题,这使许多用户不满。
解决此问题的最简单方法是使用
您
spring-framework-bom
的
<dependencyManagement>
部分中的,
pom.xml
如以下示例所示:
<dependencyManagement>
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
前面的示例确保Spring Security的所有传递依赖项都使用Spring 5.2.0.RELEASE模块。
这种方法使用Maven的“物料清单”(BOM)概念,并且仅在Maven 2.0.9+中可用。 有关如何解决依赖关系的其他详细信息,请参见 Maven的依赖机制简介文档 。 |
4.2.3. Maven仓库
所有GA版本(即以.RELEASE结尾的版本)均已部署到Maven Central,因此无需在pom中声明其他Maven存储库。
如果使用SNAPSHOT版本,则需要确保定义了Spring Snapshot存储库,如以下示例所示:
<repositories>
<!-- ... possibly other repository elements ... -->
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
如果使用里程碑版本或候选版本,则需要确保定义了Spring Milestone存储库,如以下示例所示:
<repositories>
<!-- ... possibly other repository elements ... -->
<repository>
<id>spring-milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
4.3. 摇篮
作为大多数开源项目,Spring Security将其依赖项部署为Maven工件,从而提供了一流的Gradle支持。 以下主题详细介绍了使用Gradle时如何使用Spring Security。
4.3.1. 带有Gradle的Spring Boot
Spring Boot提供了一个
spring-boot-starter-security
启动程序,
该
启动程序将与Spring Security相关的依赖项聚合在一起。
使用启动器的最简单且首选的方法是
通过IDE集成(
Eclipse
,
IntelliJ
,
NetBeans
)或通过
https://start.spring.io
使用
Spring Initializr
。
另外,您可以手动添加启动器,如以下示例所示:
dependencies {
compile "org.springframework.boot:spring-boot-starter-security"
}
由于Spring Boot提供了Maven BOM来管理依赖版本,因此您无需指定版本。 如果您希望覆盖Spring Security版本,可以通过提供Gradle属性来实现,如以下示例所示:
ext['spring-security.version']='5.2.0.RELEASE'
由于Spring Security仅在主要版本中进行重大更改,因此可以将较新版本的Spring Security与Spring Boot一起使用是安全的。 但是,有时您可能还需要更新Spring Framework的版本。 您可以通过添加Gradle属性来执行此操作,如以下示例所示:
ext['spring.version']='5.2.0.RELEASE'
如果您使用其他功能(例如LDAP,OpenID和其他功能),则还需要包括适当的 项目模块 。
4.3.2. 没有Spring Boot的Gradle
当您在没有Spring Boot的情况下使用Spring Security时,首选方法是使用Spring Security的BOM,以确保在整个项目中使用一致的Spring Security版本。 您可以使用 Dependency Management Plugin 来做到这一点 ,如以下示例所示:
plugins {
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
dependencyManagement {
imports {
mavenBom 'org.springframework.security:spring-security-bom:5.2.0.RELEASE'
}
}
最小的Spring Security Maven依赖关系集通常如下所示:
dependencies {
compile "org.springframework.security:spring-security-web"
compile "org.springframework.security:spring-security-config"
}
如果您使用其他功能(例如LDAP,OpenID和其他功能),则还需要包括适当的 项目模块 。
Spring Security是基于Spring Framework 5.2.0.RELEASE构建的,但通常应与任何较新版本的Spring Framework 5.x一起使用。
{JB}许多用户可能会误以为Spring Security的可传递依赖项解决了Spring Framework 5.2.0.RELEASE,这会引起奇怪的类路径问题。
解决此问题的最简单方法是
spring-framework-bom
在的
<dependencyManagement>
部分中
使用
pom.xml
。
您可以使用
Dependency Management Plugin
来做到这一点
,如以下示例所示:
plugins {
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
dependencyManagement {
imports {
mavenBom 'org.springframework:spring-framework-bom:5.2.0.RELEASE'
}
}
前面的示例确保Spring Security的所有传递依赖项都使用Spring 5.2.0.RELEASE模块。
4.3.3. Gradle仓库
所有GA版本(即以.RELEASE结尾的版本)均已部署到Maven Central,因此使用mavenCentral()存储库足以满足GA版本的要求。 以下示例显示了如何执行此操作:
repositories {
mavenCentral()
}
如果使用SNAPSHOT版本,则需要确保已定义Spring Snapshot存储库,如以下示例所示:
repositories {
maven { url 'https://repo.spring.io/snapshot' }
}
如果使用里程碑版本或候选版本,则需要确保定义了Spring Milestone存储库,如以下示例所示:
repositories {
maven { url 'https://repo.spring.io/milestone' }
}
5.项目模块
在Spring Security 3.0中,代码库被细分为单独的jar,这些jar更清楚地区分了不同的功能区域和第三方依赖项。
如果使用Maven构建项目,则应将这些模块添加到中
pom.xml
。
即使您不使用Maven,我们也建议您查阅
pom.xml
文件以了解第三方依赖关系和版本。
另一个好主意是检查示例应用程序中包含的库。
5.1.
核心-
spring-security-core.jar
该模块包含核心身份验证和访问控制类和接口,远程支持和基本配置API。 使用Spring Security的任何应用程序都需要它。 它支持独立的应用程序,远程客户端,方法(服务层)安全性和JDBC用户配置。 它包含以下顶级程序包:
-
org.springframework.security.core
-
org.springframework.security.access
-
org.springframework.security.authentication
-
org.springframework.security.provisioning
5.2.
远程处理—
spring-security-remoting.jar
该模块提供了与Spring Remoting的集成。
除非您要编写使用Spring Remoting的远程客户端,否则您不需要这样做。
主要包装是
org.springframework.security.remoting
。
5.3.
Web-
spring-security-web.jar
该模块包含过滤器和相关的Web安全基础结构代码。
它包含任何与Servlet API相关的内容。
如果需要Spring Security Web认证服务和基于URL的访问控制,则需要它。
主要包装是
org.springframework.security.web
。
5.4.
配置-
spring-security-config.jar
该模块包含安全名称空间解析代码和Java配置代码。
如果将Spring Security XML名称空间用于配置或Spring Security的Java Configuration支持,则需要它。
主要包装是
org.springframework.security.config
。
这些类都不打算直接在应用程序中使用。
5.5.
LDAP-
spring-security-ldap.jar
该模块提供LDAP身份验证和供应代码。
如果您需要使用LDAP认证或管理LDAP用户条目,则为必填项。
顶级软件包是
org.springframework.security.ldap
。
5.6.
OAuth 2.0核心-
spring-security-oauth2-core.jar
spring-security-oauth2-core.jar
包含为OAuth 2.0授权框架和OpenID Connect Core 1.0提供支持的核心类和接口。
使用OAuth 2.0或OpenID Connect Core 1.0的应用程序(例如客户端,资源服务器和授权服务器)需要它。
顶级软件包是
org.springframework.security.oauth2.core
。
5.7.
OAuth 2.0客户端-
spring-security-oauth2-client.jar
spring-security-oauth2-client.jar
包含Spring Security对OAuth 2.0授权框架和OpenID Connect Core 1.0的客户端支持。
使用OAuth 2.0登录或OAuth客户端支持的应用程序需要使用它。
顶级软件包是
org.springframework.security.oauth2.client
。
5.8.
OAuth 2.0 JOSE —
spring-security-oauth2-jose.jar
spring-security-oauth2-jose.jar
包含Spring Security对JOSE(Javascript对象签名和加密)框架的支持。
JOSE框架旨在提供一种在各方之间安全地转移索赔的方法。
它是根据一系列规范构建的:
-
JSON Web令牌(JWT)
-
JSON Web签名(JWS)
-
JSON Web加密(JWE)
-
JSON Web密钥(JWK)
它包含以下顶级程序包:
-
org.springframework.security.oauth2.jwt
-
org.springframework.security.oauth2.jose
5.9。
OAuth 2.0资源服务器-
spring-security-oauth2-resource-server.jar
spring-security-oauth2-resource-server.jar
包含Spring Security对OAuth 2.0资源服务器的支持。
它用于通过OAuth 2.0承载令牌保护API。
顶级软件包是
org.springframework.security.oauth2.server.resource
。
5.10。
ACL-
spring-security-acl.jar
该模块包含专门的域对象ACL实现。
它用于将安全性应用于应用程序中的特定域对象实例。
顶级软件包是
org.springframework.security.acls
。
5.11.
CAS —
spring-security-cas.jar
该模块包含Spring Security的CAS客户端集成。
如果要对CAS单点登录服务器使用Spring Security Web认证,则应该使用它。
顶级软件包是
org.springframework.security.cas
。
6.样品
Spring Security包含许多 示例 应用程序。
Servlet应用
Filter
。
这意味着它可以与在Servlet容器中运行的任何应用程序一起使用。
更具体地说,您无需在基于Servlet的应用程序中使用Spring即可利用Spring Security。
7.你好Spring Security
本节介绍了使用 Spring Boot , Java Configuration 或 XML Configuration 的最小Spring Security应用程序 。
7.1. 你好Spring Security(启动)
本节介绍了如何将Spring Security与Spring Boot结合使用的最小设置。 有关如何将Spring Security与Java配置一起使用,请参阅 Hello Spring Security(Java配置) 。 有关如何将Spring Security与XML配置一起使用,请参阅 Hello Spring Security(XML) 。
完整的应用程序可以在 samples / boot / helloworld中找到 |
7.1.2. 启动Hello Spring安全启动
现在,您可以
使用Maven插件的
目标
来
运行Spring Boot应用程序
run
。
以下示例显示了如何执行此操作(以及执行此操作的输出的开头):
$ ./mvn spring-boot:run
...
INFO 23689 --- [ restartedMain] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
...
7.1.3. Spring Boot自动配置
Spring Boot自动:
-
启用Spring Security的默认配置,该默认配置将Servlet创建
Filter
为名为的BeanspringSecurityFilterChain
。 此bean负责应用程序内的所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。 -
UserDetailsService
用用户名 创建一个 bean,user
并将其随机生成的密码记录到控制台。 -
为每个请求向以Servlet容器
Filter
命名的bean 注册springSecurityFilterChain
。
Spring Boot的配置不多,但功能很多。 功能摘要如下:
-
要求经过身份验证的用户才能与应用程序进行任何交互
-
为您生成一个默认的登录表单
-
让用户名
user
和密码登录到控制台,以使用基于表单的身份验证进行身份验证(在前面的示例中,密码为8e557245-73e2-4286-969a-ff57fe326336
) -
使用BCrypt保护密码存储
-
让用户注销
-
CSRF攻击 预防
-
会话固定 保护
-
安全标题集成
-
HTTP严格传输安全性 用于安全请求
-
缓存控制(以后可以由您的应用程序覆盖,以允许缓存您的静态资源)
-
X-Frame-Options集成有助于防止 Clickjacking
-
-
与以下Servlet API方法集成:
7.2. Hello Spring Security(Java配置)
本节介绍如何在Java配置中使用Spring Security。 有关如何将Spring Security与XML配置一起使用,请参阅 Hello Spring Security(XML) 。 有关如何在Spring Boot配置中使用Spring Security的信息,请参见 Hello Spring Security(引导) 。
您可以在 samples / javaconfig / helloworld中 找到完整的应用程序 。 |
7.2.2.
最小
@EnableWebSecurity
配置
第一步是创建我们的Spring Security Java配置。
该配置将创建一个servlet
Filter
(称为
springSecurityFilterChain
),该
servlet
负责应用程序中的所有安全功能(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。
以下示例显示了Spring Security Java配置的最基本示例:
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.provisioning.*;
@EnableWebSecurity
public class WebSecurityConfig {
// @formatter:off
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
// @formatter:on
}
此配置的确没有太多,但是却做了很多。 功能摘要如下:
-
要求经过身份验证的用户才能与应用程序进行任何交互
-
为您生成一个默认的登录表单
-
允许用户使用 基于表单的身份验证 的用户名
user
和密码进行password
身份验证 -
使用BCrypt保护密码存储
-
让用户注销
-
CSRF攻击 预防
-
会话固定 保护
-
安全标题集成
-
HTTP严格传输安全性 用于安全请求
-
缓存控制(以后可以由您的应用程序覆盖,以允许缓存您的静态资源)
-
X-Frame-Options集成有助于防止 Clickjacking
-
-
与以下Servlet API方法集成:
7.2.3.
使用
AbstractSecurityWebApplicationInitializer
下一步是向
springSecurityFilterChain
战争
登记
。
Spring Security提供了一个
AbstractSecurityWebApplicationInitializer
利用
Spring的WebApplicationInitializer支持
的基类(
)
。
以下示例显示了示例配置:
import org.springframework.security.web.context.*;
public class SecurityInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() {
super(WebSecurityConfig.class);
}
}
在
SecurityInitializer
做以下的事情:
-
添加
ContextLoaderListener
会加载WebSecurityConfig
。 -
查找
Filter
名为 类型的BeanspringSecurityFilterChain
并将其注册以处理应用程序中的每个URL。
如果要与Spring MVC应用程序集成,请确保配置,
例子21. MvcInitializer.java
|
7.3. Hello Spring安全性(XML)
本节介绍如何在XML配置中使用Spring Security。 有关如何将Spring Security与Java配置一起使用,请参阅 Hello Spring Security(Java配置) 。 有关如何在Spring Boot配置中使用Spring Security的信息,请参见 Hello Spring Security(引导) 。
7.3.2.
最小
<http>
配置
在本节中,我们讨论如何将Spring Security与XML配置一起使用。
完整的应用程序可以在 samples / xml / helloworld中找到 |
第一步是创建我们的Spring Security XML配置。
该配置将创建一个Servlet
Filter
(称为
springSecurityFilterChain
),该
Servlet
负责应用程序中的所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。
以下示例显示了Spring Security XML配置的最基本示例:
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
<http />
<user-service>
<user name="user" password="{noop}password" authorities="ROLE_USER" />
</user-service>
</b:beans>
此配置的确没有太多,但是却做了很多。 功能摘要如下:
-
要求经过身份验证的用户才能与应用程序进行任何交互
-
为您生成一个默认的登录表单
-
允许用户使用 基于表单的身份验证 的用户名
user
和密码进行password
身份验证 -
使用BCrypt保护密码存储
-
让用户注销
-
CSRF攻击 预防
-
会话固定 保护
-
安全标题集成
-
HTTP严格传输安全性 用于安全请求
-
缓存控制(以后可以由您的应用程序覆盖,以允许缓存您的静态资源)
-
X-Frame-Options集成有助于防止 Clickjacking
-
-
与以下Servlet API方法集成:
7.3.3.
web.xml
组态
下一步是确保已读入我们的安全性配置。为此,我们需要确保a
ContextLoaderListener
已注册并且
a
contextConfigLocation
包括配置。
以下示例显示了如何执行此操作:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 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">
<!--
Loads the Spring configurations from contextConfigLocation
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
The locations of the Spring Configuration. In this case, all configuration is
in /WEB-INF/spring/
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/*.xml
</param-value>
</context-param>
<!--
DelegatingFilterProxy looks for a Spring bean by the name of filter (springSecurityFilterChain) and delegates
all work to that Bean. This is how the Servlet Container can a Spring Bean to act as a Servlet Filter.
-->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
如果您与现有的Spring MVC应用程序集成,请确保配置,
src / main / webapp / WEB-INF / web.xml
|
8.架构与实现
一旦熟悉了设置和运行一些基于名称空间配置的应用程序,您可能希望对框架在名称空间外观背后的实际工作方式有更多的了解。 像大多数软件一样,Spring Security具有某些中心接口,类和概念抽象,它们在整个框架中都经常使用。 在 Reference指南的这一部分中,我们将研究其中的一些,并了解它们如何协同工作以在Spring Security中支持身份验证和访问控制。
8.1. 技术概述
8.1.1. 运行环境
Spring Security 5.2.0.RELEASE需要Java 8 Runtime Environment或更高版本。 由于Spring Security旨在以独立的方式运行,因此无需在Java运行时环境中放置任何特殊的配置文件。 特别是,不需要配置特殊的Java身份验证和授权服务(JAAS)策略文件或将Spring Security放置在公共类路径位置。
同样,如果您使用的是EJB容器或Servlet容器,则无需在任何地方放置任何特殊的配置文件,也无需在服务器类加载器中包含Spring Security。 所有必需的文件将包含在您的应用程序中。
这种设计提供了最大的部署时间灵活性,因为您只需将目标工件(JAR,WAR或EAR)从一个系统复制到另一个系统即可立即使用。
8.1.2. 核心组件
从Spring Security 3.0开始,将
spring-security-core
jar
的内容
减少到最低限度。
它不再包含与Web应用程序安全性,LDAP或名称空间配置有关的任何代码。
我们将在这里看一下您将在核心模块中找到的一些Java类型。
它们代表了框架的构建块,因此,如果您需要超越简单的名称空间配置,那么即使实际上不需要直接与它们进行交互,也必须了解它们的含义,这一点很重要。
SecurityContextHolder,SecurityContext和身份验证对象
最基本的对象是
SecurityContextHolder
。
我们在这里存储应用程序当前安全上下文的详细信息,其中包括当前使用该应用程序的主体的详细信息。
默认情况下,会
SecurityContextHolder
使用
ThreadLocal
来存储这些详细信息,这意味着即使没有将安全性上下文作为这些方法的参数显式传递,安全性上下文也始终可用于同一执行线程中的方法。
ThreadLocal
如果在处理当前委托人的请求之后要清除线程,则以这种方式
使用a
是非常安全的。
当然,Spring Security会自动为您解决此问题,因此无需担心。
由于某些应用程序使用
ThreadLocal
线程的特定方式,因此它们
并不完全适合使用
。
例如,Swing客户端可能希望Java虚拟机中的所有线程都使用相同的安全上下文。
SecurityContextHolder
可以在启动时配置策略,以指定希望如何存储上下文。
对于独立应用程序,您将使用该
SecurityContextHolder.MODE_GLOBAL
策略。
其他应用程序可能希望让安全线程产生的线程也采用相同的安全身份。
这是通过使用实现的
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
。
您可以通过
SecurityContextHolder.MODE_THREADLOCAL
两种方式
从默认更改模式
。
第一个是设置系统属性,第二个是在上调用静态方法
SecurityContextHolder
。
大多数应用程序都不需要更改默认值,但是如果需要更改,请查看JavaDoc
SecurityContextHolder
了解更多。
获取有关当前用户的信息
在内部,
SecurityContextHolder
我们存储了当前与应用程序交互的主体的详细信息。
Spring Security使用一个
Authentication
对象来表示此信息。
您通常不需要自己创建一个
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
作为主体。
UserDetailsService
上述代码片段中需要注意的另一项内容是,您可以从
Authentication
对象中
获取主体
。
委托人只是一个
Object
。
大多数情况下,可以将其转换为
UserDetails
对象。
UserDetails
是Spring Security中的核心接口。
它代表一个原理,但以一种可扩展的和特定于应用程序的方式。
可以将其
UserDetails
看作是您自己的用户数据库和Spring Security内部需要什么之间的适配器
SecurityContextHolder
。
作为您自己的用户数据库中某些内容的表示形式,通常您会将强制
UserDetails
转换为应用程序提供的原始对象,以便可以调用特定于业务的方法(例如
getEmail()
,
getEmployeeNumber()
等等)。
现在,您可能想知道,那么什么时候提供
UserDetails
对象?
我怎么做?
我以为您说的这个东西是声明性的,我不需要编写任何Java代码-有什么用?
简短的答案是有一个名为的特殊接口
UserDetailsService
。
此接口上的唯一方法接受一个
String
基于用户名的参数,并返回
UserDetails
:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
这是在Spring Security中为用户加载信息的最常用方法,只要需要有关用户的信息,就会在整个框架中使用它。
成功认证后,
UserDetails
将用于构建
Authentication
存储在中的对象
SecurityContextHolder
(有关更多信息,请
参见
下文
)。
好消息是,我们提供了许多
UserDetailsService
实现,包括一个使用内存映射(
InMemoryDaoImpl
)的实现,另一个使用JDBC(
JdbcDaoImpl
)的实现。
但是,大多数用户倾向于编写自己的应用程序,而其实现往往只是位于代表其员工,客户或应用程序其他用户的现有数据访问对象(DAO)之上。
请记住,您的
UserDetailsService
收益始终可以通过
SecurityContextHolder
使用上面的代码片段
获得
的好处
。
经常会有一些困惑
|
授予的权限
除了主体,提供的另一个重要方法
Authentication
是
getAuthorities()
。
此方法提供了一个
GrantedAuthority
对象
数组
。
GrantedAuthority
毫不奇怪,
A
是授予委托人的权限。
此类权限通常是“角色”,例如
ROLE_ADMINISTRATOR
或
ROLE_HR_SUPERVISOR
。
稍后将这些角色配置为Web授权,方法授权和域对象授权。
Spring Security的其他部分能够解释这些权限,并希望它们存在。
GrantedAuthority
对象通常由加载
UserDetailsService
。
通常,
GrantedAuthority
对象是应用程序范围的权限。
它们不特定于给定的域对象。
因此,您不可能
GrantedAuthority
代表
Employee
对象号54
的权限
,因为如果有成千上万个这样的权限,您很快就会用完内存(或者至少导致应用程序花费很长时间来完成)。验证用户身份)。
当然,Spring Security是专门为满足这一通用需求而设计的,但您可以为此目的使用项目的域对象安全性功能。
摘要
回顾一下,到目前为止,我们已经看到了Spring Security的主要组成部分:
-
SecurityContextHolder
,提供对的访问SecurityContext
。 -
SecurityContext
,以保存Authentication
(可能还有特定 于 请求的)安全信息。 -
Authentication
,以特定于Spring Security的方式表示主体。 -
GrantedAuthority
,以反映授予主体的应用程序范围的权限。 -
UserDetails
,以提供必要的信息以从应用程序的DAO或其他安全数据源构建Authentication对象。 -
UserDetailsService
,以UserDetails
在传入String
基于-的用户名(或证书ID等) 时 创建一个 。
现在,您已经了解了这些重复使用的组件,下面让我们仔细看看身份验证的过程。
8.1.3. 认证方式
Spring Security可以参与许多不同的身份验证环境。 虽然我们建议人们使用Spring Security进行身份验证,而不是与现有的容器管理的身份验证集成,但是仍然支持它-与您自己的专有身份验证系统集成一样。
Spring Security中的身份验证是什么?
让我们考虑一个大家都熟悉的标准身份验证方案。
-
提示用户使用用户名和密码登录。
-
系统(成功)验证用户名的密码正确。
-
获取该用户的上下文信息(他们的角色列表等)。
-
为用户建立了安全上下文
-
用户可能会继续执行某些操作,该操作可能会受到访问控制机制的保护,该访问控制机制会根据当前安全上下文信息检查该操作所需的权限。
前四项构成了身份验证过程,因此我们将看看它们在Spring Security中是如何发生的。
-
获取用户名和密码,并将其组合到的一个实例
UsernamePasswordAuthenticationToken
(Authentication
接口 的实例 ,我们之前已经看到)。 -
令牌会传递到的实例
AuthenticationManager
进行验证。 -
成功认证后 ,
AuthenticationManager
返回一个完全填充的Authentication
实例。 -
通过调用
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<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
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正常工作并仍然提供授权功能是很容易的。
您所需要做的就是编写一个过滤器(或等效过滤器),该过滤器从某个位置读取第三方用户信息,构建特定于Spring Security的
Authentication
对象,然后将其放入
SecurityContextHolder
。
在这种情况下,您还需要考虑通常由内置身份验证基础结构自动处理的事情。
例如,您可能需要先创建HTTP会话来
缓存请求之间的上下文
,然后再将响应写入客户端
[ 1 ]
。
如果您想知道
AuthenticationManager
在实际示例中
如何
实现,我们将在
核心服务一章中进行介绍
。
8.1.4. Web应用程序中的身份验证
现在,让我们探讨一下在Web应用程序中使用Spring Security(未
web.xml
启用安全性)的情况。
如何认证用户并建立安全上下文?
考虑典型的Web应用程序的身份验证过程:
-
您访问主页,然后单击链接。
-
请求发送到服务器,服务器确定您已请求受保护的资源。
-
由于您目前尚未通过身份验证,因此服务器会发回响应,指示您必须进行身份验证。 响应将是HTTP响应代码,或重定向到特定网页。
-
根据身份验证机制,您的浏览器将重定向到特定网页,以便您可以填写表格,或者浏览器将以某种方式检索您的身份(通过BASIC身份验证对话框,cookie,X.509证书等)。 )。
-
浏览器将响应发送回服务器。 这将是包含您填写的表单内容的HTTP POST或包含身份验证详细信息的HTTP标头。
-
接下来,服务器将决定所提供的凭据是否有效。 如果有效,则将进行下一步。 如果它们无效,通常会要求您的浏览器再试一次(因此您返回到上面的第二步)。
-
您尝试引起身份验证过程的原始请求将被重试。 希望您已获得足够授权的身份验证,以访问受保护的资源。 如果您具有足够的访问权限,则请求将成功。 否则,您将收到一个HTTP错误代码403,表示“禁止”。
Spring Security具有负责上述大多数步骤的不同类。
主要参与者(按照使用顺序)是
ExceptionTranslationFilter
,
AuthenticationEntryPoint
和和“身份验证机制”,它们负责调用
AuthenticationManager
上一节中看到的。
ExceptionTranslationFilter
ExceptionTranslationFilter
是一个Spring Security过滤器,它负责检测抛出的任何Spring Security异常。
此类异常通常由
AbstractSecurityInterceptor
授权服务的主要提供者
抛出
。
我们将
AbstractSecurityInterceptor
在下一节中
讨论
,但是现在我们只需要知道它会产生Java异常,并且对HTTP或对主体进行身份验证一无所知。
取而代之的是,
ExceptionTranslationFilter
提供此服务,具体负责返回错误代码403(如果主体已通过身份验证,因此仅缺少足够的访问权限-按照上述第七步),或启动
AuthenticationEntryPoint
(如果主体未通过身份验证,因此我们需要开始第三步)。
认证入口点
该
AuthenticationEntryPoint
负责第三步在上面的列表中。
可以想象,每个Web应用程序将具有默认的身份验证策略(嗯,可以像配置Spring Security中的所有其他功能一样配置它,但是现在让我们保持简单)。
每个主要认证系统都有其自己的
AuthenticationEntryPoint
实现,该实现通常执行步骤3中所述的操作之一。
认证机制
一旦您的浏览器提交了身份验证凭据(作为HTTP表单帖子或HTTP标头),服务器上就需要有一些东西来“收集”这些身份验证详细信息。
到目前为止,我们位于上述列表的第六步。
在Spring Security中,我们有一个特殊的名称,用于从用户代理(通常是Web浏览器)收集身份验证详细信息的功能,将其称为“身份验证机制”。
示例是基于表单的登录和基本身份验证。
从用户代理收集到身份验证详细信息后,
Authentication
便会构建
一个
“请求”对象,然后将其呈现给
AuthenticationManager
。
身份验证机制收到完满的
Authentication
对象后,它将认为请求有效,将
Authentication
放入
SecurityContextHolder
,并导致重试原始请求(上述步骤7)。
另一方面,如果
AuthenticationManager
拒绝了请求,则认证机制将要求用户代理重试(上面的第二步)。
在请求之间存储SecurityContext
根据应用程序的类型,可能需要制定一种策略来存储用户操作之间的安全上下文。
在典型的Web应用程序中,用户登录一次,然后通过其会话ID进行标识。
服务器缓存持续时间会话的主体信息。
在Spring Security中,存储
SecurityContext
请求之间
的责任
落在
SecurityContextPersistenceFilter
,默认情况下,将上下文存储为
HttpSession
HTTP请求之间
的
属性。
它将上下文还原到
SecurityContextHolder
每个请求,并且至关重要的是,清除
SecurityContextHolder
请求何时完成。
HttpSession
为了安全起见,
您不应该直接与
进行
互动
。
这样做根本没有道理-始终使用
SecurityContextHolder
替代项。
许多其他类型的应用程序(例如,无状态RESTful Web服务)不使用HTTP会话,并且将在每个请求上重新进行身份验证。
但是,仍然必须
SecurityContextPersistenceFilter
确保链中包含,以确保
SecurityContextHolder
在每次请求后清除。
在单个会话中接收并发请求的应用程序中,
|
8.1.5. Spring Security中的访问控制(授权)
在Spring Security中负责做出访问控制决策的主要接口是
AccessDecisionManager
。
它具有一种
decide
方法,
该
方法采用
Authentication
代表请求访问的主体
的
对象,“安全对象”(请参见下文)和适用于该对象的安全元数据属性列表(例如授予访问权限所需的角色列表) )。
安全和AOP建议
如果您熟悉AOP,您会知道有不同类型的建议可用:之前,之后,引发和周围。 环绕建议非常有用,因为顾问可以选择是否继续进行方法调用,是否修改响应以及是否引发异常。 Spring Security提供了有关方法调用以及Web请求的全面建议。 我们使用Spring的标准AOP支持来获得方法调用的通用建议,并使用标准的Filter来实现Web请求的通用建议。
对于不熟悉AOP的人来说,要了解的关键是Spring Security可以帮助您保护方法调用以及Web请求。 大多数人都对在其服务层上确保方法调用感兴趣。 这是因为服务层是大多数业务逻辑驻留在当前Java EE应用程序中的地方。 如果只需要在服务层中确保方法调用的安全,那么Spring的标准AOP就足够了。 如果需要直接保护域对象,则可能会发现AspectJ值得考虑。
您可以选择使用AspectJ或Spring AOP执行方法授权,也可以选择使用过滤器执行Web请求授权。 您可以一起使用零,一,二或三种方法。 主流用法是执行一些Web请求授权,并在服务层上执行一些Spring AOP方法调用授权。
安全对象和AbstractSecurityInterceptor
那么,什么 是 “安全对象”? Spring Security使用该术语来指代任何可以对其应用安全性(例如授权决策)的对象。 最常见的示例是方法调用和Web请求。
每个受支持的安全对象类型都有其自己的拦截器类,该类是的子类
AbstractSecurityInterceptor
。
重要的
AbstractSecurityInterceptor
是,在调用时,
如果主体已通过身份验证
,
SecurityContextHolder
则将包含有效
Authentication
的内容。
AbstractSecurityInterceptor
提供用于处理安全对象请求的一致工作流,通常是:
-
查找与当前请求关联的“配置属性”
-
将安全对象,当前
Authentication
属性和配置属性提交AccessDecisionManager
给授权决策 -
(可选)更改
Authentication
调用 的 依据 -
允许进行安全对象调用(假设已授予访问权限)
-
调用
AfterInvocationManager
如果配置,一旦调用返回。 如果调用引发了异常,AfterInvocationManager
则不会调用。
什么是配置属性?
可以将“配置属性”视为一个String,它对所使用的类具有特殊的含义
AbstractSecurityInterceptor
。
它们由
ConfigAttribute
框架内的
接口表示
。
它们可以是简单的角色名称,也可以具有更复杂的含义,具体取决于实现的复杂程度
AccessDecisionManager
。
使用
AbstractSecurityInterceptor
配置了
SecurityMetadataSource
,它使用来查找安全对象的属性。
通常,此配置将对用户隐藏。
配置属性将作为安全方法的注释或安全URL的访问属性输入。
例如,当我们
<intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>
在名称空间介绍中
看到类似内容
时,这表示配置属性
ROLE_A
和
ROLE_B
适用于与给定模式匹配的Web请求。
实际上,使用默认
AccessDecisionManager
配置,这意味着
GrantedAuthority
将允许具有这两个属性之一匹配的
任何人
访问。
严格来说,它们只是属性,其解释取决于
AccessDecisionManager
实现。
前缀的使用
ROLE_
是一个标记,以指示这些属性是角色,并且应由Spring Security的消费
RoleVoter
。
这仅
AccessDecisionManager
在使用
基于投票者的情况下才有意义
。
我们将
AccessDecisionManager
在
授权一章中
了解如何
实现
。
RunAsManager
假设
AccessDecisionManager
决定允许该请求,
AbstractSecurityInterceptor
通常将继续进行该请求。
话虽这么说,在极少数情况下,用户可能希望
用不同的
替换
Authentication
内部的
,这由
调用a
处理
。
在相当不常见的情况下,例如服务层方法需要调用远程系统并显示不同的标识时,这可能很有用。
因为Spring Security会自动将安全身份从一台服务器传播到另一台服务器(假设您使用的是正确配置的RMI或HttpInvoker远程协议客户端),所以这可能很有用。
SecurityContext
Authentication
AccessDecisionManager
RunAsManager
AfterInvocationManager
在安全对象调用继续进行之后,然后返回-这可能意味着方法调用完成或过滤器链继续进行-
AbstractSecurityInterceptor
最终有机会处理该调用。
在此阶段,
AbstractSecurityInterceptor
有兴趣修改返回对象。
我们可能希望发生这种情况,因为无法在安全对象调用的“途中”做出授权决定。
由于高度可插拔,因此
AbstractSecurityInterceptor
将控制权传递给,
AfterInvocationManager
以便在需要时实际修改对象。
此类甚至可以完全替换对象,或者引发异常,也可以按照其选择的任何方式对其进行更改。
调用后检查仅在调用成功的情况下执行。
如果发生异常,将跳过其他检查。
AbstractSecurityInterceptor
安全拦截器和“安全对象”模型
中显示了它及其相关对象
8.2. 核心服务
现在,我们对Spring Security的架构和核心类的高度概括,让我们来仔细看看一个或两个核心接口及其实现的,特别是
AuthenticationManager
,
UserDetailsService
和
AccessDecisionManager
。
这些在本文档的其余部分中会定期出现,因此了解它们的配置方式和操作方式非常重要。
8.2.1. AuthenticationManager,ProviderManager和AuthenticationProvider
该
AuthenticationManager
只是一个接口,这样的实现可以让我们选择,但它是如何在实践中运作?
如果我们需要检查多个身份验证数据库或不同身份验证服务(例如数据库和LDAP服务器)的组合,该怎么办?
调用Spring Security中的默认实现,
ProviderManager
而不是处理身份验证请求本身,它委托给已配置的
AuthenticationProvider
s
列表,
依次查询每个s,以查看其是否可以执行身份验证。
每个提供程序都将引发异常或返回完全填充的
Authentication
对象。
记得我们的好朋友,
UserDetails
和
UserDetailsService
?
如果没有,请返回上一章并刷新您的记忆。
验证身份验证请求的最常见方法是加载相应
UserDetails
的密码,并对照用户输入的密码检查已加载的密码。
这是
DaoAuthenticationProvider
(见下文)
使用的方法
。
加载的
UserDetails
对象-尤其是
GrantedAuthority
包含的对象-构建
Authentication
成功通过身份验证返回并存储在中的
完全填充
对象
时将使用
它
SecurityContext
。
如果使用名称空间,
ProviderManager
则会在内部创建并维护
的实例
,然后使用名称空间身份验证提供程序元素将提供程序添加到该名称空间(请参阅
“名称空间”一章
)。
在这种情况下,您不应
ProviderManager
在应用程序上下文中
声明
bean。
但是,如果不使用名称空间,则可以这样声明:
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
<ref local="ldapAuthenticationProvider"/>
</list>
</constructor-arg>
</bean>
在上面的示例中,我们有三个提供程序。
它们按照所示的顺序进行尝试(使用来暗示
List
),每个提供程序都可以尝试进行身份验证,也可以通过简单地返回来跳过身份验证
null
。
如果所有实现均返回null,
ProviderManager
则将抛出
ProviderNotFoundException
。
如果您想了解有关链接提供程序的更多信息,请 Reference
ProviderManager
Javadoc。
诸如Web表单登录处理过滤器之类的身份验证机制注入了对的引用
ProviderManager
,并将调用它来处理其身份验证请求。
您所需的提供程序有时可以与身份验证机制互换,而在其他时候,它们将取决于特定的身份验证机制。
例如,
DaoAuthenticationProvider
并且
LdapAuthenticationProvider
与提交简单的用户名/密码身份验证请求的任何机制兼容,因此将与基于表单的登录名或HTTP Basic身份验证一起使用。
另一方面,某些身份验证机制会创建一个身份验证请求对象,该对象只能由一种类型的
AuthenticationProvider
。
一个示例就是JA-SIG CAS,它使用服务票证的概念,因此只能由进行身份验证
CasAuthenticationProvider
。
您不必太担心这一点,因为如果您忘记注册合适的提供程序,则
ProviderNotFoundException
在尝试进行身份验证时
只会收到一个
。
清除成功认证的凭据
默认情况下(从Spring Security 3.1开始),
ProviderManager
它将尝试从
Authentication
成功的身份验证请求返回
的
对象中
清除所有敏感的凭据信息
。
这样可以防止将密码之类的信息保留的时间过长。
例如,在使用用户对象的缓存来提高无状态应用程序的性能时,这可能会导致问题。
如果
Authentication
包含对缓存中某个对象(例如
UserDetails
实例)
的
引用,并且已删除其凭据,则将无法再针对缓存的值进行身份验证。
如果使用缓存,则需要考虑到这一点。
一个明显的解决方案是先在高速缓存实现中或在
AuthenticationProvider
创建返回
Authentication
对象的对象
中创建
对象的副本
。
或者,您可以在禁用该
eraseCredentialsAfterAuthentication
属性
ProviderManager
。
有关更多信息,请参见Javadoc。
DaoAuthenticationProvider
AuthenticationProvider
Spring Security实现
的最简单的方法
是
DaoAuthenticationProvider
,也是框架最早支持的方法之一。
它利用
UserDetailsService
(作为DAO)查找用户名,密码和
GrantedAuthority
s。
只需将比较提交的密码
UsernamePasswordAuthenticationToken
与加载
的密码,即可对用户进行身份验证
UserDetailsService
。
配置提供程序非常简单:
<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>
该
PasswordEncoder
是可选的。
A
PasswordEncoder
提供对
UserDetails
从配置的返回
的
对象中
提供的密码进行编码和解码
UserDetailsService
。
这将在
下面
更详细地讨论
。
8.2.2. UserDetailsService实施
如本 Reference指南前面所述,大多数身份验证提供程序都利用
UserDetails
和
UserDetailsService
接口。
回想一下,合同
UserDetailsService
是一种方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
返回的
UserDetails
是一个接口,该接口提供了获取器,该获取器保证以非空方式提供身份验证信息,例如用户名,密码,已授予的权限以及用户帐户是启用还是禁用。
即使用
UserDetailsService
户名和密码实际上没有用作身份验证决策的一部分,
大多数身份验证提供程序也会使用
。
他们可能
UserDetails
仅
将返回的
对象用作其
GrantedAuthority
信息,因为某些其他系统(例如LDAP或X.509或CAS等)承担了实际验证凭据的责任。
给定的实现
UserDetailsService
是如此简单,因此用户应该使用自己选择的持久性策略轻松检索身份验证信息。
话虽如此,Spring Security确实包含一些有用的基本实现,我们将在下面进行介绍。
内存中身份验证
创建自定义
UserDetailsService
实现从选择的持久性引擎中提取信息
很容易使用
,但是许多应用程序不需要这种复杂性。
如果您正在构建原型应用程序或刚开始集成Spring Security,而又不想真正花费时间配置数据库或编写
UserDetailsService
实现,
则尤其如此
。
对于这种情况,一个简单的选择是使用
user-service
来自安全
名称空间
的
元素
:
<user-service id="userDetailsService">
<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
NoOpPasswordEncoder should be used. This is not safe for production, but makes reading
in samples easier. Normally passwords should be hashed using BCrypt -->
<user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="{noop}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
,示例配置如下所示:
<bean 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=""/>
</bean>
<bean id="userDetailsService"
class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
您可以通过修改
DriverManagerDataSource
上面显示的内容
使用不同的关系数据库管理系统
。
您还可以使用从JNDI获得的全局数据源,就像其他任何Spring配置一样。
权威团体
默认情况下,
JdbcDaoImpl
假设权限直接映射到用户,则为单个用户加载权限(请参阅
数据库架构附录
)。
另一种方法是将权限划分为组,然后将组分配给用户。
有些人喜欢使用这种方法来管理用户权限。
有关
JdbcDaoImpl
如何启用组权限的更多信息,
请参见
Javadoc。
组模式也包含在附录中。
9.认证
9.1. 内存中身份验证
我们已经看到了为单个用户配置内存中身份验证的示例。 下面是配置多个用户的示例:
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
9.2. JDBC验证
您可以找到更新以支持基于JDBC的身份验证。
下面的示例假定您已经
DataSource
在应用程序中
定义了一个
。
该
JDBC-javaconfig
样品提供了使用基于JDBC认证的一个完整的示例。
@Autowired
private DataSource dataSource;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser(users.username("user").password("password").roles("USER"))
.withUser(users.username("admin").password("password").roles("USER","ADMIN"));
}
9.3. LDAP验证
9.3.1. 总览
LDAP通常被组织用作用户信息的中央存储库和身份验证服务。 它还可以用于存储应用程序用户的角色信息。
关于如何配置LDAP服务器,有许多不同的方案,因此Spring Security的LDAP提供程序是完全可配置的。 它使用单独的策略接口进行身份验证和角色检索,并提供可以配置为处理各种情况的默认实现。
在尝试将其与Spring Security结合使用之前,您应该熟悉LDAP。 以下链接很好地介绍了相关概念,并提供了使用免费LDAP服务器OpenLDAP设置目录的指南: http : //www.zytrax.com/books/ldap/ 。 熟悉用于从Java访问LDAP的JNDI API可能也很有用。 我们在LDAP提供程序中未使用任何第三方LDAP库(Mozilla,JLDAP等),但是Spring LDAP被广泛使用,因此如果您计划添加自己的自定义项,则对该项目有些熟悉可能会很有用。
使用LDAP身份验证时,重要的是要确保正确配置LDAP连接池。 如果您不熟悉该操作,可以 Reference Java LDAP文档 。
9.3.2. 将LDAP与Spring Security结合使用
Spring Security中的LDAP认证可以大致分为以下几个阶段。
-
从登录名获取唯一的LDAP“专有名称”或DN。 除非事先知道用户名到DN的确切映射,否则这通常意味着在目录中执行搜索。 因此,用户在登录时可能会输入名称“ joe”,但是用于验证LDAP的实际名称将是完整DN,例如
uid=joe,ou=users,dc=spring,dc=io
。 -
通过“绑定”该用户或通过对该用户的密码与DN目录条目中的password属性进行远程“比较”操作来对用户进行身份验证。
-
加载用户的权限列表。
例外是仅使用LDAP目录检索用户信息并在本地对其进行身份验证。 这可能是不可能的,因为目录经常被设置为具有诸如用户密码之类的属性的有限读取访问权限。
我们将在下面查看一些配置方案。 有关可用配置选项的完整信息,请查阅安全名称空间模式(XML编辑器中应提供该信息)。
9.4. 配置LDAP服务器
您需要做的第一件事是配置要对其进行身份验证的服务器。
这是使用
<ldap-server>
安全名称空间中
的
元素
完成的
。
可以使用以下
url
属性
将其配置为指向外部LDAP服务器
:
<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
spring-security
提供与
嵌入式ldap服务器的
集成
apacheds
并
unboundid
作为嵌入式ldap服务器的
集成
。
您可以使用中的属性
mode
在
它们之间进行选择
ldap-server
。
|
9.4.1. 使用嵌入式测试服务器
该
<ldap-server>
元素还可以用于创建嵌入式服务器,这对于测试和演示非常有用。
在这种情况下,您可以不带
url
属性
使用它
:
<ldap-server root="dc=springframework,dc=org"/>
在这里,我们指定目录的根DIT应该为“ dc = springframework,dc = org”,这是默认设置。
通过这种方式,名称空间解析器将创建一个嵌入式Apache Directory服务器,并在类路径中扫描所有LDIF文件,并尝试将其加载到服务器中。
您可以使用
ldif
属性来自
定义此行为,该
属性定义了要加载的LDIF资源:
<ldap-server ldif="classpath:users.ldif" />
这使LDAP的启动和运行变得容易得多,因为在任何时候都无法与外部服务器一起工作。 它还使用户免受连接Apache Directory服务器所需的复杂bean配置的影响。 使用普通的Spring Beans,配置将更加混乱。 您必须具有必要的Apache Directory依赖项jar,供应用程序使用。 这些可以从LDAP示例应用程序获得。
9.4.2. 使用绑定身份验证
这是最常见的LDAP身份验证方案。
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"/>
这个简单的示例将通过用提供的模式替换用户登录名并尝试使用登录密码将该用户绑定来获取该用户的DN。 如果所有用户都存储在目录中的单个节点下,则可以。 相反,如果您希望配置LDAP搜索过滤器来定位用户,则可以使用以下方法:
<ldap-authentication-provider user-search-filter="(uid={0})"
user-search-base="ou=people"/>
如果与上述服务器定义一起使用,这将
ou=people,dc=springframework,dc=org
使用
user-search-filter
属性
的值
作为过滤器
在DN下执行搜索
。
再次用用户登录名代替过滤器名称中的参数,因此它将搜索
uid
属性等于用户名
的条目
。
如果
user-search-base
未提供,将从根目录执行搜索。
9.4.3. 正在加载当局
通过以下属性控制如何从LDAP目录中的组加载权限。
-
group-search-base
。 定义目录树的一部分,应在该部分下执行组搜索。 -
group-role-attribute
。 该属性包含组条目定义的权限名称。 默认为cn
-
group-search-filter
。 用于搜索组成员身份的过滤器。 默认值为uniqueMember={0}
,对应于groupOfUniqueNames
LDAP类 [ 2 ] 。 在这种情况下,替换参数是用户的完整专有名称。{1}
如果要过滤登录名,可以使用 该参数 。
因此,如果我们使用以下配置
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"
group-search-base="ou=groups" />
并成功验证为用户“ ben”,随后的权限加载将在目录条目下执行搜索
ou=groups,dc=springframework,dc=org
,以查找包含
uniqueMember
具有value
属性的条目
uid=ben,ou=people,dc=springframework,dc=org
。
默认情况下,权限名称将带有前缀
ROLE_
。
您可以使用
role-prefix
属性
更改此设置
。
如果您不需要任何前缀,请使用
role-prefix="none"
。
有关加载权限的更多信息,请参见
DefaultLdapAuthoritiesPopulator
该类
的Javadoc
。
9.5. 实现类
我们上面使用的名称空间配置选项比明确使用Spring Bean简单易用,而且简洁得多。 在某些情况下,您可能需要了解如何在应用程序上下文中直接配置Spring Security LDAP。 例如,您可能希望自定义某些类的行为。 如果您对使用名称空间配置感到满意,则可以跳过本节和下一节。
最主要的LDAP提供器类是
LdapAuthenticationProvider
,实际上并没有做太多,而是代理的工作,其他两个bean,一个
LdapAuthenticator
和
LdapAuthoritiesPopulator
它负责用户认证和检索用户的组
GrantedAuthority
分别秒。
9.5.1. LdapAuthenticator的实现
验证者还负责检索任何必需的用户属性。 这是因为对属性的权限可能取决于所使用的身份验证类型。 例如,如果以用户身份进行绑定,则可能有必要使用用户自己的权限来读取它们。
Spring Security目前提供两种身份验证策略:
-
直接对LDAP服务器进行身份验证(“绑定”身份验证)。
-
密码比较,将用户提供的密码与存储库中存储的密码进行比较。 可以通过检索password属性的值并在本地对其进行检查来完成此操作,也可以通过执行LDAP“比较”操作来完成,在该操作中,将提供的密码传递给服务器进行比较,并且永远不会检索到真实的密码值。
通用功能
在可能(通过任何一种策略)对用户进行身份验证之前,必须从提供给应用程序的登录名中获得专有名称(DN)。
这可以通过简单的模式匹配(通过设置
setUserDnPatterns
array属性)或通过设置
userSearch
属性来完成。
对于DN模式匹配方法,将使用标准Java模式格式,并将登录名替换为parameter
{0}
。
该模式应相对于配置文件
SpringSecurityContextSource
将绑定
到的DN
(
有关此信息,
请参阅有关
连接LDAP服务器
的部分
)。
例如,如果您使用带有URL的LDAP服务器
ldap://monkeymachine.co.uk/dc=springframework,dc=org
,并且具有pattern
uid={0},ou=greatapes
,则登录名“ gorilla”将映射到DN。
uid=gorilla,ou=greatapes,dc=springframework,dc=org
。
依次尝试每个已配置的DN模式,直到找到匹配项。
有关使用搜索的信息,请参见
下面
有关
搜索对象
的部分
。
也可以使用两种方法的组合-首先检查模式,如果找不到匹配的DN,将使用搜索。
9.5.2. 连接到LDAP服务器
上面讨论的Bean必须能够连接到服务器。
它们都必须提供a
SpringSecurityContextSource
,它是Spring LDAP的扩展
ContextSource
。
除非有特殊要求,否则通常将配置一个
DefaultSpringSecurityContextSource
bean,可以使用LDAP服务器的URL以及(可选)使用“ manager”用户的用户名和密码来配置bean,绑定到服务器时默认使用该用户名和密码(代替匿名绑定)。
有关更多信息,请阅读此类的Javadoc和Spring LDAP的Javadoc
AbstractContextSource
。
9.5.3. LDAP搜索对象
在目录中定位用户条目时,通常需要比简单的DN匹配更复杂的策略。
可以将其封装在一个
LdapUserSearch
实例中,
该
实例可以提供给身份验证器实现,例如,以允许它们定位用户。
提供的实现为
FilterBasedLdapUserSearch
。
FilterBasedLdapUserSearch
该bean使用LDAP过滤器来匹配目录中的用户对象。
Javadoc中针对
JDK DirContext类
的相应搜索方法说明了该过程
。
如此处所述,可以为搜索过滤器提供参数。
对于此类,唯一有效的参数是
{0}
它将用用户的登录名替换。
9.5.4. LdapAuthoritiesPopulator
成功验证用户身份后,
LdapAuthenticationProvider
将通过调用配置的
LdapAuthoritiesPopulator
bean
尝试为用户加载一组权限
。
的
DefaultLdapAuthoritiesPopulator
是,这将通过搜索组的目录,其中所述用户是其成员(通常这些将加载当局的实现
groupOfNames
或
groupOfUniqueNames
目录中的条目)。
有关此类的更多详细信息,请查阅Javadoc。
如果您只想使用LDAP进行身份验证,但是从其他来源(例如数据库)加载授权,则可以提供自己的接口实现,然后注入该接口。
9.5.5. Spring Bean配置
使用我们在这里讨论过的一些bean的典型配置可能看起来像这样:
<bean id="contextSource"
class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/>
<property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
<property name="password" value="password"/>
</bean>
<bean id="ldapAuthProvider"
class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg>
<bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="contextSource"/>
<property name="userDnPatterns">
<list><value>uid={0},ou=people</value></list>
</property>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<constructor-arg ref="contextSource"/>
<constructor-arg value="ou=groups"/>
<property name="groupRoleAttribute" value="ou"/>
</bean>
</constructor-arg>
</bean>
这将设置提供程序以使用URL访问LDAP服务器
ldap://monkeymachine:389/dc=springframework,dc=org
。
身份验证将通过尝试与DN绑定来执行
uid=<user-login-name>,ou=people,dc=springframework,dc=org
。
身份验证成功后,将通过
ou=groups,dc=springframework,dc=org
使用默认过滤器
在DN下搜索来将角色分配给用户
(member=<user’s-DN>)
。
角色名称将从每个匹配项的“ ou”属性中获取。
要配置用户搜索对象,该对象使用过滤器
(uid=<user-login-name>)
而不是DN模式(或除此之外),请配置以下bean
<bean id="userSearch"
class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value=""/>
<constructor-arg index="1" value="(uid={0})"/>
<constructor-arg index="2" ref="contextSource" />
</bean>
并通过设置
BindAuthenticator
bean的
userSearch
属性来
使用它
。
然后,在尝试以该用户身份进行绑定之前,身份验证器将调用搜索对象以获取正确的用户DN。
9.5.6. LDAP属性和自定义的用户详细信息
使用身份验证的最终结果与
LdapAuthenticationProvider
使用标准
UserDetailsService
接口
的常规Spring Security身份验证相同
。
UserDetails
创建
一个
对象并将其存储在返回的
Authentication
对象中。
与使用一样
UserDetailsService
,一个共同的要求是能够自定义此实现并添加额外的属性。
使用LDAP时,这些通常是用户条目中的属性。
UserDetails
对象
的创建
由提供者的
UserDetailsContextMapper
策略
控制,该
策略负责将用户对象与LDAP上下文数据进行映射:
public interface UserDetailsContextMapper {
UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<GrantedAuthority> authorities);
void mapUserToContext(UserDetails user, DirContextAdapter ctx);
}
仅第一种方法与身份验证有关。
如果您提供此接口的实现并将其注入
LdapAuthenticationProvider
,则可以完全控制UserDetails对象的创建方式。
第一个参数是Spring LDAP的实例,
DirContextOperations
它使您可以访问在身份验证期间加载的LDAP属性。
该
username
参数是用于认证的名称,最后一个参数是配置的为用户加载的权限集合
LdapAuthoritiesPopulator
。
上下文数据的加载方式根据所使用的身份验证类型而略有不同。
使用时
BindAuthenticator
,将从bind操作返回的上下文用于读取属性,否则将使用从配置中获得的标准上下文读取数据
ContextSource
(当
配置
了搜索以定位用户时,这将是由搜索对象)。
9.6. Active Directory验证
Active Directory支持其自己的非标准身份验证选项,并且正常使用模式不太适合标准
LdapAuthenticationProvider
。
通常,身份验证是使用域用户名(以形式
user@domain
)而不是使用LDAP可分辨名称来执行的。
为了简化此操作,Spring Security 3.1具有一个身份验证提供程序,该身份验证提供程序是针对典型的Active Directory设置而定制的。
9.6.1. ActiveDirectoryLdapAuthenticationProvider
配置
ActiveDirectoryLdapAuthenticationProvider
非常简单。
您只需要提供域名和提供服务器地址的LDAP URL
[ 3 ]
。
配置示例如下所示:
<bean id="adAuthenticationProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<constructor-arg value="mydomain.com" />
<constructor-arg value="ldap://adserver.mydomain.com/" />
</bean>
请注意,无需指定单独
ContextSource
的位置即可定义服务器位置-Bean是完全独立的。
例如,名为“ Sharon”的用户将能够通过输入用户名
sharon
或完整的Active Directory
userPrincipalName
(即)
进行身份验证
sharon@mydomain.com
。
然后将定位用户的目录条目,并返回属性以供在自定义创建的
UserDetails
对象中使用(
UserDetailsContextMapper
如上所述,可以为此目的注入
a
)。
与目录的所有交互都以用户本身的身份进行。
没有“经理”用户的概念。
默认情况下,从
memberOf
用户条目
的
属性值
获得用户权限
。
可以再次使用来定制分配给用户的权限
UserDetailsContextMapper
。
您还可以将a
GrantedAuthoritiesMapper
注入提供程序实例中,以控制最终在
Authentication
对象
中获得的权限
。
9.7. LDAP Java配置
您可以找到更新以支持基于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实例。
dn:ou = groups,dc = springframework,dc = org 对象类:顶部 对象类:organizationalUnit ou:团体 dn:ou = people,dc = springframework,dc = org 对象类:顶部 对象类:organizationalUnit ou:人 dn:uid = admin,ou = people,dc = springframework,dc = org 对象类:顶部 对象类:人 对象类:organizationalPerson 对象类:inetOrgPerson cn:罗德·约翰逊 sn:约翰逊 uid:管理员 userPassword:密码 dn:uid = user,ou = people,dc = springframework,dc = org 对象类:顶部 对象类:人 对象类:organizationalPerson 对象类:inetOrgPerson cn:Dianne Emu sn:mu uid:用户 userPassword:密码 dn:cn = user,ou = groups,dc = springframework,dc = org 对象类:顶部 对象类:groupOfNames cn:用户 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 对象类:顶部 对象类:groupOfNames cn:管理员 uniqueMember:uid = admin,ou = people,dc = springframework,dc = org
9.8. 身份验证提供者
9.8.1. AuthenticationProvider Java配置
您可以通过将定制暴露
AuthenticationProvider
为Bean
来定义定制身份验证
。
例如,以下代码将在
SpringAuthenticationProvider
实现的情况
下自定义身份验证
AuthenticationProvider
:
仅在
AuthenticationManagerBuilder
尚未填充时使用
|
@Bean
public SpringAuthenticationProvider springAuthenticationProvider() {
return new SpringAuthenticationProvider();
}
9.8.2. AuthenticationProvider XML配置
实际上,与添加到应用程序上下文文件中的几个名称相比,您将需要更具扩展性的用户信息源。
您很可能希望将用户信息存储在数据库或LDAP服务器之类的文件中。
LDAP名称空间配置将在
LDAP一章中介绍
,因此在此不再赘述。
如果您
UserDetailsService
在应用程序上下文中
具有Spring Security的自定义实现(
称为“ 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
用户数据表
的数据库
。
另外,您可以配置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
按以下方式
使用标准
Bean
<authentication-manager>
<authentication-provider ref='myAuthenticationProvider'/>
</authentication-manager>
myAuthenticationProvider
在您的应用程序上下文中实现的Bean的名称
在哪里
AuthenticationProvider
。
您可以使用多个
authentication-provider
元素,在这种情况下,将按照声明它们的顺序查询提供者。
有关
如何
使用命名空间配置
Spring Security的更多信息,
请参见
认证管理器和命名
AuthenticationManager
空间。
9.9。 UserDetailsService
您可以通过将定制暴露
UserDetailsService
为Bean
来定义定制身份验证
。
例如,以下代码将在
SpringDataUserDetailsService
实现的情况
下自定义身份验证
UserDetailsService
:
仅当
AuthenticationManagerBuilder
尚未填充且未
AuthenticationProviderBean
定义
no时使用
。
|
@Bean
public SpringDataUserDetailsService springDataUserDetailsService() {
return new SpringDataUserDetailsService();
}
您还可以通过将a
PasswordEncoder
作为bean
公开来自定义密码的编码方式
。
例如,如果使用bcrypt,则可以添加如下所示的bean定义:
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
9.10。 密码编码
Spring Security的
PasswordEncoder
界面用于执行密码的单向转换,以允许安全地存储密码。
给出的
PasswordEncoder
是一种单向转换,而密码转换需要采用两种方式(即存储用于对数据库进行身份验证的凭据)时,则无意使用。
通常
PasswordEncoder
用于在认证时存储需要与用户提供的密码进行比较的密码。
9.10.1. 密码记录
多年来,用于存储密码的标准机制已经发展。 最初,密码以纯文本格式存储。 假定密码是安全的,因为数据存储密码已保存在访问它所需的凭据中。 但是,恶意用户能够使用SQL Injection这样的攻击找到方法来获取用户名和密码的大型“数据转储”。 随着越来越多的用户凭证成为公共安全专家,我们意识到我们需要做更多的工作来保护用户密码。
然后鼓励开发人员在通过诸如SHA-256之类的单向哈希运行密码后存储密码。 当用户尝试进行身份验证时,会将散列的密码与他们键入的密码的散列进行比较。 这意味着系统仅需要存储密码的单向哈希。 如果发生违规,则仅暴露密码的一种哈希方式。 由于散列是一种方式,计算出给定哈希值的密码很难计算,因此找出系统中的每个密码都不值得。 为了击败这个新系统,恶意用户决定创建称为 Rainbow Tables的 查找 表 。 他们不必每次都猜测每个密码,而是计算一次密码并将其存储在查找表中。
为了减轻Rainbow Tables的有效性,鼓励开发人员使用加盐的密码。 不仅将密码用作哈希函数的输入,还将为每个用户的密码生成随机字节(称为salt)。 盐和用户的密码将通过散列函数运行,从而产生唯一的散列。 盐将以明文形式与用户密码一起存储。 然后,当用户尝试进行身份验证时,会将哈希密码与存储的盐的哈希值和他们键入的密码进行比较。 唯一的盐意味着Rainbow Tables不再有效,因为每种盐和密码组合的哈希值都不同。
在现代,我们意识到加密哈希(例如SHA-256)不再安全。 原因是使用现代硬件,我们可以每秒执行数十亿次哈希计算。 这意味着我们可以轻松地分别破解每个密码。
现在鼓励开发人员利用自适应单向功能来存储密码。 带有自适应单向功能的密码验证有意占用大量资源(即CPU,内存等)。 自适应单向功能允许配置“工作因数”,该因数会随着硬件的改进而增加。 建议将“工作因数”调整为大约1秒钟,以验证系统上的密码。 这种权衡使攻击者难以破解密码,但代价却不高,这给您自己的系统带来了沉重负担。 Spring Security试图为“工作因素”提供一个良好的起点,但是鼓励用户为自己的系统自定义“工作因素”,因为不同系统之间的性能会有很大差异。 bcrypt , PBKDF2 , scrypt 和 Argon2 。
由于自适应单向功能有意占用大量资源,因此为每个请求验证用户名和密码都会大大降低应用程序的性能。 Spring Security(或任何其他库)无法采取任何措施来加快密码的验证速度,因为通过增加验证资源的强度来获得安全性。 鼓励用户将长期凭证(即用户名和密码)交换为短期凭证(即会话,OAuth令牌等)。 可以快速验证短期凭证,而不会损失任何安全性。
9.10.2. DelegatingPasswordEncoder
在此之前的Spring Security 5.0的默认
PasswordEncoder
是
NoOpPasswordEncoder
其所需的明文密码。
根据“
密码历史记录”
部分,您可能希望现在的默认值
PasswordEncoder
是
BCryptPasswordEncoder
。
但是,这忽略了三个现实问题:
-
有许多使用旧密码编码的应用程序无法轻松迁移
-
密码存储的最佳做法将再次更改。
-
作为一个框架,Spring Security不能经常进行重大更改
相反,Spring Security引入了
DelegatingPasswordEncoder
通过以下方法解决所有问题的方法:
-
确保使用当前密码存储建议对密码进行编码
-
允许以现代和旧式格式验证密码
-
允许将来升级编码
您可以轻松地构建的一个实例
DelegatingPasswordEncoder
使用
PasswordEncoderFactories
。
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
或者,您可以创建自己的自定义实例。 例如:
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
密码存储格式
密码的一般格式为:
{id}encodedPassword
这样
id
的标识符是用于查找
PasswordEncoder
应使用
的标识符,
并且
encodedPassword
是所选的原始编码密码
PasswordEncoder
。
在
id
必须在密码的开始,开始
{
和结束
}
。
如果
id
找不到,
id
则将为null。
例如,以下可能是使用different编码的密码列表
id
。
所有原始密码均为“密码”。
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG (1)
{noop}password (2)
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc (3)
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= (4)
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 (5)
1个 |
第一个密码的
PasswordEncoder
ID为
bcrypt
,编码
密码
为
$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
。
匹配时将委托给
BCryptPasswordEncoder |
2 |
第二个密码的
PasswordEncoder
ID为
noop
,编码
密码
为
password
。
匹配时将委托给
NoOpPasswordEncoder |
3 |
第三个密码的
PasswordEncoder
ID为
pbkdf2
,编码
密码
为
5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
。
匹配时将委托给
Pbkdf2PasswordEncoder |
4 |
第四个密码的
PasswordEncoder
ID为
scrypt
,编码的
密码
为
,
$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
匹配时将委派给
SCryptPasswordEncoder |
5 |
最终密码的
PasswordEncoder
ID为
sha256
,编码
密码
为
97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
。
匹配时将委托给
StandardPasswordEncoder |
一些用户可能会担心为潜在的黑客提供了存储格式。
不必担心,因为密码的存储不依赖于算法是秘密。
此外,大多数格式很容易让攻击者弄清楚没有前缀的情况。
例如,BCrypt密码通常以开头
|
密码编码
的
idForEncode
传递到构造确定哪个
PasswordEncoder
将用于编码的密码。
在
DelegatingPasswordEncoder
上面
的
构造中,这意味着编码结果
password
将委托给
BCryptPasswordEncoder
并以开头
{bcrypt}
。
最终结果如下所示:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
密码匹配
匹配是基于
{id}
和
id
到
PasswordEncoder
构造函数中提供的
的映射
来完成的
。
我们的“
密码存储格式”
示例提供了一个有效的示例。
默认情况下,
matches(CharSequence, String)
使用密码和
id
未映射(包括空ID)
的调用的结果
将导致
IllegalArgumentException
。
可以使用来自定义此行为
DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)
。
通过使用,
id
我们可以匹配任何密码编码,但使用最现代的密码编码对密码进行编码。
这很重要,因为与加密不同,密码哈希被设计为没有简单的方法来恢复明文。
由于无法恢复明文,因此很难迁移密码。
尽管用户迁移很简单
NoOpPasswordEncoder
,但我们默认选择包含它,以使入门体验更简单。
入门经验
如果您要编写演示或示例,则花一些时间来哈希用户密码会很麻烦。 有一些便利机制可以简化此过程,但这仍然不适合生产。
User user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("user")
.build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
如果要创建多个用户,则还可以重复使用该构建器。
UserBuilder users = User.withDefaultPasswordEncoder();
User user = users
.username("user")
.password("password")
.roles("USER")
.build();
User admin = users
.username("admin")
.password("password")
.roles("USER","ADMIN")
.build();
这会散列存储的密码,但是密码仍在内存和已编译的源代码中公开。 因此,对于生产环境它仍然不被认为是安全的。 对于生产,您应该在外部对密码进行哈希处理。
故障排除
当所存储的密码之一没有如“ 密码存储格式”中 所述的id时,会发生以下错误 。
java.lang.IllegalArgumentException:没有为id“ null”映射的PasswordEncoder 在org.springframework.security.crypto.password.DelegatingPasswordEncoder $ UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)处 在org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)
解决该错误的最简单方法是切换为显式提供
PasswordEncoder
密码进行编码的方式。
解决此问题的最简单方法是弄清楚密码的当前存储方式,并明确提供正确的密码
PasswordEncoder
。
如果您是从Spring Security 4.2.x迁移的,则可以通过公开一个
NoOpPasswordEncoder
bean
恢复到以前的行为
。
例如,如果您使用的是Java配置,则可以创建如下所示的配置:
还原为
|
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
如果您使用的是XML配置,则可以公开
PasswordEncoder
带有id的
passwordEncoder
:
<b:bean id="passwordEncoder"
class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
或者,您可以为所有密码加上正确的ID前缀,然后继续使用
DelegatingPasswordEncoder
。
例如,如果您使用的是BCrypt,则可以从以下方式迁移密码:
$ 2a $ 10 $ dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM / BG
至
{bcrypt} $ 2a $ 10 $ dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM / BG
有关映射的完整列表,请参阅 PasswordEncoderFactories 上的Javadoc 。
9.10.3. BCryptPasswordEncoder
该
BCryptPasswordEncoder
实现使用广泛支持的
bcrypt
算法对密码进行哈希处理。
为了使其更能抵抗密码破解,bcrypt故意降低了速度。
与其他自适应单向功能一样,应将其调整为大约1秒钟,以验证系统上的密码。
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
9.10.4. Argon2PasswordEncoder
该
Argon2PasswordEncoder
实现使用
Argon2
算法对密码进行哈希处理。
Argon2是“
密码哈希竞赛”
的获胜者
。
为了克服自定义硬件上的密码破解问题,Argon2是一种故意慢速的算法,需要大量内存。
与其他自适应单向功能一样,应将其调整为大约1秒钟,以验证系统上的密码。
如果
Argon2PasswordEncoder
需要BouncyCastle,则为
当前实现
。
// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
9.10.5. Pbkdf2PasswordEncoder
该
Pbkdf2PasswordEncoder
实现使用
PBKDF2
算法对密码进行哈希处理。
为了消除密码破解,PBKDF2是一种故意缓慢的算法。
与其他自适应单向功能一样,应将其调整为大约1秒钟,以验证系统上的密码。
当需要FIPS认证时,此算法是不错的选择。
// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
9.10.6. SCryptPasswordEncoder
该
SCryptPasswordEncoder
实现使用
scrypt
算法对密码进行哈希处理。
为了克服自定义硬件scrypt上的密码破解问题,它是一种故意缓慢的算法,需要大量内存。
与其他自适应单向功能一样,应将其调整为大约1秒钟,以验证系统上的密码。
// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
9.10.7. 其他密码编码器
存在大量其他
PasswordEncoder
实现完全是为了向后兼容的实现。
它们均已弃用,以表明它们不再被视为安全。
但是,由于很难迁移现有的旧系统,因此没有删除它们的计划。
9.10.8. 密码编码器XML配置
密码应始终使用为此目的而设计的安全哈希算法(而不是诸如SHA或MD5的标准算法)进行编码。
这由
<password-encoder>
元素
支持
。
使用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="$2a$10$ddEWZUl8aU0GdZPPpy7wbu82dvEw/pBpbRvDQRqA41y6mK1CoH00m"
authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="$2a$10$/elFpMBnAYYig6KRR5bvOOYeZr1ie1hSogJryg9qDlhza4oCw1Qka"
authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
在大多数情况下,bcrypt是一个不错的选择,除非您有一个旧系统迫使您使用其他算法。 如果您使用的是简单的哈希算法,或者更糟的是存储纯文本密码,则应考虑迁移到更安全的选项,例如bcrypt。
9.11. 身份验证管理器和命名空间
Spring Security提供身份验证服务的主要接口是
AuthenticationManager
。
这通常是Spring Security的
ProviderManager
类
的实例,
如果您以前使用过该框架,则可能已经熟悉。
否则,将在后面的“
技术概述”一章中进行介绍
。
使用
authentication-manager
命名空间元素
注册Bean实例
。
AuthenticationManager
如果通过名称空间使用HTTP或方法安全性,
则不能使用自定义
,但这不会成为问题,因为您可以完全控制
AuthenticationProvider
所使用的。
您可能要向注册其他
AuthenticationProvider
bean,
ProviderManager
然后可以使用
<authentication-provider>
带有
ref
属性
的
元素
来执行此操作
,其中属性的值是要添加的provider bean的名称。
例如:
<authentication-manager>
<authentication-provider ref="casAuthenticationProvider"/>
</authentication-manager>
<bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
</bean>
另一个常见的要求是,上下文中的另一个bean可能需要引用
AuthenticationManager
。
您可以轻松注册的别名,
AuthenticationManager
并在应用程序上下文中的其他位置使用此名称。
<security:authentication-manager alias="authenticationManager">
...
</security:authentication-manager>
<bean id="customizedFormLoginFilter"
class="com.somecompany.security.web.CustomFormLoginFilter">
<property name="authenticationManager" ref="authenticationManager"/>
...
</bean>
9.12. 会话管理
与HTTP会话相关的功能由
SessionManagementFilter
和
SessionAuthenticationStrategy
过滤器委托给
的
接口
的组合来处理
。
典型的用法包括防止会话固定保护攻击,检测会话超时以及限制已认证用户可以同时打开多少个会话。
9.12.1. 检测超时
您可以配置Spring Security来检测提交的无效会话ID,并将用户重定向到适当的URL。
这是通过以下
session-management
元素
实现的
:
<http>
...
<session-management invalid-session-url="/invalidSession.htm" />
</http>
请注意,如果使用此机制来检测会话超时,则在用户注销然后重新登录而不关闭浏览器的情况下,它可能会错误地报告错误。 这是因为在使会话无效时不会清除会话cookie,即使用户已注销,会话cookie也会重新提交。 您可能能够在注销时显式删除JSESSIONID cookie,例如通过在注销处理程序中使用以下语法:
<http>
<logout delete-cookies="JSESSIONID" />
</http>
不幸的是,不能保证它可以与每个servlet容器一起使用,因此您需要在您的环境中对其进行测试
===如果您正在代理后运行应用程序,则还可以通过配置代理服务器来删除会话cookie。
例如,使用Apache HTTPD的mod_headers,以下指令将
JSESSIONID
通过在注销请求的响应
中使
cookie过期
来删除该
cookie(假设该应用程序已部署在path下
/tutorial
):
|
<LocationMatch "/tutorial/logout">
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
</LocationMatch>
===
9.12.2. 并发会话控制
如果您希望限制单个用户登录应用程序的能力,Spring Security会通过以下简单的补充来支持此功能。
首先,您需要向文件中添加以下侦听器,
web.xml
以使Spring Security保持有关会话生命周期事件的最新信息:
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
然后将以下行添加到您的应用程序上下文:
<http>
...
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
</http>
这将防止用户多次登录-第二次登录将使第一次登录无效。 通常,您希望避免再次登录,在这种情况下,您可以使用
<http>
...
<session-management>
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>
然后,第二次登录将被拒绝。
“拒绝”是指如果用户
authentication-failure-url
正在使用基于表单的登录名,则
该用户将被发送到该页面
。
如果第二次身份验证是通过另一个非交互机制(例如“ remember-me”)进行的,则“未授权”(401)错误将发送给客户端。
相反,如果要使用错误页面,则可以将属性添加
session-authentication-error-url
到
session-management
元素。
如果使用定制的身份验证筛选器进行基于表单的登录,则必须显式配置并发会话控制支持。 更多细节可以在 会话管理一章中找到 。
9.12.3. 会话固定攻击防护
会话固定
攻击是一种潜在的风险,恶意攻击者有可能通过访问站点来创建会话,然后诱使另一个用户以相同的会话登录(通过向他们发送包含会话标识符作为参数的链接,以便例)。
Spring Security通过创建新会话或在用户登录时更改会话ID来自动防御这种情况。如果您不需要此保护,或者与其他要求冲突,则可以使用上的
session-fixation-protection
属性
控制行为
<session-management>
,有四个选项
-
none
-什么也不要做。 原始会话将保留。 -
newSession
-创建一个新的“干净”会话,而不复制现有会话数据(仍将复制与Spring Security相关的属性)。 -
migrateSession
-创建一个新会话,并将所有现有会话属性复制到新会话。 这是Servlet 3.0或更早版本的容器中的默认值。 -
changeSessionId
-不要创建新的会话。 相反,请使用Servlet容器(HttpServletRequest#changeSessionId()
) 提供的会话固定保护 。 此选项仅在Servlet 3.1(Java EE 7)和更高版本的容器中可用。 在较旧的容器中指定它会导致异常。 这是Servlet 3.1和更高版本容器中的默认设置。
发生会话固定保护时,将导致在
SessionFixationProtectionEvent
应用程序上下文中
发布会话固定保护
。
如果使用
changeSessionId
,这种保护会
也
导致任何
javax.servlet.http.HttpSessionIdListener
被通知S,所以如果你的代码侦听这两个事件谨慎使用。
有关
其他信息,
请参见“
会话管理”
一章。
9.12.4. SessionManagementFilter
该
SessionManagementFilter
检查的内容
SecurityContextRepository
针对的当前内容
SecurityContextHolder
,以确定是否用户已经在当前请求期间被认证,通常由非交互式认证机制,如预认证或记住-ME
[ 4 ]
。
如果存储库包含安全上下文,则过滤器不执行任何操作。
如果不是,并且线程局部
SecurityContext
包含一个(非匿名)
Authentication
对象,则过滤器将假定它们已由堆栈中的先前过滤器验证。
然后它将调用configure
SessionAuthenticationStrategy
。
9.12.5. 会话认证策略
SessionAuthenticationStrategy
被两个
SessionManagementFilter
和
AbstractAuthenticationProcessingFilter
,因此,如果您使用的是自定义表单登录类,例如,您需要将其注入到这两个。
在这种情况下,将名称空间和自定义bean结合起来的典型配置如下所示:
<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
...
</beans:bean>
<beans:bean id="sas" class=
"org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
请注意,
SessionFixationProtectionStrategy
如果您在实现的会话中存储Bean
HttpSessionBindingListener
(包括Spring会话范围的Bean)
,则使用默认值
可能会导致问题
。
有关更多信息,请参见Javadoc。
9.12.6. 并发控制
Spring Security可以防止主体同时向同一应用程序进行身份验证超过指定次数。 许多ISV都利用此功能来实施许可,而Web管理员喜欢此功能,因为它有助于防止人们共享登录名。 例如,您可以阻止用户“蝙蝠侠”从两个不同的会话登录到Web应用程序。 您可以使他们的先前登录到期,也可以在他们再次尝试登录时报告错误,从而阻止第二次登录。 请注意,如果您使用第二种方法,则未明确注销的用户(例如,刚刚关闭浏览器的用户)将无法再次登录,直到他们的原始会话期满为止。
名称空间支持并发控制,因此,请查阅前面的名称空间一章以获取最简单的配置。 有时您需要自定义内容。
该实现使用的专门版本
SessionAuthenticationStrategy
,称为
ConcurrentSessionControlAuthenticationStrategy
。
以前,并发身份验证检查是由进行的
|
要使用并发会话支持,您需要将以下内容添加到
web.xml
:
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
此外,您需要将添加
ConcurrentSessionFilter
到中
FilterChainProxy
。
该
ConcurrentSessionFilter
需要两个构造函数的参数,
sessionRegistry
,其通常指向的一个实例
SessionRegistryImpl
,并且
sessionInformationExpiredStrategy
,它定义当会话过期应用策略。
使用名称空间创建
FilterChainProxy
和其他默认Bean
的配置
如下所示:
<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>
<beans:bean id="redirectSessionInformationExpiredStrategy"
class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/session-expired.htm" />
</beans:bean>
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="sessionInformationExpiredStrategy" ref="redirectSessionInformationExpiredStrategy" />
</beans:bean>
<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="sessionRegistry"
class="org.springframework.security.core.session.SessionRegistryImpl" />
将侦听器添加到
web.xml
会导致
每次a
开始或终止
时将
an
ApplicationEvent
发布到Spring
。
这很关键,因为它允许在
会话结束时通知。
如果没有它,即使用户退出另一个会话或超时,一旦超出会话允许量,用户将永远无法再次登录。
ApplicationContext
HttpSession
SessionRegistryImpl
查询SessionRegistry中当前经过身份验证的用户及其会话
通过名称空间或使用普通bean设置并发控制具有有益的副作用,即为您提供对
SessionRegistry
可以直接在应用程序中使用的
的引用
,因此即使您不想限制会话数用户可能已经拥有了,但仍然值得建立基础架构。
您可以将该
maximumSession
属性
设置
为-1,以允许无限制的会话。
如果使用的是名称空间,则可以
SessionRegistry
使用
session-registry-alias
属性
设置内部创建的别名
,并提供一个引用,您可以将其注入到自己的bean中。
该
getAllPrincipals()
方法为您提供了当前已认证用户的列表。
您可以通过调用
getAllSessions(Object principal, boolean includeExpiredSessions)
方法
列出用户的会话,该
方法将返回
SessionInformation
对象
列表
。
您还可以通过调用终止用户的会话
expireNow()
上一个
SessionInformation
实例。
当用户返回到应用程序时,将阻止他们继续操作。
例如,您可能会发现这些方法在管理应用程序中很有用。
看看Javadoc了解更多信息。
9.13. 记住我身份验证
9.13.1. 总览
“记住我”或“持久登录”身份验证是指网站能够记住会话之间的主体身份。 通常,这是通过向浏览器发送一个cookie来实现的,该cookie在以后的会话中被检测到并导致自动登录。 Spring Security提供了进行这些操作所需的钩子,并具有两个具体的“记住我”实现。 一种使用散列来保留基于cookie的令牌的安全性,另一种使用数据库或其他持久性存储机制来存储生成的令牌。
请注意,两个实现都需要一个
UserDetailsService
。
如果您正在使用不使用的身份验证提供程序
UserDetailsService
(例如LDAP提供程序),那么除非您
UserDetailsService
在应用程序上下文中
也有
bean,
否则它将无法工作
。
9.13.2. 简单的基于哈希的令牌方法
这种方法使用哈希来实现有用的“记住我”策略。 本质上,在成功进行交互式身份验证后,会将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>
元素
即可启用“记住我”身份验证
:
<http>
...
<remember-me key="myAppKey"/>
</http>
在
UserDetailsService
通常将被自动地选择。
如果您的应用程序上下文中有多个,则需要指定应与哪个
user-service-ref
属性
一起使用
,其中的值是
UserDetailsService
bean
的名称
。
9.13.3. 持久令牌方法
这种方法是基于 http://jaspan.com/improved_persistent_login_cookie_best_practice 文章 进行的一些细微修改 [ 5 ] 。 要将这种方法与名称空间配置一起使用,您将提供一个数据源 Reference:
<http>
...
<remember-me data-source-ref="someDataSource"/>
</http>
数据库应包含一个
persistent_logins
使用以下SQL(或等效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)
9.13.4. 记住我的接口和实现
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);
请参阅Javadoc,以更全面地讨论方法的作用,尽管在此阶段请注意,
AbstractAuthenticationProcessingFilter
仅调用
loginFail()
和
loginSuccess()
方法。
每当
autoLogin()
方法
不包含
RememberMeAuthenticationFilter
时
,
都会通过
调用
该
方法
。
因此,此接口为底层的“记住我”实现提供了与身份验证相关的事件的充分通知,并在候选Web请求可能包含cookie并希望被记住时委托给该实现。
这种设计允许使用任何数量的“记住我”实施策略。
上面我们已经看到,Spring Security提供了两种实现。
我们将依次研究这些。
SecurityContextHolder
Authentication
TokenBasedRememberMeServices
此实现支持“
基于简单哈希的令牌方法”中
描述的更
简单方法
。
TokenBasedRememberMeServices
生成一个
RememberMeAuthenticationToken
,由处理
RememberMeAuthenticationProvider
。
A
key
在此身份验证提供程序和之间共享
TokenBasedRememberMeServices
。
另外,还
TokenBasedRememberMeServices
需要一个UserDetailsService,从中可以检索用户名和密码以进行签名比较,并生成
RememberMeAuthenticationToken
包含正确
GrantedAuthority
的。
应用程序应提供某种注销命令,如果用户请求,该命令会使cookie无效。
TokenBasedRememberMeServices
还实现了Spring Security的
LogoutHandler
界面,因此可用于
LogoutFilter
自动清除cookie。
在应用程序上下文中启用“记住我”服务所需的bean如下:
<bean id="rememberMeFilter" class=
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="theAuthenticationManager" />
</bean>
<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
</bean>
<bean id="rememberMeAuthenticationProvider" class=
"org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="springRocks"/>
</bean>
不要忘记将
RememberMeServices
实现
添加
到
UsernamePasswordAuthenticationFilter.setRememberMeServices()
属性中,
RememberMeAuthenticationProvider
在
AuthenticationManager.setProviders()
列表中添加,然后添加
RememberMeAuthenticationFilter
到您的
属性中
FilterChainProxy
(通常紧随在之后
UsernamePasswordAuthenticationFilter
)。
PersistentTokenBasedRememberMeServices
此类的使用方式与相同
TokenBasedRememberMeServices
,但还需要配置一个
PersistentTokenRepository
来存储令牌。
有两种标准实现。
-
InMemoryTokenRepositoryImpl
仅用于测试。 -
JdbcTokenRepositoryImpl
将令牌存储在数据库中。
上面的“ 持久令牌方法”中 描述了数据库模式 。
9.14. OpenID支持
<http>
<intercept-url pattern="/**" access="ROLE_USER" />
<openid-login />
</http>
然后,您应该向OpenID提供者注册自己(例如myopenid.com),并将用户信息添加到内存中
<user-service>
:
<user name="https://jimi.hendrix.myopenid.com/" authorities="ROLE_USER" />
您应该能够使用该
myopenid.com
网站
登录进行
身份验证。
UserDetailsService
通过
user-service-ref
在
openid-login
元素
上
设置
属性,
还可以选择一个特定的
bean来使用OpenID
。
有关
更多信息,
请参见上一节中的
身份验证提供程序
。
请注意,我们已从上述用户配置中省略了password属性,因为这组用户数据仅用于加载用户的权限。
系统会在内部生成一个随机密码,以防止您意外地将此用户数据用作配置中其他位置的身份验证源。
9.14.1. 属性交换
支持OpenID 属性交换 。 例如,以下配置将尝试从OpenID提供程序中检索电子邮件和全名,以供应用程序使用:
<openid-login>
<attribute-exchange>
<openid-attribute name="email" type="https://axschema.org/contact/email" required="true"/>
<openid-attribute name="name" type="https://axschema.org/namePerson"/>
</attribute-exchange>
</openid-login>
每个OpenID属性的“类型”是一个URI,由特定模式确定,在这种情况下为
https://axschema.org/
。
如果必须检索属性以成功进行身份验证,则
required
可以设置
该
属性。
支持的确切架构和属性将取决于您的OpenID提供程序。
属性值作为身份验证过程的一部分返回,之后可以使用以下代码进行访问:
OpenIDAuthenticationToken token =
(OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
List<OpenIDAttribute> attributes = token.getAttributes();
该
OpenIDAttribute
包含的属性类型和所检索的值(或多个值在多值的属性的情况下)。
SecurityContextHolder
当我们在
技术概述
一章中
查看核心Spring Security组件时,
将了解有关如何使用
该类的
更多信息
。
如果您希望使用多个身份提供者,则还支持多个属性交换配置。
您可以
attribute-exchange
使用
identifier-matcher
每个属性
提供多个
元素
。
它包含一个正则表达式,它将与用户提供的OpenID标识符匹配。
有关示例配置,请参见代码库中的OpenID示例应用程序,为Google,Yahoo和MyOpenID提供程序提供了不同的属性列表。
9.15. 匿名认证
9.15.1. 总览
通常,采取“默认拒绝”的做法被认为是一种良好的安全做法,在该行为中,您明确指定允许的内容,并禁止其他所有内容。
定义未经身份验证的用户可以访问的内容的情况与此类似,尤其是对于Web应用程序。
许多站点要求用户必须通过身份验证才能使用少数几个URL(例如,主页和登录页面)。
在这种情况下,最简单的是为这些特定的URL定义访问配置属性,而不是为每个受保护的资源定义访问配置属性。
换句话说,有时候很高兴
ROLE_SOMETHING
默认情况下是必需的,并且仅允许此规则的某些例外,例如应用程序的登录,注销和主页。
您也可以从过滤器链中完全忽略这些页面,从而绕过访问控制检查,但是由于其他原因,这可能是不可取的,特别是如果这些页面对于经过身份验证的用户而言行为不同。
这就是我们所说的匿名身份验证。
请注意,“匿名身份验证”的用户和未经身份验证的用户之间没有真正的概念差异。
Spring Security的匿名身份验证只是为您提供了一种更方便的方式来配置访问控制属性。
getCallerPrincipal
即使确实存在匿名身份验证对象,
对Servlet API调用的调用(
例如)仍将返回null
SecurityContextHolder
。
在其他情况下,匿名身份验证很有用,例如,当审核拦截器查询时,
SecurityContextHolder
以标识哪个主体负责给定操作。
如果类知道
SecurityContextHolder
始终包含一个
Authentication
对象,并且永不
包含一个
对象,
则可以更强大地编写类
null
。
9.15.2. 组态
使用HTTP配置Spring Security 3.0时会自动提供匿名身份验证支持,并且可以使用
<anonymous>
元素
进行自定义(或禁用)匿名身份验证
。
除非您使用传统的Bean配置,否则无需配置此处描述的Bean。
三个类共同提供了匿名身份验证功能。
AnonymousAuthenticationToken
是的实现
Authentication
,并存储
GrantedAuthority
适用于匿名主体的。
有一个对应的
AnonymousAuthenticationProvider
,它被链接到,
ProviderManager
因此
AnonymousAuthenticationToken
可以接受。
最后,有一个
AnonymousAuthenticationFilter
,在正常的身份验证机制之后链接在一起
AnonymousAuthenticationToken
,
SecurityContextHolder
如果那里不存在
,
则会
自动将添加
到
Authentication
。
筛选器和身份验证提供程序的定义如下所示:
<bean id="anonymousAuthFilter"
class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="foobar"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>
<bean id="anonymousAuthenticationProvider"
class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>
</bean>
的
key
是在过滤器和认证提供器之间共享,使得由前创建的标记由后者所接受
[ 6 ]
。
该
userAttribute
在的形式来表达
usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]
。
这与的
userMap
属性
等号后使用的语法相同
InMemoryDaoImpl
。
如前所述,匿名身份验证的好处是所有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>
9.15.3. AuthenticationTrustResolver
完善匿名认证讨论的是
AuthenticationTrustResolver
接口及其相应的
AuthenticationTrustResolverImpl
实现。
该接口提供了一种
isAnonymous(Authentication)
方法,该方法允许感兴趣的类考虑这种特殊类型的身份验证状态。
在
ExceptionTranslationFilter
使用该接口在处理
AccessDeniedException
秒。
如果
AccessDeniedException
抛出,并且身份验证为匿名类型,则过滤器将启动,而不是引发403(禁止)响应,
AuthenticationEntryPoint
以便委托人可以正确地进行身份验证。
这是必要的区别,否则主体将始终被视为“已认证”,并且永远不会获得通过表单,基本,摘要或某些其他常规认证机制登录的机会。
您通常会
ROLE_ANONYMOUS
在上面的拦截器配置中
看到该
属性被替换为
IS_AUTHENTICATED_ANONYMOUSLY
,这在定义访问控制时实际上是相同的。
这是一个使用示例
AuthenticatedVoter
,我们将在
授权一章中
看到
。
它使用
AuthenticationTrustResolver
来处理此特定的配置属性,并将访问权限授予匿名用户。
该
AuthenticatedVoter
方法功能更强大,因为它使您可以区分匿名用户,记住我的用户和经过完全认证的用户。
如果您不需要此功能,则可以坚持使用
ROLE_ANONYMOUS
,它将由Spring Security的standard处理
RoleVoter
。
9.16. 验证前方案
在某些情况下,您想使用Spring Security进行授权,但是在访问该应用程序之前,某些外部系统已经对该用户进行了可靠的身份验证。 我们将这些情况称为“预身份验证”方案。 示例包括X.509,Siteminder和运行应用程序的Java EE容器进行的身份验证。 使用预身份验证时,Spring Security必须
-
标识发出请求的用户。
-
获取用户的权限。
详细信息将取决于外部身份验证机制。
如果是X.509,则可以通过其证书信息来标识用户;如果是Siteminder,则可以通过HTTP请求标头来标识用户。
如果依靠容器身份验证,将通过
getUserPrincipal()
在传入的HTTP请求上
调用
方法
来标识用户
。
在某些情况下,外部机制可能会为用户提供角色/权限信息,但在其他情况下,必须从单独的来源(例如)获得权限
UserDetailsService
。
9.16.1. 预身份验证框架类
因为大多数预认证机制遵循相同的模式,所以Spring Security具有一组类,这些类提供了用于实现预认证的认证提供程序的内部框架。
这消除了重复,并允许以结构化的方式添加新的实现,而不必从头开始编写所有内容。
如果您想使用
X.509身份验证之
类的内容,则无需了解这些类
,因为它已经具有一个名称空间配置选项,该选项更易于使用和入门。
如果您需要使用显式的bean配置或计划编写自己的实现,那么对提供的实现如何工作的理解将很有用。
您会在
org.springframework.security.web.authentication.preauth
。
我们仅在此处提供概述,因此您应该在适当的地方查阅Javadoc和源代码。
AbstractPreAuthenticatedProcessingFilter
此类将检查安全性上下文的当前内容,如果为空,它将尝试从HTTP请求中提取用户信息并将其提交给
AuthenticationManager
。
子类重写以下方法来获取此信息:
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
调用完这些之后,过滤器将创建一个
PreAuthenticatedAuthenticationToken
包含返回数据的,并将其提交进行身份验证。
这里的“身份验证”实际上只是意味着进一步处理以加载用户的权限,但是遵循标准的Spring Security身份验证体系结构。
像其他Spring Security身份验证过滤器一样,预身份验证过滤器具有一个
authenticationDetailsSource
属性,
该
属性默认情况下将创建一个
WebAuthenticationDetails
对象来存储其他信息,例如会话标识符和
对象
details
属性中的
始发IP地址
Authentication
。
如果可以从预身份验证机制获取用户角色信息,则数据也将存储在此属性中,其中详细信息实现了
GrantedAuthoritiesContainer
接口。
这使身份验证提供程序可以读取从外部分配给用户的权限。
接下来,我们将看一个具体示例。
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
如果为筛选器配置了
authenticationDetailsSource
此类的实例,则通过
isUserInRole(String role)
为每个“映射角色”的预定集合
调用
方法来
获得权限信息
。
该类从configure中获取这些
MappableAttributesRetriever
。
可能的实现包括在应用程序上下文中对列表进行硬编码,以及从
文件中
的
<security-role>
信息中
读取角色信息
web.xml
。
预认证示例应用程序使用后一种方法。
还有一个附加阶段,其中
GrantedAuthority
使用configureed
将角色(或属性)映射到Spring Security
对象
Attributes2GrantedAuthoritiesMapper
。
默认值只是将通常的
ROLE_
前缀
添加
到名称中,但是它使您可以完全控制行为。
PreAuthenticatedAuthenticationProvider
经过预身份验证的提供程序除了
UserDetails
为用户
加载
对象外,
仅需做其他事情
。
它通过委派给来实现此目的
AuthenticationUserDetailsService
。
后者与标准类似,
UserDetailsService
但采用一个
Authentication
对象而不只是用户名:
public interface AuthenticationUserDetailsService {
UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
}
该接口可能还有其他用途,但具有预身份验证功能,它允许访问包装在
Authentication
对象中
的授权机构
,如上一节所述。
该
PreAuthenticatedGrantedAuthoritiesUserDetailsService
班做到这一点。
或者,它可以
UserDetailsService
通过
UserDetailsByNameServiceWrapper
实现
委派给标准
。
Http403ForbiddenEntryPoint
AuthenticationEntryPoint
在
技术概述
一章中
对此
进行了讨论
。
通常,它负责启动未经身份验证的用户的身份验证过程(当他们尝试访问受保护的资源时),但是在经过预先身份验证的情况下,这并不适用。
ExceptionTranslationFilter
如果您没有将预身份验证与其他身份验证机制结合使用,则只能使用此类的实例
配置
。
如果用户被
AbstractPreAuthenticatedProcessingFilter
空身份验证
拒绝而将调用该方法
。
403
如果被调用,
它将始终返回
-forbidden响应代码。
9.16.2. 具体实施
X.509身份验证在其 单独的章节中介绍 。 在这里,我们将看一些为其他预先认证的场景提供支持的类。
请求标头身份验证(Siteminder)
外部认证系统可以通过在HTTP请求上设置特定的标头来向应用程序提供信息。
一个著名的例子是Siteminder,它在名为的标头中传递用户名
SM_USER
。
该类支持该机制,该类
RequestHeaderAuthenticationFilter
仅从标头中提取用户名。
默认情况下,使用名称
SM_USER
作为标题名称。
有关更多详细信息,请参见Javadoc。
请注意,当使用这样的系统时,框架完全不执行身份验证检查,并且 正确配置外部系统并保护对应用程序的所有访问 非常 重要。 如果攻击者能够在不检测到原始请求的情况下伪造标头,则他们可能会选择所需的任何用户名。 |
Siteminder示例配置
使用此过滤器的典型配置如下所示:
<security:http>
<!-- Additional http configuration omitted -->
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</security:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
我们在这里假设
安全名称空间
用于配置。
还假定您已
UserDetailsService
在配置中
添加了一个
(称为“ userDetailsService”)以加载用户的角色。
Java EE容器认证
该类
J2eePreAuthenticatedProcessingFilter
将从的
userPrincipal
属性中
提取用户名
HttpServletRequest
。
如上文
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource中
所述,通常将此过滤器的使用与Java EE角色的使用结合起来
。
代码库中有一个使用此方法的示例应用程序,因此如果您有兴趣,可以从github上获取代码,并查看应用程序上下文文件。
代码在
samples/xml/preauth
目录中。
9.17. Java身份验证和授权服务(JAAS)提供程序
9.17.2. AbstractJaasAuthenticationProvider
这
AbstractJaasAuthenticationProvider
是所提供的JAAS
AuthenticationProvider
实现
的基础
。
子类必须实现创建的方法
LoginContext
。
在
AbstractJaasAuthenticationProvider
具有许多下面讨论的是可注入它依赖。
JAAS CallbackHandler
大多数JAAS
LoginModule
都需要某种回调。
这些回调通常用于从用户获取用户名和密码。
在Spring Security部署中,Spring Security负责此用户交互(通过身份验证机制)。
因此,当认证请求被委托给JAAS时,Spring Security的认证机制将已经完全填充了一个
Authentication
对象,其中包含JAAS所需的所有信息
LoginModule
。
因此,Spring Security的JAAS包提供了两个默认的回调处理程序,
JaasNameCallbackHandler
和
JaasPasswordCallbackHandler
。
这些回调处理程序均实现
JaasAuthenticationCallbackHandler
。
在大多数情况下,无需了解内部机制即可简单地使用这些回调处理程序。
对于那些需要完全控制回调行为,内部
AbstractJaasAuthenticationProvider
封装这些
JaasAuthenticationCallbackHandler
s的一个
InternalCallbackHandler
。
该
InternalCallbackHandler
是真正实现JAAS正常类
CallbackHandler
接口。
每当使用JAAS时
LoginModule
,都会向其传递一个已配置
InternalCallbackHandler
的
应用程序上下文列表
。
如果
LoginModule
请求针对的回调
InternalCallbackHandler
,则该回调又传递给
JaasAuthenticationCallbackHandler
正在包装的。
JAAS授权人
JAAS与校长合作。
JAAS中甚至将“角色”表示为主体。
另一方面,Spring Security可以处理
Authentication
对象。
每个
Authentication
对象包含单个主体和多个
GrantedAuthority
。
为了促进这些不同概念之间的映射,Spring Security的JAAS软件包包括一个
AuthorityGranter
接口。
An
AuthorityGranter
负责检查JAAS委托人并返回一组
String
,代表分配给委托人的权限。
对于每个返回的授权字符串,
AbstractJaasAuthenticationProvider
会创建一个
JaasGrantedAuthority
(实现Spring Security的
GrantedAuthority
接口),其中包含授权字符串和
AuthorityGranter
传递
的JAAS主体
。
首先
AbstractJaasAuthenticationProvider
通过使用JAAS成功验证用户的凭据
LoginModule
,然后访问,
获得JAAS主体
LoginContext
。
进行调用
LoginContext.getSubject().getPrincipals()
,并将每个结果主体传递给
AuthorityGranter
针对该
AbstractJaasAuthenticationProvider.setAuthorityGranters(List)
属性
定义的
每个主体
。
AuthorityGranter
鉴于每个JAAS主体都具有特定于实现的含义,因此
Spring Security不包含任何production
。
但是,
TestAuthorityGranter
在单元测试中
有一个
演示了一个简单的
AuthorityGranter
实现。
9.17.3. DefaultJaasAuthenticationProvider
所述
DefaultJaasAuthenticationProvider
允许JAAS
Configuration
对象被注入到其作为依赖。
然后
LoginContext
使用注入的JAAS
创建一个
Configuration
。
这意味着,
DefaultJaasAuthenticationProvider
没有绑定任何特定的实现
Configuration
为
JaasAuthenticationProvider
是。
InMemory配置
为了可以很容易地注入
Configuration
到
DefaultJaasAuthenticationProvider
,在内存中执行名为默认
InMemoryConfiguration
设置。
实施构造函数接受一个
Map
,其中每个键代表一个登录配置名和值表示
Array
的
AppConfigurationEntry
第
InMemoryConfiguration
还支持默认
Array
的
AppConfigurationEntry
对象,如果在提供的中找不到映射,将使用这些对象
的默认值
Map
。
有关详细信息,请 Reference的类级别javadoc
InMemoryConfiguration
。
DefaultJaasAuthenticationProvider示例配置
尽管Spring的配置
InMemoryConfiguration
比标准JAAS配置文件更冗长,但结合使用它
DefaultJaasAuthenticationProvider
比
JaasAuthenticationProvider
不依赖默认
Configuration
实现
更为灵活
。
下面提供
了
DefaultJaasAuthenticationProvider
using
的示例配置
InMemoryConfiguration
。
请注意,的自定义实现也
Configuration
可以轻松注入
DefaultJaasAuthenticationProvider
。
<bean id="jaasAuthProvider"
class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
<property name="configuration">
<bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
<constructor-arg>
<map>
<!--
SPRINGSECURITY is the default loginContextName
for AbstractJaasAuthenticationProvider
-->
<entry key="SPRINGSECURITY">
<array>
<bean class="javax.security.auth.login.AppConfigurationEntry">
<constructor-arg value="sample.SampleLoginModule" />
<constructor-arg>
<util:constant static-field=
"javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED"/>
</constructor-arg>
<constructor-arg>
<map></map>
</constructor-arg>
</bean>
</array>
</entry>
</map>
</constructor-arg>
</bean>
</property>
<property name="authorityGranters">
<list>
<!-- You will need to write your own implementation of AuthorityGranter -->
<bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
</list>
</property>
</bean>
9.17.4. JaasAuthenticationProvider
该
JaasAuthenticationProvider
假设默认
Configuration
是实例
ConfigFile实现
。
进行此假设是为了尝试更新
Configuration
。
该
JaasAuthenticationProvider
则使用默认
Configuration
创建的
LoginContext
。
假设我们有一个JAAS登录配置文件,
/WEB-INF/login.conf
其内容如下:
JAASTest {
sample.SampleLoginModule required;
};
像所有Spring Security bean一样,
JaasAuthenticationProvider
可以通过应用程序上下文进行配置。
以下定义将对应于上述JAAS登录配置文件:
<bean id="jaasAuthenticationProvider"
class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">
<property name="loginConfig" value="/WEB-INF/login.conf"/>
<property name="loginContextName" value="JAASTest"/>
<property name="callbackHandlers">
<list>
<bean
class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/>
<bean
class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/>
</list>
</property>
<property name="authorityGranters">
<list>
<bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
</list>
</property>
</bean>
9.17.5. 作为主题跑步
如果已配置,
JaasApiIntegrationFilter
则会尝试在
Subject
上
以方式运行
JaasAuthenticationToken
。
这意味着
Subject
可以使用以下命令访问:
Subject subject = Subject.getSubject(AccessController.getContext());
可以使用 jaas-api-provision 属性 轻松配置此集成 。 与依赖于填充JAAS主题的旧版或外部API集成时,此功能很有用。
9.18. CAS认证
9.18.1. 总览
JA-SIG产生了企业范围内的单点登录系统,称为CAS。 与其他计划不同,JA-SIG的中央身份验证服务是开源的,被广泛使用,易于理解,独立于平台并支持代理功能。 Spring Security完全支持CAS,并提供了从Spring Security的单应用程序部署到由企业范围的CAS服务器保护的多应用程序部署的简便迁移路径。
您可以在 https://www.apereo.org上 了解有关CAS的更多信息 。 您还需要访问此站点以下载CAS Server文件。