3. Spring Security 5.1 的新内容

Spring Security 5.1 提供了一些新特性。下面是发行版的亮点。

3.1 新特性

  • 小节 11, 测试方法安全

    • 支持在测试中设置 SecurityContext 进行定制。例如,@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) 将在 JUnit 的 @Before 和测试执行之前设置用户。
    • @WithUserDetails 现在可以使用 ReactiveUserDetailsService
  • 小节 10.4, “Jackson 支持” - 添加 BadCredentialsException 的支持
  • 小节 39.3, “@AuthenticationPrincipal”

    • 支持在 WebFlux 中解析 bean(已经在 Spring MVC 中支持)
    • 支持解决 WebFlux 中的 errorOnInvalidType(已经在 Spring MVC 中支持)

4. 示例和指南(从这里开始)

如果你想开始使用 Spring Security,最好的地方是我们的示例应用程序。

表格 4.1. 示例应用程序

资源 描述 指南

Hello Spring Security

演示如何使用基于 Java 的配置将 Spring Security 与现有的应用程序集成。

Hello Spring Security Guide

Hello Spring Security Boot

演示如何将 Spring Security 与现有的 Spring Boot 应用程序集成。

Hello Spring Security Boot Guide

Hello Spring Security XML

演示如何使用基于 XML 的配置将 Spring Security 与现有的应用程序集成。

Hello Spring Security XML Guide

Hello Spring MVC Security

演示如何将 Spring Security 与现有的 Spring MVC 应用程序集成。

Hello Spring MVC Security Guide

Custom Login Form

演示如何创建自定义登录表单。

Custom Login Form Guide

OAuth 2.0 Login

演示如何将 OAuth 2.0 登录与 OAuth 2.0 或 OpenID 连接 1.0 提供者集成。

OAuth 2.0 Login Guide


5. Java 配置

在 Spring 3.1 中,Spring Framework 添加了对 Java 配置的普遍支持。自从 Spring Security 3.2 以来,Spring Security Java 配置支持使得用户能够在不使用任何 XML 的情况下轻松配置 Spring Security。

如果你熟悉 小节 6, Security 命名空间配置 Configuration,那么你应该发现它与 Security Java 配置支持之间有很多相似之处。

[Note] Note

Spring Security 提供了大量的示例应用程序,它们演示了 Spring Security Java配置的使用。

5.1 Hello Web Security Java 配置

第一步是创建我们的 Spring Security Java 配置。配置创建了一个名为 springSecurityFilterChain 的 Servlet 过滤器,负责应用程序中的所有安全性(保护应用程序 URL、验证提交的用户名和密码、重定向到表单中的日志等)。你可以找到下面的 Spring Security Java 配置的最基本示例:

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

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

@EnableWebSecurity
public class WebSecurityConfig implements WebMvcConfigurer {

	@Bean
	public UserDetailsService userDetailsService() throws Exception {
		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
		manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
		return manager;
	}
}

这个配置真的不多,但它做了很多。你可以找到以下特征的摘要:

  • 需要对应用程序中的每个 URL 进行身份验证
  • 生成一个登录表单 Generate a login form for you
  • 允许用户以 Username user 和 Password password 来使用表单基础身份验证进行身份验证
  • 允许用户注销
  • CSRF 攻击 预防
  • 会话固定保护
  • Security Header 集成

    • 安全请求的 HTTP 严格传输安全性
    • X-Content-Type-Options 集成
    • 缓存控件(可以在应用程序后面重写以允许缓存静态资源)
    • X-XSS-Protection 集成
    • X-Frame-Options 集成有助于防止 Clickjacking
  • 与以下 servlet API 方法集成

    • HttpServletRequest#getRemoteUser()
    • HttpServletRequest.html#getUserPrincipal()
    • HttpServletRequest.html#isUserInRole(java.lang.String)
    • HttpServletRequest.html#login(java.lang.String, java.lang.String)
    • HttpServletRequest.html#logout()

5.1.1 AbstractSecurityWebApplicationInitializer

下一步是在 war 中注册 springSecurityFilterChain。这可以在 Servlet 3.0+ 环境中用 Spring 的 WebApplicationInitializer 支持在 Java 配置中完成。不足为奇的是,Spring Security 提供了一个基类 AbstractSecurityWebApplicationInitializer,它将确保为你注册 springSecurityFilterChain。我们使用 AbstractSecurityWebApplicationInitializer 的方式根据我们是否已经在使用 Spring 或者 Spring Security 是否是我们应用程序中唯一的 Spring 组件而不同。

  • 小节 5.1.2, “AbstractSecurityWebApplicationInitializer 而没有存在的 Spring” - 如果你还没有使用 Spring,请使用这些说明
  • 小节 5.1.3, “AbstractSecurityWebApplicationInitializer 与 Spring MVC” - 如果你使用 Spring,请使用这些说明

5.1.2 AbstractSecurityWebApplicationInitializer 而没有存在的 Spring

如果不使用 Spring 或 Spring MVC,则需要将 WebSecurityConfig 传递到超类中,以确保获得配置。你可以在下面找到一个例子:

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

public class SecurityWebApplicationInitializer
	extends AbstractSecurityWebApplicationInitializer {

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

SecurityWebApplicationInitializer 将执行以下操作:

  • 为应用程序中的每个 URL 自动注册 springSecurityFilterChain 过滤器
  • 添加 ContextLoaderListener 以加载 WebSecurityConfig。

5.1.3 AbstractSecurityWebApplicationInitializer 与 Spring MVC

如果我们在应用程序中的其它地方使用 Spring,那么很可能已经有一个 WebApplicationInitializer 正在加载 Spring 配置。如果我们使用以前的配置,会得到一个错误。相反,我们应该用现有的 ApplicationContext 注册 Spring Security。例如,如果我们使用 Spring MVC,我们的 SecurityWebApplicationInitializer 看起来会像下面这样:

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

public class SecurityWebApplicationInitializer
	extends AbstractSecurityWebApplicationInitializer {

}

这只会简单地为应用程序中的每个 URL 注册 springSecurityFilterChain 过滤器。之后,我们将确保 WebSecurityConfig 加载到我们现有的 ApplicationInitializer 中。例如,如果我们使用 Spring MVC,它将被添加到 getRootConfigClasses() 中。

public class MvcWebApplicationInitializer extends
		AbstractAnnotationConfigDispatcherServletInitializer {

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

	// ... other overrides ...
}

5.2 HttpSecurity

到目前为止,我们的 WebSecurityConfig 只包含了如何验证用户的信息。Spring Security 如何知道我们希望所有用户都被认证?Spring Security 如何知道我们想要支持基于表单的身份验证?原因在于,WebSecurityConfigurerAdapter 在 configure(HttpSecurity http) 方法中提供了如下默认配置:

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

上面的默认配置:

  • 确保对我们的应用程序的任何请求都需要用户进行身份验证
  • 允许用户使用基于表单的登录进行身份验证
  • 允许用户使用 HTTP 基本身份验证

你将注意到,此配置与 XML 命名空间配置非常相似:

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

关闭 XML 标签的 Java 配置等价于使用 and() 方法来表示,这允许我们继续配置父级。如果你阅读代码,它也是有意义的。我想配置授权请求并配置表单登录并配置 HTTP 基本身份验证。

5.3 Java 配置和表单登录

你可能想知道在提示你登录时,登录表单来自哪里,因为我们没有提到任何 HTML 文件或 JSP。由于 Spring Security 的默认配置没有显式地为登录页面设置 URL,因此 Spring Security 根据启用的特性以及使用用于处理提交的登录的 URL 的标准值,自动生成一个默认目标 URL,用户将发送到登录后等等。

虽然自动生成的登录页面便于快速启动和运行,但是大多数应用程序都希望提供自己的登录页面。为了这样做,我们可以更新配置如下所示:

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

1

更新的配置指定登录页面的位置。

2

我们必须允许所有用户(即未经认证的用户)访问我们的登录页面。formLogin().permitAll() 方法允许授予与基于表单的登录相关的所有 URL 的所有用户的访问权限。

下面我们可以看到一个用 JSP 实现的示例登录页面,用于我们当前的配置:

[Note] Note

下面的登录页面代表我们当前的配置。如果一些缺省值不能满足我们的需要,可以轻松地更新配置。

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

1

指向 /login URL 的 POST 将尝试对用户进行身份验证。

2

如果存在查询参数 error,则尝试进行身份验证并失败。

3

如果存在查询参数 logout,则用户已成功退出。

4

username 必须作为名为 username 的 HTTP 参数存在

5

password 必须作为名为 password 的 HTTP 参数存在

6

我们必须 小节 19.4.3, “包含 CSRF 令牌”,请参阅 小节 19, 跨站点请求伪造 (CSRF) 部分来了解更多信息。

5.4 授权请求

我们的示例只要求用户进行身份验证,并为我们的应用程序中的每个 URL 都进行了验证。我们可以通过为我们的 http.authorizeRequests() 方法添加多个子节点来指定 URL 的自定义需求。例如:

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

1

对于 http.authorizeRequests() 方法有多个子类,每个匹配器按照它们声明的顺序来考虑。

2

我们指定了任何用户可以访问的多个 URL 模式。具体来说,如果 URL 以 "/resources/" 开始,等于 "/signup",或等于 "/about",则任何用户都可以访问请求。

3

以 "/admin/" 开始的任何 URL 都将限制在具有 "ROLE_ADMIN" 角色的用户。你将注意到,因为我们正在调用 hasRole 方法,所以不需要指定 "ROLE_" 前缀。

4

以 "/db/" 开始的任何 URL 都要求用户同时拥有 "ROLE_ADMIN" 和 "ROLE_DBA"。你将注意到,由于我们使用 hasRole 表达式,所以不需要指定 "ROLE_" 前缀。

5

任何尚未匹配的 URL 只要求用户进行身份验证。

5.5 处理注销

当使用 WebSecurityConfigurerAdapter 时,自动应用注销功能。默认情况下,访问 URL /logout 将通过以下方式记录用户:

  • 作废 HTTP Session
  • 清除所有被配置的 RememberMe 身份验证
  • 清除 SecurityContextHolder
  • 重定向到 /login?logout

与配置登录功能类似,无论如何,你还可以有多种选项来进一步定制注销需求:

protected void configure(HttpSecurity http) throws Exception {
	http
		.logout()                                                                1
			.logoutUrl("/my/logout")                                                 2
			.logoutSuccessUrl("/my/index")                                           3
			.logoutSuccessHandler(logoutSuccessHandler)                              4
			.invalidateHttpSession(true)                                             5
			.addLogoutHandler(logoutHandler)                                         6
			.deleteCookies(cookieNamesToClear)                                       7
			.and()
		...
}

1

提供注销支持。这在使用 WebSecurityConfigurerAdapter 时自动应用。

2

触发注销发生的 URL(默认为 /logout)。如果启用了 CSRF 保护(默认),那么请求也必须是 POST。欲了解更多信息,请参阅 JavaDoc。

3

注销发生后重定向到的 URL。默认是 /login?logout。欲了解更多信息,请参阅 JavaDoc。

4

让我们指定一个自定义 LogoutSuccessHandler。如果指定了此项,则忽略 logoutSuccessUrl()。欲了解更多信息,请参阅 JavaDoc。

5

指定是否在注销时使 HttpSession 无效。默认情况下是这样。在掩码下配置 SecurityContextLogoutHandler。欲了解更多信息,请参阅 JavaDoc。

6

添加 LogoutHandler。默认情况下,SecurityContextLogoutHandler 被添加为最后一个 LogoutHandler。

7

允许在注销成功时删除的 Cookie 名称。这是一个显式添加 CookieClearingLogoutHandler 的快捷方式。

[Note] Note

当然也可以使用 XML 命名空间符号来配置注销。有关详细信息,请参阅 Spring Security XML 命名空间部分中注销元素的文档。

通常,为了自定义注销功能,可以添加 LogoutHandler 和/或 LogoutSuccessHandler 实现。对于许多常见的场景,当使用 fluent API 时,这些处理程序被应用于覆盖之下。

5.5.1 LogoutHandler

通常,LogoutHandler 实现标识类能够参与注销处理。它们将被要求执行必要的清理工作。因此,它们不应抛出异常。提供了各种实现方式:

  • PersistentTokenBasedRememberMeServices
  • TokenBasedRememberMeServices
  • CookieClearingLogoutHandler
  • CsrfLogoutHandler
  • SecurityContextLogoutHandler

更多详细信息请参阅 小节 18.4, “记住我接口和实现”。

除了直接提供 LogoutHandler 实现之外,fluent API 还提供了快捷方式,这些快捷方式提供了相应的 LogoutHandler 实现。例如,deleteCookies() 允许注销成功时指定名称删除一个或多个 Cookie。与添加 CookieClearingLogoutHandler 相比,这是一个快捷方式。

5.5.2 LogoutSuccessHandler

LogoutSuccessHandler 在成功注销之后被 LogoutFilter 调用,以处理例如重定向或转发到适当的目的地。请注意,该接口与 LogoutHandler 几乎相同,但可能引发异常。

提供以下实现:

  • SimpleUrlLogoutSuccessHandler
  • HttpStatusReturningLogoutSuccessHandler

如上所述,你不需要直接指定 SimpleUrlLogoutSuccessHandler。相反,fluent API 通过设置 logoutSuccessUrl() 提供快捷方式。这将在封面下建立 SimpleUrlLogoutSuccessHandler。所提供的 URL 将在注销发生后被重定向到。默认是 /login?logout。

在 REST API 类型的场景中,HttpStatusReturningLogoutSuccessHandler 可能是有趣的。这个 LogoutSuccessHandler 允许你提供要返回的普通 HTTP 状态代码,而不是在成功注销时重定向到 URL。如果未配置,默认情况下将返回状态代码 200。

5.5.3 进一步关于注销的参考文档

  • 注销处理
  • 测试注销
  • HttpServletRequest.logout()
  • 小节 18.4, “记住我接口和实现”
  • CSRF 附加说明章节中的注销
  • 单点注销章节 (CAS protocol)
  • Spring Security XML 命名空间章节中的注销元素文档

5.6 WebFlux Security

Spring Security 的 WebFlux 支持依赖于 WebFilter,并且对于 Spring WebFlux 和 Spring WebFlux.Fn 同样适用。你可以找到展示下面代码的几个示例应用程序:

  • Hello WebFlux hellowebflux
  • Hello WebFlux.Fn hellowebfluxfn
  • Hello WebFlux 方法 hellowebflux-method

5.6.1 最小化 WebFlux Security 配置

你可以找到如下的最小化 WebFlux Security 配置:

@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {

	@Bean
	public MapReactiveUserDetailsService userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
			.username("user")
			.password("user")
			.roles("USER")
			.build();
		return new MapReactiveUserDetailsService(user);
	}
}

此配置提供表单和 http 基本身份验证、设置授权以要求经过身份验证的用户访问任何页面、设置默认登录页面和默认注销页面、设置与安全相关的 HTTP 报头、CSRF 保护等等。

5.6.2 明确的 WebFlux Security 配置

你可以找到如下的最小化 WebFlux Security 配置的明确的版本:

@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {

	@Bean
	public MapReactiveUserDetailsService userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
			.username("user")
			.password("user")
			.roles("USER")
			.build();
		return new MapReactiveUserDetailsService(user);
	}

	@Bean
	public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange()
				.anyExchange().authenticated()
				.and()
			.httpBasic().and()
			.formLogin();
		return http.build();
	}
}

这个配置明确地设置了与我们的最小配置相同的所有东西。从这里你可以轻松地对默认值进行更改。

5.7 OAuth 2.0 登录

OAuth 2.0 登录特性为应用程序提供了使用用户在 OAuth 2.0 提供者(如 GitHub)或 OpenID 连接 1.0 提供者(如 Google)的现有帐户登录应用程序的能力。OAuth 2.0 登录实现用例:"Login with Google" 或 "Login with GitHub"”。

[Note] Note

OAuth 2.0 登录是通过使用 Authorization Code Grant 来实现的,如 OAuth 2.0 Authorization Framework 和 OpenID Connect Core 1.0 中所指定的。

5.7.1 Spring Boot 2.0 示例

Spring Boot 2.0 为 OAuth 2.0 登录带来了全自动配置功能。

本节展示如何使用 Google 作为身份验证提供者来配置 OAuth 2.0 登录示例,并介绍以下主题:

  • 初始设置
  • 设置重定向 URI
  • 配置 application.yml
  • 启动应用程序

初始设置

要使用 Google 的 OAuth 2.0 身份验证系统进行登录,必须在 Google API 控制台中设置项目以获得 OAuth 2.0 凭证。

[Note] Note

用于身份验证的 Google 的 OAuth 2.0 实现符合 OpenID Connect 1.0 规范,并且是 OpenID 认证的。

遵循 OpenID 连接页面上的说明,从设置 OAuth 2.0 开始。

在完成获取 OAuth 2.0 凭证指令之后,你应该拥有一个新的 OAuth Client,该 Client 具有由 Client ID 和 Client Secret 组成的凭证。

设置重定向 URI

重定向 URI 是应用程序中的路径,最终用户的用户代理在用 Google 进行身份验证并授予访问同意页上的 OAuth 客户端(在上一步中创建)权限之后被重定向回该路径。

在设置重定向 URI 子节中,确保授权重定向 URI 字段设置为 http://localhost:8080/login/oauth2/code/google。

[Tip] Tip

默认的重定向 URI 模版是 {baseUrl}/login/oauth2/code/{registrationId}。registrationId 是 ClientRegistration 的唯一标识。

配置 application.yml

现在有了新的 OAuth Client 和 Google,你需要配置应用程序以使用 OAuth Client 进行身份验证。这样做:

  1. Go to application.yml and set the following configuration:

    spring:
      security:
        oauth2:
          client:
            registration:	1
              google:	2
                client-id: google-client-id
                client-secret: google-client-secret

    示例 5.1. OAuth Client 属性

    1

    spring.security.oauth2.client.registration 是 OAuth Client 属性的基本属性的前缀。

    2

    遵循基本属性前缀是 ClientRegistration 的 ID,如 google。


  2. 用先前创建的 OAuth 2.0 凭证替换 client-id 和 client-secret 属性中的值。

启动应用程序

启动 Spring Boot 2.0 示例并转到 http://localhost:8080。然后重定向到默认的自动生成的登录页面,该页面显示了 Google 的链接。

点击 Google 链接,然后重定向到 Google 进行身份验证。

在使用你的 Google 帐户凭证进行身份验证后,下一页呈现给你的是同意屏幕。同意屏幕要求你允许或拒绝访问你先前创建的 OAuth Client。单击 Allow 授权 OAuth Client 访问你的电子邮件地址和基本配置文件信息。

此时,OAuth Client 从 UserInfo 端点检索电子邮件地址和基本配置文件信息,并建立经过身份验证的会话。

5.7.2 ClientRegistration

ClientRegistration 是用 OAuth 2.0 或 OpenID 连接 1.0 提供者注册的客户端的表示。

客户端注册保存信息,例如, 客户端 id、客户端 secret、授权授予类型、重定向 URI、范围、授权 URI、令牌 URI 和其它细节。

ClientRegistration 及其属性定义如下:

public final class ClientRegistration {
	private String registrationId;	1
	private String clientId;	2
	private String clientSecret;	3
	private ClientAuthenticationMethod clientAuthenticationMethod;	4
	private AuthorizationGrantType authorizationGrantType;	5
	private String redirectUriTemplate;	6
	private Set<String> scopes;	7
	private ProviderDetails providerDetails;
	private String clientName;	8

	public class ProviderDetails {
		private String authorizationUri;	9
		private String tokenUri;	10
		private UserInfoEndpoint userInfoEndpoint;
		private String jwkSetUri;	11

		public class UserInfoEndpoint {
			private String uri;	12
			private String userNameAttributeName;	13

		}
	}
}

1

registrationId:这个 ID 作为 ClientRegistration 的唯一标识。

2

clientId:客户端标识。

3

clientSecret: 客户端 secret。

4

clientAuthenticationMethod:用于使用提供者对客户端进行身份验证的方法。支持的值是 basic 和 post。

5

authorizationGrantType:OAuth 2.0 授权框架定义了四种授权授予类型。支持的值是 authorization_code 和 implicit。

6

redirectUriTemplate:客户端的注册重定向 URI,授权服务器将最终用户的用户代理重定向到最终用户对客户端进行了身份验证和授权访问之后。默认重定向 URI 模板是 {baseUrl}/login/oauth2/code/{registrationId},它支持 URI 模板变量。

7

scopes:客户端在授权请求流程中请求的范围,如 openid、email 或 profile。

8

clientName:客户端使用的描述名称。这个名称在一定的场景使用,在自动生产的登录页面展示客户端的名称。

9

authorizationUri:授权服务器的身份验证端点 URI。

10

tokenUri:授权服务器的令牌端点 URI。

11

jwkSetUri:用于从授权服务器检索 JSON Web Key (JWK) 集合的 URI,其中包含用于验证 ID 令牌的 JSON Web Signature (JWS) 的密码密钥,以及可选的用户信息响应。

12

(userInfoEndpoint)uri:用于访问已验证的最终用户 claims/attributes 的 UserInfo 端点 URI。

13

userNameAttributeName:在 UserInfo 响应中返回的属性名称,它引用最终用户的名称或标识符。

5.7.3 Spring Boot 2.0 属性映射

下表概述了 Spring Boot 2.0 OAuth Client 属性到 ClientRegistration 属性的映射。

Spring Boot 2.0 ClientRegistration

spring.security.oauth2.client.registration.[registrationId]

registrationId

spring.security.oauth2.client.registration.[registrationId].client-id

clientId

spring.security.oauth2.client.registration.[registrationId].client-secret

clientSecret

spring.security.oauth2.client.registration.[registrationId].client-authentication-method

clientAuthenticationMethod

spring.security.oauth2.client.registration.[registrationId].authorization-grant-type

authorizationGrantType

spring.security.oauth2.client.registration.[registrationId].redirect-uri-template

redirectUriTemplate

spring.security.oauth2.client.registration.[registrationId].scope

scopes

spring.security.oauth2.client.registration.[registrationId].client-name

clientName

spring.security.oauth2.client.provider.[providerId].authorization-uri

providerDetails.authorizationUri

spring.security.oauth2.client.provider.[providerId].token-uri

providerDetails.tokenUri

spring.security.oauth2.client.provider.[providerId].jwk-set-uri

providerDetails.jwkSetUri

spring.security.oauth2.client.provider.[providerId].user-info-uri

providerDetails.userInfoEndpoint.uri

spring.security.oauth2.client.provider.[providerId].userNameAttribute

providerDetails.userInfoEndpoint.userNameAttributeName

5.7.4 ClientRegistrationRepository

ClientRegistrationRepository 作为 OAuth 2.0 / OpenID Connect 1.0 ClientRegistration 的存储库.

[Note] Note

客户端注册信息最终由关联的授权服务器存储和拥有。该存储库提供检索主客户端注册信息的子集的能力,该子集存储在授权服务器中。

Spring Boot 2.0 自动配置将 spring.security.oauth2.client.registration.[registrationId] 下的每一个属性都绑定到 ClientRegistration 实例,然后在 ClientRegistrationRepository 中组成每个 ClientRegistration 实例。

[Note] Note

ClientRegistrationRepository 的默认实现是 InMemoryClientRegistrationRepository。

自动配置也将 ClientRegistrationRepository 注册为应用程序上下文中的一个 @Bean,因此,当应用程序需要时,它也可以用于依赖注入,

下面的列表展示了一个示例:

@Controller
public class OAuth2LoginController {

	@Autowired
	private ClientRegistrationRepository clientRegistrationRepository;

	@RequestMapping("/")
	public String index() {
		ClientRegistration googleRegistration =
			this.clientRegistrationRepository.findByRegistrationId("google");

		...

		return "index";
	}
}

5.7.5 CommonOAuth2Provider

CommonOAuth2Provider 预先定义了一组默认的客户端属性给许多著名的提供者:Google、GitHub、Facebook 和 Okta。

例如,authorization-uri、token-uri 和 user-info-uri 不常为提供者改变。因此,提供默认值以减少所需配置是有意义的。

如前所述,当配置 Google 客户端时,只需要 client-id 和 client-secret 属性。

下面的列表展示了一个示例:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: google-client-id
            client-secret: google-client-secret
[Tip] Tip

客户端属性的自动默认在这里无缝地工作,因为 registrationId (google)与 CommonOAuth2Provider 中的 GOOGLE 枚举(区分大小写)匹配。

对于可能需要指定不同的 registrationId(例如 google-login)的情况,仍然可以通过配置提供者属性来利用客户端属性的自动默认值。

下面的列表展示了一个示例:

spring:
  security:
    oauth2:
      client:
        registration:
          google-login:	1
            provider: google	2
            client-id: google-client-id
            client-secret: google-client-secret

1

registrationId 被设置为 google-login.

2

provider 属性设置为 google,它将利用 CommonOAuth2Provider.GOOGLE.getBuilder() 中设置的客户端属性的自动默认值。

5.7.6 配置自定义提供者属性

有一些 OAuth 2.0 提供者支持多租户,这导致每个租户(或子域)具有不同的协议端点。

例如,OAuth 客户端使用 Okta 注册,会被分配到指定的子域,并具有不同的协议端点。

对于这些情况,Spring Boot 2.0 提供了用于配置自定义提供程序属性的下列基本属性:spring.security.oauth2.client.provider.[providerId]。

下面的列表展示了一个示例:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
        provider:
          okta:	1
            authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
            token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
            user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
            user-name-attribute: sub
            jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys

1

基础属性(spring.security.oauth2.client.provider.okta)允许自定义配置协议端点位置。

5.7.7 覆盖 Spring Boot 2.0 自动配置

用于 OAuth Client 支持的 Spring Boot 2.0 自动配置类是 OAuth2ClientAutoConfiguration。

它执行以下任务:

  • 从配置的 OAuth Client 属性注册一个 ClientRegistrationRepository @Bean,由 ClientRegistration 组成。
  • 提供 WebSecurityConfigurerAdapter @Configuration,并通过 httpSecurity.oauth2Login() 启用 OAuth 2.0 登录。

如果你需要根据你的具体要求重写自动配置,你可以这样做:

  • 注册一个 ClientRegistrationRepository @Bean
  • 提供一个 WebSecurityConfigurerAdapter
  • 完全重写自动配置

注册一个 ClientRegistrationRepository @Bean

下面的示例展示了如何注册一个 ClientRegistrationRepository @Bean:

@Configuration
public class OAuth2LoginConfig {

	@Bean
	public ClientRegistrationRepository clientRegistrationRepository() {
		return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
	}

	private ClientRegistration googleClientRegistration() {
		return ClientRegistration.withRegistrationId("google")
			.clientId("google-client-id")
			.clientSecret("google-client-secret")
			.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
			.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
			.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
			.scope("openid", "profile", "email", "address", "phone")
			.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
			.tokenUri("https://www.googleapis.com/oauth2/v4/token")
			.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
			.userNameAttributeName(IdTokenClaimNames.SUB)
			.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
			.clientName("Google")
			.build();
	}
}

提供一个 WebSecurityConfigurerAdapter

下面的示例展示了如何使用 @EnableWebSecurity 提供一个 WebSecurityConfigurerAdapter,并通过 httpSecurity.oauth2Login() 启用 OAuth 2.0 登录:

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

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

完全重写自动配置

下面的示例展示了如何通过注册一个 ClientRegistrationRepository @Bean 和提供一个 WebSecurityConfigurerAdapter 来完全重写自动配置,这两者都在前面的两个小节中进行了描述。

@Configuration
public class OAuth2LoginConfig {

	@EnableWebSecurity
	public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

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

	@Bean
	public ClientRegistrationRepository clientRegistrationRepository() {
		return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
	}

	private ClientRegistration googleClientRegistration() {
		return ClientRegistration.withRegistrationId("google")
			.clientId("google-client-id")
			.clientSecret("google-client-secret")
			.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
			.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
			.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
			.scope("openid", "profile", "email", "address", "phone")
			.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
			.tokenUri("https://www.googleapis.com/oauth2/v4/token")
			.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
			.userNameAttributeName(IdTokenClaimNames.SUB)
			.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
			.clientName("Google")
			.build();
	}
}

5.7.8 没有 Spring Boot 2.0 的 Java 配置

如果不能使用 Spring Boot 2.0,并且希望配置 CommonOAuth2Provider(例如,Google)中的一个预定义提供者,则应用以下配置:

@Configuration
public class OAuth2LoginConfig {

	@EnableWebSecurity
	public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

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

	@Bean
	public ClientRegistrationRepository clientRegistrationRepository() {
		return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
	}

	@Bean
	public OAuth2AuthorizedClientService authorizedClientService() {
		return new InMemoryOAuth2AuthorizedClientService(this.clientRegistrationRepository());
	}

	private ClientRegistration googleClientRegistration() {
		return CommonOAuth2Provider.GOOGLE.getBuilder("google")
			.clientId("google-client-id")
			.clientSecret("google-client-secret")
			.build();
	}
}

5.7.9 OAuth2AuthorizedClient / OAuth2AuthorizedClientService

OAuth2AuthorizedClient 是授权客户端的表现。当最终用户(资源所有者)授权客户端访问其受保护的资源时,客户端被认为是被授权的。

OAuth2AuthorizedClient 用于将 OAuth2AccessToken 与 ClientRegistration(客户端)和资源所有者相关联,后者是授予授权的主要最终用户。

OAuth2AuthorizedClientService 的主要作用是管理 OAuth2AuthorizedClient 实例。从开发人员的角度来看,它提供了查找与客户端关联的 OAuth2AccessToken 的能力,以便可用于发起对资源服务器的请求。

[Note] Note

Spring Boot 2.0 自动配置在应用程序上下文中注册 OAuth2AuthorizedClientService @Bean。

开发人员还可以在应用程序上下文中注册 OAuth2AuthorizedClientService @Bean(覆盖 Spring Boot 2.0 自动配置),以便能够查找与特定 ClientRegistration(客户端)相关联的 OAuth2AccessToken。

下面的列表展示了一个示例:

@Controller
public class OAuth2LoginController {

	@Autowired
	private OAuth2AuthorizedClientService authorizedClientService;

	@RequestMapping("/userinfo")
	public String userinfo(OAuth2AuthenticationToken authentication) {
		// authentication.getAuthorizedClientRegistrationId() returns the
		// registrationId of the Client that was authorized during the Login flow
		OAuth2AuthorizedClient authorizedClient =
			this.authorizedClientService.loadAuthorizedClient(
				authentication.getAuthorizedClientRegistrationId(),
				authentication.getName());

		OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

		...

		return "userinfo";
	}
}

5.7.10 附加资源

下面的附加资源描述高级配置选项:

  • OAuth 2.0 登录页
  • 身份验证端点:

    • AuthorizationRequestRepository
  • 重定向端点
  • 令牌端点:

    • OAuth2AccessTokenResponseClient
  • 用户信息端点:

    • 映射用户授权
    • 配置一个自定义 OAuth2User
    • OAuth 2.0 用户服务
    • OpenID Connect 1.0 用户服务

5.8 身份验证

到目前为止,我们只研究了最基本的身份验证配置。让我们来看看一些配置认证的更高级的选项。

5.8.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;
}

5.8.2 JDBC 的身份验证

你可以找到更新以支持基于 JDBC 的身份验证。下面的示例假定你已经在应用程序中定义了数据源。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"));
}

5.8.3 LDAP 的身份验证

你可以找到更新以支持基于 LDAP 的身份验证。ldap-javaconfig 示例提供了使用基于 LDAP 的身份验证的完整示例。

@Autowired
private DataSource dataSource;

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

上面的例子使用了下面的 LDIF 和一个嵌入式 Apache DS LDAP 实例。

users.ldif. 

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

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

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

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

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

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

5.8.4 身份验证提供者

你可以通过将自定义 AuthenticationProvider 暴露为 bean 来定义自定义身份验证。例如,假设 SpringAuthenticationProvider 实现 AuthenticationProvider,以下将自定义身份验证:

[Note] Note

这仅在 AuthenticationManagerBuilder 尚未被填充时使用。

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

5.8.5 用户详细信息服务

您可以通过将自定义 UserDetailsService 暴露为 bean 来定义自定义身份验证。例如,假设 SpringDataUserDetailsService 实现 UserDetailsService,以下将自定义身份验证:

[Note] Note

这仅在 AuthenticationManagerBuilder 尚未被填充以及没有 AuthenticationProviderBean 被定义时使用。

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

还可以通过将 PasswordEncoder 作为 bean 来公开自定义密码是如何编码的。例如,如果使用 bcrypt,可以添加如下定义的 bean 定义:

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

5.9 多个 HttpSecurity

我们可以配置多个 HttpSecurity 实例就像我们可以有多个 <http> 块。关键是要多次扩展 WebSecurityConfigurationAdapter。例如,下面是一个具有 URL 的不同配置的示例,该 URL 从 /api/ 开始。

@EnableWebSecurity
public class MultiHttpSecurityConfig {
	@Bean                                                             1
	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;
	}

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

	@Configuration                                                   4
	public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

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

1

正常情况下配置身份验证

2

创建包含 @Order 的 WebSecurityConfigurerAdapter 实例,以指定应该首先考虑哪个 WebSecurityConfigurerAdapter。

3

http.antMatcher 声明此 HttpSecurity 仅适用于以 /api/ 开头的 URL。

4

创建 WebSecurityConfigurerAdapter 的另一个实例。如果 URL 不以 /api/ 开头,则使用此配置。此配置是在 ApiWebSecurityConfigurationAdapter 之后考虑的,因为它在 1 之后有一个 @Order 值(没 @Order 则默认为最后)。

5.10 方法安全

从版本 2.0 起,Spring Security 大大提高了对服务层方法的安全性的支持。它支持 JSR-250 注解安全性以及框架的原始 @Secured 注解。从 3.0 开始,你还可以使用新的基于表达式的注解。你可以使用 intercept-methods 元素对单个 bean 应用安全性,以修饰 bean 声明,或者你可以使用 AspectJ 样式切入点在整个服务层上保护多个 bean。

5.10.1 EnableGlobalMethodSecurity

我们可以在任何 @Configuration 实例上使用 @EnableGlobalMethodSecurity 注解启用基于注解的安全性。例如,下面将启用 Spring Security 的 @Secured 注解。

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

将注解添加到方法(在类或接口上)将相应地限制对该方法的访问。Spring Security 的本机注解支持定义了该方法的一组属性。这些将传递给 AccessDecisionManager,以便它做出实际决策:

public interface BankService {

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

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

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

JSR-250 注解的支持可以使用

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

这些是基于标准的,并且允许应用简单的基于角色的约束,但是没有强大的 Spring Security 的本地注释。若要使用基于表达式的新语法,将使用

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

和等效 Java 代码将是

public interface BankService {

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

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

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

5.10.2 GlobalMethodSecurityConfiguration

有时,你可能需要执行比使用 @EnableGlobalMethodSecurity 注解允许的更复杂的操作。对于这些实例,可以扩展 GlobalMethodSecurityConfiguration,以确保子类中存在 @EnableGlobalMethodSecurity 注解。例如,如果你想提供自定义 MethodSecurityExpressionHandler,可以使用以下配置:

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

有关可以重写的方法的其它信息,请参阅 GlobalMethodSecurityConfiguration Javadoc。

5.10.3 EnableReactiveMethodSecurity

Spring Security 使用 Reactor 的上下文支持方法安全,它设置使用 ReactiveSecurityContextHolder。例如,这里展示了如何检索当前登录的用户的消息。

[Note] Note

为此,该方法的返回类型必须是 org.reactivestreams.Publisher(即 Mono/Flux)。这是必要的,以整合的 Reactor 的上下文。

Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");

Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
	.map(SecurityContext::getAuthentication)
	.map(Authentication::getName)
	.flatMap(this::findMessageByUsername)
	// In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
	.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));

StepVerifier.create(messageByUsername)
	.expectNext("Hi user")
	.verifyComplete();

这一 this::findMessageByUsername 定义为:

Mono<String> findMessageByUsername(String username) {
	return Mono.just("Hi " + username);
}

下面是在应用程序中使用方法安全时的最小方法安全配置。

@EnableReactiveMethodSecurity
public class SecurityConfig {
	@Bean
	public MapReactiveUserDetailsService userDetailsService() {
		User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
		UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
		UserDetails admin = userBuilder.username("admin").password("admin").roles("USER","ADMIN").build();
		return new MapReactiveUserDetailsService(rob, admin);
	}
}

考虑下面的类:

@Component
public class HelloWorldMessageService {
	@PreAuthorize("hasRole('ADMIN')")
	public Mono<String> findMessage() {
		return Mono.just("Hello World!");
	}
}

结合上面的配置,@PreAuthorize("hasRole('ADMIN')") 将确保 findByMessage 仅由具有 ADMIN 角色的用户调用。重要的是要注意,在标准方法安全工作中的任何表达式都是用于 @EnableReactiveMethodSecurity的。但是,此时我们只支持表达式的 Boolean 或布尔型的返回类型。这意味着表达式不能阻止。

当与 小节 5.6, “WebFlux Security” 集成时,根据认证的用户,通过 Spring Security 自动建立 Reactor 上下文。

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

	@Bean
	SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
		return http
			// Demonstrate that method security works
			// Best practice to use both for defense in depth
			.authorizeExchange()
				.anyExchange().permitAll()
				.and()
			.httpBasic().and()
			.build();
	}

	@Bean
	MapReactiveUserDetailsService userDetailsService() {
		User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
		UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
		UserDetails admin = userBuilder.username("admin").password("admin").roles("USER","ADMIN").build();
		return new MapReactiveUserDetailsService(rob, admin);
	}
}

你可以在 hellowebflux-method 中找到一个完整的示例。

5.11 后处理配置对象

Spring Security 的 Java 配置不公开其配置的每个对象的每个属性。这简化了大多数用户的配置。毕竟,如果每个属性被公开,用户可以使用标准的 bean 配置。

虽然有充分的理由不直接公开每一个属性,但用户可能仍然需要更高级的配置选项。为了解决这个问题,Spring Security 引入了 ObjectPostProcessor 的概念,该对象可以用来修改或替换 Java 配置创建的许多对象实例。例如,如果希望在 FilterSecurityInterceptor 上配置 filterSecurityPublishAuthorizationSuccess 属性,可以使用以下命令:

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

5.12 自定义 DSL

你可以在 Spring Security 中提供你自己的自定义 DSL。例如,你可能有这样的东西:

public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
	private boolean flag;

	@Override
	public void init(H http) throws Exception {
		// any method that adds another configurer
		// must be done in the init method
		http.csrf().disable();
	}

	@Override
	public void configure(H http) throws Exception {
		ApplicationContext context = http.getSharedObject(ApplicationContext.class);

		// here we lookup from the ApplicationContext. You can also just create a new instance.
		MyFilter myFilter = context.getBean(MyFilter.class);
		myFilter.setFlag(flag);
		http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
	}

	public MyCustomDsl flag(boolean value) {
		this.flag = value;
		return this;
	}

	public static MyCustomDsl customDsl() {
		return new MyCustomDsl();
	}
}
[Note] Note

这实际上是如何实现像 HttpSecurity.authorizeRequests() 这样的方法。

自定义 DSL 可以这样使用:

@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.apply(customDsl())
				.flag(true)
				.and()
			...;
	}
}

代码按以下顺序调用:

  • 调用 `Config` 的配置方法中的代码
  • 调用 `MyCustomDsl` 的初始化方法中的代码
  • 调用 `MyCustomDsl` 的配置方法中的代码

如果需要,可以通过使用 SpringFactories 默认使用 WebSecurityConfiguerAdapter 添加 MyCustomDsl。例如,你将创建一个名为 META-INF/spring.factories 的类路径上的资源,其内容如下:

META-INF/spring.factories. 

org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl

希望禁用默认值的用户可以显式地这样做。

@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.apply(customDsl()).disable()
			...;
	}
}