Spring Security—OAuth2 客户端认证和授权

一、客户端认证

1、JWT Bearer

|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 关于 JWT Bearer 客户端认证的进一步详情,请参考OAuth 2.0客户端认证和授权许可的 JSON Web Token (JWT)简介。 |

JWT Bearer 客户端认证的默认实现是 NimbusJwtClientAuthenticationParametersConverter,它是一个 Converter,通过在 client_assertion 参数中添加签名的JSON Web Token(JWS)来定制令牌请求参数。

用于签署 JWS 的 java.security.PrivateKeyjavax.crypto.SecretKey 由与 NimbusJwtClientAuthenticationParametersConverter 相关的 com.nimbusds.jose.jwk.JWK 解析器提供。

使用private_key_jwt进行认证

给出以下Spring Boot 2.x属性,用于OAuth 2.0客户端注册。

复制代码
spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-authentication-method: private_key_jwt
            authorization-grant-type: authorization_code
            ...

下面的例子显示了如何配置 DefaultAuthorizationCodeTokenResponseClient

  • Java

    Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
    if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
    // Assuming RSA key type
    RSAPublicKey publicKey = ...
    RSAPrivateKey privateKey = ...
    return new RSAKey.Builder(publicKey)
    .privateKey(privateKey)
    .keyID(UUID.randomUUID().toString())
    .build();
    }
    return null;
    };

    OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
    new OAuth2AuthorizationCodeGrantRequestEntityConverter();
    requestEntityConverter.addParametersConverter(
    new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));

    DefaultAuthorizationCodeTokenResponseClient tokenResponseClient =
    new DefaultAuthorizationCodeTokenResponseClient();
    tokenResponseClient.setRequestEntityConverter(requestEntityConverter);

使用 client_secret_jwt 进行认证

给出以下Spring Boot 2.x属性,用于OAuth 2.0客户端注册。

复制代码
spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            client-authentication-method: client_secret_jwt
            authorization-grant-type: client_credentials
            ...

下面的例子显示了如何配置 DefaultClientCredentialsTokenResponseClient

  • Java

    Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
    if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {
    SecretKeySpec secretKey = new SecretKeySpec(
    clientRegistration.getClientSecret().getBytes(StandardCharsets.UTF_8),
    "HmacSHA256");
    return new OctetSequenceKey.Builder(secretKey)
    .keyID(UUID.randomUUID().toString())
    .build();
    }
    return null;
    };

    OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
    new OAuth2ClientCredentialsGrantRequestEntityConverter();
    requestEntityConverter.addParametersConverter(
    new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));

    DefaultClientCredentialsTokenResponseClient tokenResponseClient =
    new DefaultClientCredentialsTokenResponseClient();
    tokenResponseClient.setRequestEntityConverter(requestEntityConverter);

自定义JWT断言(JWT assertion)

NimbusJwtClientAuthenticationParametersConverter 产生的JWT默认包含 isssubaudjtiiatexp 等 claim。你可以通过提供一个 Consumer<NimbusJwtClientAuthenticationParametersConverter.JwtClientAuthenticationContext<T>> 给 `setJwtClientAssertionCustomizer()`来定制头信息和/或claim。下面的例子显示了如何定制JWT的 claim。

  • Java

    Function<ClientRegistration, JWK> jwkResolver = ...

    NimbusJwtClientAuthenticationParametersConverter<OAuth2ClientCredentialsGrantRequest> converter =
    new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver);
    converter.setJwtClientAssertionCustomizer((context) -> {
    context.getHeaders().header("custom-header", "header-value");
    context.getClaims().claim("custom-claim", "claim-value");
    });

二、 OAuth2 客户端授权

1、解析授权客户端

@RegisteredOAuth2AuthorizedClient 注解提供了将方法参数解析为 OAuth2AuthorizedClient 类型的参数值的能力。与使用 OAuth2AuthorizedClientManagerOAuth2AuthorizedClientService 来访问 OAuth2AuthorizedClient 相比,这是一个方便的选择。下面的例子展示了如何使用 @RegisteredOAuth2AuthorizedClient

  • Java

    @Controller
    public class OAuth2ClientController {

    复制代码
      @GetMapping("/")
      public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
      	OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
    
      	...
    
      	return "index";
      }

    }

@RegisteredOAuth2AuthorizedClient 注解由 OAuth2AuthorizedClientArgumentResolver 处理,它直接使用​ OAuth2AuthorizedClientManager ​,因此继承其能力。

2、为Servlet环境整合WebClient

OAuth 2.0客户端支持通过使用 ExchangeFilterFunctionWebClient 整合。

ServletOAuth2AuthorizedClientExchangeFilterFunction 提供了一种机制,通过使用 OAuth2AuthorizedClient 并包括相关的 OAuth2AccessToken 作为承载令牌来请求受保护的资源。它直接使用​ OAuth2AuthorizedClientManager ​,因此,它继承了以下功能。

  • 如果客户还没有得到授权,就会请求一个 OAuth2AccessToken

    • authorization_code: 触发 Authorization 请求重定向,启动流程。

    • client_credentials: 访问令牌是直接从令牌端点获得的。

    • password: 访问令牌是直接从令牌端点获得的。

  • 如果 OAuth2AccessToken 已经过期,如果有 OAuth2AuthorizedClientProvider 可以执行授权,它将被刷新(或更新)。

下面的代码显示了一个如何配置支持OAuth 2.0客户端的 WebClient 的例子。

  • Java

    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
    new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    return WebClient.builder()
    .apply(oauth2Client.oauth2Configuration())
    .build();
    }

提供授权客户端

ServletOAuth2AuthorizedClientExchangeFilterFunction 通过从 ClientRequest.attributes()(请求属性)中解析 OAuth2AuthorizedClient,来确定(对一个请求)要使用的客户端。

下面的代码显示了如何设置一个 OAuth2AuthorizedClient 作为请求属性。

  • Java

    @GetMapping("/")
    public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
    String resourceUri = ...

    复制代码
      String body = webClient
      		.get()
      		.uri(resourceUri)
      		.attributes(oauth2AuthorizedClient(authorizedClient))   
      		.retrieve()
      		.bodyToMono(String.class)
      		.block();
    
      ...
    
      return "index";

    }

|-----------------------------------------------------------------------------------------------------------|
| oauth2AuthorizedClient() is a static method in ServletOAuth2AuthorizedClientExchangeFilterFunction. |

下面的代码显示了如何设置 ClientRegistration.getRegistrationId() 作为请求属性。

  • Java

    @GetMapping("/")
    public String index() {
    String resourceUri = ...

    复制代码
      String body = webClient
      		.get()
      		.uri(resourceUri)
      		.attributes(clientRegistrationId("okta"))   
      		.retrieve()
      		.bodyToMono(String.class)
      		.block();
    
      ...
    
      return "index";

    }

|---------------------------------------------------------------------------------------------------------|
| clientRegistrationId() is a static method in ServletOAuth2AuthorizedClientExchangeFilterFunction. |

默认授权客户端

如果 OAuth2AuthorizedClientClientRegistration.getRegistrationId() 都没有作为请求属性(request attribute)提供,ServletOAuth2AuthorizedClientExchangeFilterFunction 可以根据其配置决定使用默认客户端。

如果配置了 setDefaultOAuth2AuthorizedClient(true),并且用户已经通过 HttpSecurity.oauth2Login() 进行了认证,则使用与当前 OAuth2AuthenticationToken 关联的 OAuth2AccessToken

下面的代码显示了具体的配置。

  • Java

    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
    new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauth2Client.setDefaultOAuth2AuthorizedClient(true);
    return WebClient.builder()
    .apply(oauth2Client.oauth2Configuration())
    .build();
    }

|-------------------------------|
| 对这个功能要谨慎,因为所有的HTTP请求都会收到访问令牌。 |

另外,如果 setDefaultClientRegistrationId("okta") 配置了一个有效的 ClientRegistration,则会使用与 OAuth2AuthorizedClient 相关的 OAuth2AccessToken

下面的代码显示了具体的配置。

  • Java

    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
    new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauth2Client.setDefaultClientRegistrationId("okta");
    return WebClient.builder()
    .apply(oauth2Client.oauth2Configuration())
    .build();
    }

|-------------------------------|
| 对这个功能要谨慎,因为所有的HTTP请求都会收到访问令牌。 |

相关推荐
鼠鼠我捏,要死了捏10 分钟前
深入剖析Java垃圾回收性能优化实战指南
java·性能优化·gc
Pota-to成长日记30 分钟前
代码解析:基于时间轴(Timeline)的博客分页查询功能
java
塔能物联运维1 小时前
物联网设备运维中的自动化合规性检查与策略执行机制
java·运维·物联网·struts·自动化
不爱编程的小九九1 小时前
小九源码-springboot099-基于Springboot的本科实践教学管理系统
java·spring boot·后端
lang201509281 小时前
Spring Boot集成Spring Integration全解析
spring boot·后端·spring
雨夜之寂1 小时前
第一章-第二节-Cursor IDE与MCP集成.md
java·后端·架构
即将头秃的程序媛1 小时前
Sa-Token
java
大G的笔记本1 小时前
Spring IOC和AOP
java·后端·spring
武子康1 小时前
Java-155 MongoDB Spring Boot 连接实战 | Template vs Repository(含索引与常见坑)
java·数据库·spring boot·后端·mongodb·系统架构·nosql
武子康1 小时前
Java-157 MongoDB 存储引擎 WiredTiger vs InMemory:何时用、怎么配、如何验证 mongod.conf
java·数据库·sql·mongodb·性能优化·系统架构·nosql