Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架。 凭借对命令式和响应式应用程序的一流支持,它是用于保护基于Spring的应用程序的实际标准。

前言

本节讨论Spring Security的物流。

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的帮助,我们将在这里为您提供帮助。 以下是获得帮助的一些最佳方法:

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

3.2. Web助焊剂

3.3. 核心

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

另外,您可以手动添加启动器,如以下示例所示:

例子1. pom.xml
<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属性来实现,如以下示例所示:

例子2. pom.xml
<properties>
    <!-- ... -->
    <spring-security.version>5.2.0.RELEASE</spring-security.version>
</dependencies>

由于Spring Security仅在主要版本中进行重大更改,因此可以将较新版本的Spring Security与Spring Boot一起使用是安全的。 但是,有时您可能还需要更新Spring Framework的版本。 您可以通过添加Maven属性来执行此操作,如以下示例所示:

例子3. pom.xml
<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版本。 以下示例显示了如何执行此操作:

例子4. pom.xml
<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依赖关系集通常如下所示:

例子5. pom.xml
<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 如以下示例所示:

例子6. 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存储库,如以下示例所示:

例子7. pom.xml
<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存储库,如以下示例所示:

例子8. pom.xml
<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

另外,您可以手动添加启动器,如以下示例所示:

例子9. build.gradle
dependencies {
    compile "org.springframework.boot:spring-boot-starter-security"
}

由于Spring Boot提供了Maven BOM来管理依赖版本,因此您无需指定版本。 如果您希望覆盖Spring Security版本,可以通过提供Gradle属性来实现,如以下示例所示:

例子10. build.gradle
ext['spring-security.version']='5.2.0.RELEASE'

由于Spring Security仅在主要版本中进行重大更改,因此可以将较新版本的Spring Security与Spring Boot一起使用是安全的。 但是,有时您可能还需要更新Spring Framework的版本。 您可以通过添加Gradle属性来执行此操作,如以下示例所示:

例子11. build.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 来做到这一点 ,如以下示例所示:

例子12. build.gradle
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依赖关系集通常如下所示:

例子13. build.gradle
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 来做到这一点 ,如以下示例所示:

例子14. build.gradle
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版本的要求。 以下示例显示了如何执行此操作:

例子15. build.gradle
repositories {
    mavenCentral()
}

如果使用SNAPSHOT版本,则需要确保已定义Spring Snapshot存储库,如以下示例所示:

例子16. build.gradle
repositories {
    maven { url 'https://repo.spring.io/snapshot' }
}

如果使用里程碑版本或候选版本,则需要确保定义了Spring Milestone存储库,如以下示例所示:

例子17. build.gradle
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

5.12. OpenID —  spring-security-openid.jar

该模块包含OpenID Web身份验证支持。 它用于根据外部OpenID服务器对用户进行身份验证。 顶级软件包是 org.springframework.security.openid 它需要OpenID4Java。

5.13. 测试—  spring-security-test.jar

该模块包含对使用Spring Security进行测试的支持。

6.样品

Spring Security包含许多 示例 应用程序。

Servlet应用

Spring Security通过使用标准Servlet与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.1. 更新依赖项

您需要做的唯一步骤是使用 Maven Gradle 更新依赖关系 为了方便起见,您可以通过 单击此处 下载最小的Spring Boot + Spring Security应用程序

7.1.2. 启动Hello Spring安全启动

现在,您可以 使用Maven插件的 目标 运行Spring Boot应用程序 run 以下示例显示了如何执行此操作(以及执行此操作的输出的开头):

例子18.运行Spring Boot应用程序
$ ./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自动:

  • 启用S​​pring Security的默认配置,该默认配置将Servlet创建 Filter 为名为的Bean springSecurityFilterChain 此bean负责应用程序内的所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。

  • UserDetailsService 用用户名 创建一个 bean, user 并将其随机生成的密码记录到控制台。

  • 为每个请求向以Servlet容器 Filter 命名的bean 注册 springSecurityFilterChain

Spring Boot的配置不多,但功能很多。 功能摘要如下:

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.1. 更新依赖项

第一步是使用 Maven Gradle 更新依赖关系

7.2.2. 最小 @EnableWebSecurity 配置

第一步是创建我们的Spring Security Java配置。 该配置将创建一个servlet Filter (称为 springSecurityFilterChain ),该 servlet 负责应用程序中的所有安全功能(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。 以下示例显示了Spring Security Java配置的最基本示例:

例子19. WebSecurity.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
}

此配置的确没有太多,但是却做了很多。 功能摘要如下:

7.2.3. 使用 AbstractSecurityWebApplicationInitializer

下一步是向 springSecurityFilterChain 战争 登记 Spring Security提供了一个 AbstractSecurityWebApplicationInitializer 利用 Spring的WebApplicationInitializer支持 的基类(

以下示例显示了示例配置:

例子20. SecurityInitializer.java
import org.springframework.security.web.context.*;

public class SecurityInitializer
    extends AbstractSecurityWebApplicationInitializer {

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

SecurityInitializer 做以下的事情:

  • 添加 ContextLoaderListener 会加载 WebSecurityConfig

  • 查找 Filter 名为 类型的Bean springSecurityFilterChain 并将其注册以处理应用程序中的每个URL。

如果要与Spring MVC应用程序集成,请确保配置, DispatcherServlet 以从root加载配置 ApplicationContext 以下示例显示了如何执行此操作:

例子21. MvcInitializer.java
public class MvcInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    // the Root Config is registered in SecurityInitializer
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    // the Spring MVC configuration should be added to SecurityInitializer constructor
    // i.e.
    // super(MvcConfig.class, WebSecurityConfig.class);
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

7.3. Hello Spring安全性(XML)

本节介绍如何在XML配置中使用Spring Security。 有关如何将Spring Security与Java配置一起使用,请参阅 Hello Spring Security(Java配置) 有关如何在Spring Boot配置中使用Spring Security的信息,请参见 Hello Spring Security(引导)

7.3.1. 更新依赖项

第一步是使用 Maven Gradle 更新依赖关系

7.3.2. 最小 <http> 配置

在本节中,我们讨论如何将Spring Security与XML配置一起使用。

完整的应用程序可以在 samples / xml / helloworld中找到

第一步是创建我们的Spring Security XML配置。 该配置将创建一个Servlet Filter (称为 springSecurityFilterChain ),该 Servlet 负责应用程序中的所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。 以下示例显示了Spring Security XML配置的最基本示例:

例子22. src / main / webapp / WEB-INF / 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>

此配置的确没有太多,但是却做了很多。 功能摘要如下:

7.3.3. web.xml 组态

下一步是确保已读入我们的安全性配置。为此,我们需要确保a ContextLoaderListener 已注册并且 a contextConfigLocation 包括配置。 以下示例显示了如何执行此操作:

例子23. src / main / webapp / WEB-INF / web.xml
<?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应用程序集成,请确保配置, DispatcherServlet 以从root加载配置 ApplicationContext 以下示例显示了如何执行此操作:

src / main / webapp / WEB-INF / web.xml
<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- Load Spring MVC configuration from root ApplicationContext (context-param from above) -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value></param-value>
    </init-param>
</servlet>

<servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

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 作为主体。

UserDetailsS​​ervice

上述代码片段中需要注意的另一项内容是,您可以从 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 使用上面的代码片段 获得 的好处

经常会有一些困惑 UserDetailsService 它纯粹是用于用户数据的DAO,除了将数据提供给框架内的其他组件外,不执行其他功能。 特别是,它 不会 对用户进行身份验证,这由进行 AuthenticationManager 在许多情况下, 如果需要自定义身份验证过程,则直接 实施 AuthenticationProvider 更有意义

授予的权限

除了主体,提供的另一个重要方法 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中的身份验证是什么?

让我们考虑一个大家都熟悉的标准身份验证方案。

  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<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应用程序的身份验证过程:

  1. 您访问主页,然后单击链接。

  2. 请求发送到服务器,服务器确定您已请求受保护的资源。

  3. 由于您目前尚未通过身份验证,因此服务器会发回响应,指示您必须进行身份验证。 响应将是HTTP响应代码,或重定向到特定网页。

  4. 根据身份验证机制,您的浏览器将重定向到特定网页,以便您可以填写表格,或者浏览器将以某种方式检索您的身份(通过BASIC身份验证对话框,cookie,X.509证书等)。 )。

  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 负责第三步在上面的列表中。 可以想象,每个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 在每次请求后清除。

在单个会话中接收并发请求的应用程序中, SecurityContext 线程之间将共享 同一 实例。 即使 ThreadLocal 正在使用a,也 HttpSession 为每个线程 从中检索到相同的实例 如果您希望临时更改线程在其下运行的上下文,则可能会产生影响。 如果仅使用 SecurityContextHolder.getContext() ,并调用 setAuthentication(anAuthentication) 返回的上下文对象,则该 Authentication 对象将在 共享同一 实例的 所有 并发线程中 更改 SecurityContext 您可以自定义行为 SecurityContextPersistenceFilter 以创建全新 的行为 SecurityContext 对于每个请求,防止一个线程中的更改影响另一个线程。 或者,您可以在临时更改上下文的位置创建一个新实例。 该方法 SecurityContextHolder.createEmptyContext() 总是返回一个新的上下文实例。

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 提供用于处理安全对象请求的一致工作流,通常是:

  1. 查找与当前请求关联的“配置属性”

  2. 将安全对象,当前 Authentication 属性和配置属性提交 AccessDecisionManager 给授权决策

  3. (可选)更改 Authentication 调用 依据

  4. 允许进行安全对象调用(假设已授予访问权限)

  5. 调用 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 安全拦截器和“安全对象”模型 中显示了它及其相关对象

抽象安全拦截器
图1.安全拦截器和“安全对象”模型
扩展安全对象模型

只有打算采用全新的拦截和授权请求方式的开发人员才需要直接使用安全对象。 例如,有可能建立一个新的安全对象以保护对消息系统的呼叫。 任何需要安全性并且还提供拦截呼叫的方式的东西(例如,围绕建议语义的AOP)都可以成为安全对象。 话虽如此,大多数Spring应用程序将 完全透明 地使用当前支持的三种安全对象类型(AOP Alliance MethodInvocation ,AspectJ JoinPoint 和Web request FilterInvocation )。

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. UserDetailsS​​ervice实施

如本 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,将使用搜索。

BindAuthenticator

BindAuthenticator 包中 的类 org.springframework.security.ldap.authentication 实现绑定身份验证策略。 它只是尝试以用户身份进行绑定。

密码比较验证器

该类 PasswordComparisonAuthenticator 实现密码比较身份验证策略。

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 对象 中获得的权限

Active Directory错误代码

默认情况下,失败的结果将导致标准的Spring Security BadCredentialsException 如果将属性设置 convertSubErrorCodesToExceptions true ,则将解析异常消息,以尝试提取特定于Active Directory的错误代码并引发更特定的异常。 检查类Javadoc以获取更多信息。

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实例。

users.ldif
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的自定义实现( 称为“ myUserDetailsS​​ervice”),则可以使用对此进行身份验证

<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。 UserDetailsS​​ervice

您可以通过将定制暴露 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密码通常以开头 $2a$

密码编码

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配置,则可以创建如下所示的配置:

还原为 NoOpPasswordEncoder 不安全。 您应该改为使用 DelegatingPasswordEncoder 来支持安全密码编码。

@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

如果用户当前未通过身份验证,则过滤器将检查是否已请求了无效的会话ID(例如,由于超时),并且将调用configure InvalidSessionStrategy ,如果已设置。 最常见的行为就是重定向到固定URL,并将其封装在标准实现中 SimpleRedirectInvalidSessionStrategy 如前所述 通过名称空间配置无效的会话URL时,也会使用后者

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

以前,并发身份验证检查是由进行的 ProviderManager ,可以将注入 ConcurrentSessionController 后者将检查用户是否试图超过允许的会话数。 但是,这种方法要求预先创建HTTP会话,这是不希望的。 在Spring Security 3中,用户首先通过 AuthenticationManager 进行身份验证,一旦成功通过身份验证,就会创建一个会话,并检查是否允许他们打开另一个会话。

要使用并发会话支持,您需要将以下内容添加到 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 需要一个UserDetailsS​​ervice,从中可以检索用户名和密码以进行签名比较,并生成 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支持

命名空间支持 OpenID 登录,而不是普通的基于表单的登录,或者除了常规的基于表单的登录之外,还 支持 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 接口。 这使身份验证提供程序可以读取从外部分配给用户的权限。 接下来,我们将看一个具体示例。

J2eeBasedPreAuthenticatedWebAuthenticationDetailsS​​ource

如果为筛选器配置了 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 在配置中 添加了一个 (称为“ userDetailsS​​ervice”)以加载用户的角色。

Java EE容器认证

该类 J2eePreAuthenticatedProcessingFilter 将从的 userPrincipal 属性中 提取用户名 HttpServletRequest 如上文 J2eeBasedPreAuthenticatedWebAuthenticationDetailsS​​ource中 所述,通常将此过滤器的使用与Java EE角色的使用结合起来

代码库中有一个使用此方法的示例应用程序,因此如果您有兴趣,可以从github上获取代码,并查看应用程序上下文文件。 代码在 samples/xml/preauth 目录中。

9.17. Java身份验证和授权服务(JAAS)提供程序

9.17.1. 总览

Spring Security提供了一个程序包,可以将身份验证请求委派给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文件。

9.18.2. CAS如何工作