Spring Security 5.0.0 M4 发布

发布 | Rob Winch | 2017年9月15日 | ...

我代表社区,很高兴地宣布 Spring Security 5.0.0 M4 的发布。此版本包含错误修复、新功能,并基于 Spring Framework 5.0.0 RC4。您可以在 变更日志 中找到完整的详细信息。此版本的亮点包括

OAuth2 / OIDC

OAuth2 登录 Java 配置

HttpSecurity.oauth2Login() DSL 有许多改进。

您现在可以使用 AuthorizationGrantTokenExchangerSecurityTokenRepository<AccessToken> 的自定义实现来配置令牌端点,如下所示

protected void configure(HttpSecurity http) throws Exception {
  http
    .authorizeRequests()
      .anyRequest().authenticated()
      .and()
    .oauth2Login()
      .tokenEndpoint()
        .authorizationCodeTokenExchanger(this.authorizationCodeTokenExchanger())
	.accessTokenRepository(this.accessTokenRepository());
}

我们还添加了自定义授权端点重定向端点请求路径的功能

protected void configure(HttpSecurity http) throws Exception {
  http
    .authorizeRequests()
      .anyRequest().authenticated()
      .and()
    .oauth2Login()
      .authorizationEndpoint()
        .requestMatcher(new AntPathRequestMatcher("/custom-path/{clientAlias}"))
        .and()
      .redirectionEndpoint()
        .requestMatcher(new AntPathRequestMatcher("/custom-path/callback/{clientAlias}"));
}

与 Spring Security 中的所有 AbstractAuthenticationProcessingFilter 一样,您还可以设置自定义的 AuthenticationSuccessHandlerAuthenticationFailureHandler

protected void configure(HttpSecurity http) throws Exception {
  http
    .authorizeRequests()
      .anyRequest().authenticated()
      .and()
     .oauth2Login()
       .successHandler(this.customAuthenticationSuccessHandler())
       .failureHandler(this.customAuthenticationFailureHandler());
}

安全令牌存储库

我们引入了 SecurityTokenRepository<T extends SecurityToken> 抽象,它负责 SecurityToken 的持久化。

初始实现 InMemoryAccessTokenRepository 提供了 AccessToken 的持久化。在即将发布的版本中,我们还将提供一个支持刷新令牌持久化的实现。

ID 令牌和声明

IdToken 进行了一些小的改进,并为 JwtClaimAccessorStandardClaimAccessorIdTokenClaimAccessor 提供了一些最终的实现细节,这些细节提供了对其关联构造(例如 JwtIdTokenUserInfo)中声明的便捷访问。

授权请求改进

我们添加了 AuthorizationRequestRepository授权请求持久化到 Cookie 中的功能。当前的默认实现持久化到 HttpSession 中,但是,可以提供自定义实现以改为持久化到 Cookie 中。

还添加了对 AuthorizationCodeRequestRedirectFilter 中配置的 redirect-uri 中的 URI 变量的支持。

OAuth2 客户端属性

对配置 OAuth 2.0 客户端的属性进行了一些小的更新。以下配置概述了当前的结构。您会注意到,支持配置多个客户端,例如 google、github、okta 等。

security:
  oauth2:
    client:
      google:
        client-id: your-app-client-id
        client-secret: your-app-client-secret
        client-authentication-method: basic
        authorization-grant-type: authorization_code
        redirect-uri: "{scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}"
        scope: openid, profile, email, address, phone
        authorization-uri: "https://accounts.google.com/o/oauth2/v2/auth"
        token-uri: "https://www.googleapis.com/oauth2/v4/token"
        user-info-uri: "https://www.googleapis.com/oauth2/v3/userinfo"
        user-name-attribute-name: "sub"
        jwk-set-uri: "https://www.googleapis.com/oauth2/v3/certs"
        client-name: Google
        client-alias: google
      github:
        ...
      okta:
        ...

在 Spring Security 示例 oauth2login 中可以找到使用新的 Spring Security OAuth 2.0 / OpenID Connect 1.0 登录功能的完整示例。该指南将引导您完成设置示例应用程序以使用外部 OAuth 2.0 或 OpenID Connect 1.0 提供程序进行 OAuth 2.0 登录的步骤。

响应式安全

响应式方法安全

Spring Security 的响应式支持现在通过利用 Reactor 的 Context 包含方法安全。以下是亮点,但您可以在 samples/javaconfig/hellowebflux-method 中找到一个完整的示例。

第一步是使用 @EnableReactiveMethodSecurity 启用对 @PreAuthorize@PostAuthorize 注解的支持。此步骤确保对象被正确代理。

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

下一步是创建一个用 @PreAuthorize@PostAuthorize 注解的服务。例如

@PreAuthorize("hasRole('ADMIN')")
public Mono<String> findMessage() {

然后,Spring Security 的 WebFlux 支持将确保 Reactor Context 将使用当前用户填充,该用户用于确定是否授予或拒绝访问。

Spring Security 的标准 @WithMockUser相关注解 已更新为与响应式方法安全一起使用。例如

@RunWith(SpringRunner.class)
// ...
public class HelloWorldMessageServiceTests {
  @Autowired
  HelloWorldMessageService messages;

@Test public void messagesWhenNotAuthenticatedThenDenied() { StepVerifier.create(this.messages.findMessage()) .expectError(AccessDeniedException.class) .verify(); }

@Test @WithMockUser public void messagesWhenUserThenDenied() { StepVerifier.create(this.messages.findMessage()) .expectError(AccessDeniedException.class) .verify(); }

@Test @WithMockUser(roles = "ADMIN") public void messagesWhenAdminThenOk() { StepVerifier.create(this.messages.findMessage()) .expectNext("Hello World!") .verifyComplete(); } }

测试支持也与 TestWebClient 配合得很好。例如

@RunWith(SpringRunner.class)
// ...
public class HelloWebfluxMethodApplicationTests {
  @Autowired
  ApplicationContext context;

WebTestClient rest;

@Before public void setup() { this.rest = WebTestClient .bindToApplicationContext(this.context) // Setup Spring Security Test Support .apply(springSecurity()) .configureClient() .filter(basicAuthentication()) .build(); }

@Test public void messageWhenNotAuthenticated() throws Exception { this.rest .get() .uri("/message") .exchange() .expectStatus().isUnauthorized(); }

// --- authenticate with HTTP Basic ---

@Test public void messageWhenUserThenForbidden() throws Exception { this.rest .get() .uri("/message") .attributes(robsCredentials()) .exchange() .expectStatus().isEqualTo(HttpStatus.FORBIDDEN) .expectBody().isEmpty(); }

@Test public void messageWhenAdminThenOk() throws Exception { this.rest .get() .uri("/message") .attributes(adminCredentials()) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello World!"); }

// --- Use @WithMockUser ---

@Test @WithMockUser public void messageWhenWithMockUserThenForbidden() throws Exception { this.rest .get() .uri("/message") .exchange() .expectStatus().isEqualTo(HttpStatus.FORBIDDEN) .expectBody().isEmpty(); }

@Test @WithMockUser(roles = "ADMIN") public void messageWhenWithMockAdminThenOk() throws Exception { this.rest .get() .uri("/message") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello World!"); }

// --- Use mutateWith ---

@Test public void messageWhenMockUserThenForbidden() throws Exception { this.rest .mutateWith(mockUser()) .get() .uri("/message") .exchange() .expectStatus().isEqualTo(HttpStatus.FORBIDDEN) .expectBody().isEmpty(); }

@Test public void messageWhenMockAdminThenOk() throws Exception { this.rest .mutateWith(mockUser().roles("ADMIN")) .get() .uri("/message") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello World!"); }

// ...

}

WebFlux 表单登录

WebFlux 安全现在支持基于表单的登录,并提供一个默认登录页面以方便入门。例如,samples/javaconfig/hellowebflux[samples/javaconfig/hellowebflux 允许用户使用默认登录页面通过基于表单的登录进行身份验证。

@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {

@Bean public MapUserDetailsRepository userDetailsRepository() { UserDetails user = User.withUsername("user") .password("user") .roles("USER") .build(); return new MapUserDetailsRepository(user); } }

我们决定使默认登录页面链接到一个外部 CSS 文件,以便在不需要捆绑 CSS 的情况下使其看起来更美观。你觉得怎么样?

Default Log In Page

如果您未连接到互联网,登录页面将回退到无样式页面。当然,您也可以提供自己的自定义登录页面。

WebFlux 内容协商

与 servlet 世界类似,我们还为 WebFlux 安全添加了内容协商支持。例如,当请求受保护的资源而未进行身份验证时,我们来自 WebFlux 表单登录 的最小示例将在 Web 浏览器中生成一个登录页面,并在命令行中生成 WWW-Authenticate 响应。

---
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
content-length: 0

WebFlux 会话优化

我们改进了 WebFlux 身份验证和会话管理的工作方式,使其比 servlet 对应部分具有更大的灵活性。例如,当使用基于表单的登录进行身份验证时,我们来自 WebFlux 表单登录 的最小示例将产生以下结果

POST /login HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 27
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: localhost:8080

username=user&password=user

HTTP/1.1 302 Found Cache-Control: no-cache, no-store, max-age=0, must-revalidate Expires: 0 Location: / Pragma: no-cache X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1 ; mode=block content-length: 0 set-cookie: SESSION=1e04aa3c-5a15-42ed-9e25-933fd0e44b2a; HTTPOnly

但是,对于 HTTP 基本身份验证,完全相同的代码将产生以下响应

GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Basic dXNlcjp1c2Vy
Connection: keep-alive
Host: localhost:8080

HTTP/1.1 200 OK Cache-Control: no-cache, no-store, max-age=0, must-revalidate Content-Type: application/json;charset=UTF-8 Expires: 0 Pragma: no-cache X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1 ; mode=block transfer-encoding: chunked

{ "message": "Hello user!" }

请注意,基于表单的登录在响应中有一个 SESSION cookie,但 HTTP 基本身份验证没有。这是通过单个 HttpSecurity 配置完成的(我们将应用程序分成切片)。

请反馈

如果您对此版本有任何反馈,我鼓励您通过 StackOverflowGitHub Issues 或评论部分与我们联系。您也可以在 Twitter 上 ping 我 @rob_winch 或 Joe @joe_grandja

当然,最好的反馈是以 贡献 的形式出现。

获取 Spring 电子邮件

随时关注 Spring 电子邮件

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部