Spring Security—OAuth2 授权许可

一、授权代码

1、发起授权请求

OAuth2AuthorizationRequestRedirectFilter 使用 OAuth2AuthorizationRequestResolver 来解决 OAuth2AuthorizationRequest,并通过将最终用户的用户代理重定向到授权服务器的授权端点来启动授权码授予流程。

OAuth2AuthorizationRequestResolver 的主要作用是从提供的web请求中解析出一个 OAuth2AuthorizationRequest 。默认实现 DefaultOAuth2AuthorizationRequestResolver 在(默认)路径 /oauth2/authorization/{registrationId} 上进行匹配,提取 registrationId,并使用它来建立相关 ClientRegistration 的 OAuth2AuthorizationRequest。

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/authorized/okta"
            scope: read, write
        provider:
          okta:
            authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

鉴于前面的属性,一个基本路径为 /oauth2/authorization/okta 的请求会启动 OAuth2AuthorizationRequestRedirectFilter 的授权请求重定向,并最终启动授权代码授予流程。

|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| AuthorizationCodeOAuth2AuthorizedClientProvider 是 OAuth2AuthorizedClientProvider 的一个实现,用于授权码的授予,它也启动了 OAuth2AuthorizationRequestRedirectFilter 的授权请求重定向。 |

如果OAuth 2.0客户端是一个 公共客户端(Public Client),请按以下方式配置OAuth 2.0客户端的注册。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-authentication-method: none
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/authorized/okta"
            ...

公共客户端是通过使用 代码交换的证明密钥(PKCE)来支持的。如果客户端运行在一个不受信任的环境中(如本地应用程序或基于Web浏览器的应用程序),因此没有能力维护其证书的保密性,当以下条件为真(true)时,会自动使用PKCE。

  1. client-secret 被省略(或为空)。
  2. client-authentication-method 被设置为 none(ClientAuthenticationMethod.NONE)。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 如果OAuth 2.0提供商支持 Confidential Clients 的PKCE,你可以(选择性地)使用 DefaultOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()) 进行配置。 |

DefaultOAuth2AuthorizationRequestResolver 还通过使用 UriComponentsBuilder 支持URI模板变量用于 redirect-uri。

下面的配置使用了所有支持的 URI 模板变量。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            ...
            redirect-uri: "{baseScheme}://{baseHost}{basePort}{basePath}/authorized/{registrationId}"
            ...

|--------------------------------------------------------------|
| {baseUrl} 解析为 {baseScheme}://{baseHost}{basePort}{basePath}。 |

当OAuth 2.0客户端在 代理服务器 后面运行时,用 URI 模板变量配置 redirect-uri 特别有用。这样做可以确保在扩展 redirect-uri 时使用 X-Forwarded-* 头信息。

2、自定义授权请求

OAuth2AuthorizationRequestResolver 可以实现的一个主要用例是,在OAuth 2.0授权框架中定义的标准参数之上,用额外的参数来定制授权请求的能力。

例如,OpenID Connect为 Authorization Code Flow 定义了额外的OAuth 2.0请求参数,这些参数是从 OAuth 2.0授权框架 中定义的标准参数延伸出来的。其中一个扩展参数是 prompt 参数。

|--------------------------------------------------------------------------------------------------------|
| prompt 参数是可选的。以空格分隔、区分大小写的ASCII字符串列表,指定授权服务器是否提示终端用户重新认证和同意。定义的值是:none、login、consent 和 select_account。 |

下面的例子显示了如何用一个 Consumer<OAuth2AuthorizationRequest.Builder> 来配置 DefaultOAuth2AuthorizationRequestResolver,该 consumer 通过包括请求参数 prompt=consent 来定制 oauth2Login() 的授权请求。

  • Java

    @Configuration
    @EnableWebSecurity
    public class OAuth2LoginSecurityConfig {

      @Autowired
      private ClientRegistrationRepository clientRegistrationRepository;
    
      @Bean
      public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      	http
      		.authorizeHttpRequests(authorize -> authorize
      			.anyRequest().authenticated()
      		)
      		.oauth2Login(oauth2 -> oauth2
      			.authorizationEndpoint(authorization -> authorization
      				.authorizationRequestResolver(
      					authorizationRequestResolver(this.clientRegistrationRepository)
      				)
      			)
      		);
      	return http.build();
      }
    
      private OAuth2AuthorizationRequestResolver authorizationRequestResolver(
      		ClientRegistrationRepository clientRegistrationRepository) {
    
      	DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver =
      			new DefaultOAuth2AuthorizationRequestResolver(
      					clientRegistrationRepository, "/oauth2/authorization");
      	authorizationRequestResolver.setAuthorizationRequestCustomizer(
      			authorizationRequestCustomizer());
    
      	return  authorizationRequestResolver;
      }
    
      private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
      	return customizer -> customizer
      				.additionalParameters(params -> params.put("prompt", "consent"));
      }
    

    }

对于简单的用例,即额外的请求参数对于特定的提供者总是相同的,你可以直接在 authorization-uri 属性中添加它。

例如,如果请求参数 prompt 对提供者 okta 来说总是同意的,你可以这样配置它。

spring:
  security:
    oauth2:
      client:
        provider:
          okta:
            authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize?prompt=consent

前面的例子显示了在标准参数之上添加自定义参数的常见使用情况。另外,如果你的要求更高级,你可以通过覆盖 OAuth2AuthorizationRequest.authorizationRequestUri 属性来完全控制建立授权请求URI。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| OAuth2AuthorizationRequest.Builder.build() 构建了 OAuth2AuthorizationRequest.authorizationRequestUri,它表示授权请求URI,包括使用 application/x-www-form-urlencoded 格式的所有查询参数。 |

下面的例子显示了前述例子中 authorizationRequestCustomizer() 的一个变化,而是重写了 OAuth2AuthorizationRequest.authorizationRequestUri 属性。

  • Java

    private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
    return customizer -> customizer
    .authorizationRequestUri(uriBuilder -> uriBuilder
    .queryParam("prompt", "consent").build());
    }

3、储存授权请求

AuthorizationRequestRepository 负责 OAuth2AuthorizationRequest 的持久化,从发起授权请求到收到授权响应(回调)的过程。

|-------------------------------------------|
| OAuth2AuthorizationRequest 是用来关联和验证授权响应的。 |

AuthorizationRequestRepository 的默认实现是 HttpSessionOAuth2AuthorizationRequestRepository,它将 OAuth2AuthorizationRequest 存储在 HttpSession 中。

如果你有一个自定义的 AuthorizationRequestRepository 的实现,你可以按如下方式配置它。

  • Java

    @Configuration
    @EnableWebSecurity
    public class OAuth2ClientSecurityConfig {

      @Bean
      public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      	http
      		.oauth2Client(oauth2 -> oauth2
      			.authorizationCodeGrant(codeGrant -> codeGrant
      				.authorizationRequestRepository(this.authorizationRequestRepository())
      				...
      			)
              .oauth2Login(oauth2 -> oauth2
                  .authorizationEndpoint(endpoint -> endpoint
                      .authorizationRequestRepository(this.authorizationRequestRepository())
                      ...
                  )
              ).build();
      }
    
      @Bean
      public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
          return new CustomOAuth2AuthorizationRequestRepository();
      }
    

    }

4、请求 Access Token

|----------------------------------------------|
| 参见授权码授予的 Access Token Request/Response 协议流程。 |

授权码授予的 OAuth2AccessTokenResponseClient 的默认实现是 DefaultAuthorizationCodeTokenResponseClient,它使用 RestOperations 实例在授权服务器的Token端点将授权码交换为Access Token。

DefaultAuthorizationCodeTokenResponseClient 很灵活,因为它可以让你定制令牌请求的预处理和/或令牌响应的后处理。

5、自定义Access Token请求

如果你需要定制令牌请求的预处理,你可以为 DefaultAuthorizationCodeTokenResponseClient.setRequestEntityConverter() 提供一个自定义的转换器 <OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>。默认的实现(OAuth2AuthorizationCodeGrantRequestEntityConverter)建立了一个标准 OAuth 2.0 Access Token 请求 的 RequestEntity 表示。然而,提供一个自定义的 Converter 可以让你扩展标准的令牌请求并添加自定义参数。

为了只定制请求的参数,你可以为 OAuth2AuthorizationCodeGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> 来完全重写随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 如果你喜欢只添加额外的参数,你可以为 OAuth2AuthorizationCodeGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>>,它构造一个聚合的 Converter。 |

|------------------------------------------------------------------------------------------|
| 自定义 Converter 必须返回一个有效的OAuth 2.0 Access Token 请求的 RequestEntity 表示,并能被预期的OAuth 2.0提供商理解。 |

6、定制 Access Token 响应

在另一端,如果你需要定制令牌响应的后期处理,你需要为 DefaultAuthorizationCodeTokenResponseClient.setRestOperations() 提供一个自定义配置的 RestOperations。默认的 RestOperations 配置如下。

  • Java

    RestTemplate restTemplate = new RestTemplate(Arrays.asList(
    new FormHttpMessageConverter(),
    new OAuth2AccessTokenResponseHttpMessageConverter()));

    restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

|--------------------------------------------------------------------------------|
| Spring MVC的 FormHttpMessageConverter 是必需的,因为它是在发送OAuth 2.0 Access Token 请求时使用。 |

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token Response 的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供一个自定义的 Converter<Map<String, Object>, OAuth2AccessTokenResponse>,用于将OAuth2.0 Access Token 响应参数转换为 OAuth2AccessTokenResponse。

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0错误,如 400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 将OAuth2.0错误参数转换为 OAuth2Error。

无论你是定制 DefaultAuthorizationCodeTokenResponseClient 还是提供你自己的 OAuth2AccessTokenResponseClient 的实现,你都需要按以下方式配置它。

Access Token Response Configuration

  • Java

    @Configuration
    @EnableWebSecurity
    public class OAuth2ClientSecurityConfig {

      @Bean
      public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      	http
      		.oauth2Client(oauth2 -> oauth2
      			.authorizationCodeGrant(codeGrant -> codeGrant
      				.accessTokenResponseClient(this.accessTokenResponseClient())
      				...
      			)
      		);
      	return http.build();
      }
    

    }

二、Token 刷新

|------------------------------------|
| 关于刷新Token的进一步细节,请参见 OAuth 2.0授权框架。 |

1、刷新Access Token

|-----------------------------------------------|
| 参见刷新令牌授予的 Access Token Request/Response 协议流程。 |

刷新令牌授予的 OAuth2AccessTokenResponseClient 的默认实现是 DefaultRefreshTokenTokenResponseClient,它在授权服务器的令牌端点刷新Access Token时使用 RestOperations。

DefaultRefreshTokenTokenResponseClient 很灵活,因为它可以让你定制令牌请求的预处理或令牌响应的后处理。

2、定制Access Token请求

如果你需要定制令牌请求的预处理,你可以为 DefaultRefreshTokenTokenResponseClient.setRequestEntityConverter() 提供一个自定义的 Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>>。默认实现(OAuth2RefreshTokenGrantRequestEntityConverter)为标准的 OAuth 2.0 Access Token Request 建立了一个 RequestEntity 表示。然而,提供一个自定义的 Converter 可以让你扩展标准的令牌请求并添加自定义参数。

要想只定制请求的参数,你可以为 OAuth2RefreshTokenGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>> 来完全覆盖随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 如果你喜欢只添加额外的参数,你可以为 OAuth2RefreshTokenGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>>,构建一个聚合的 Converter。 |

|------------------------------------------------------------------------------------------------|
| 自定义 Converter 必须返回一个有效的OAuth 2.0 Access Token Request 的 RequestEntity 表示,并能被预期的OAuth 2.0提供商理解。 |

3、定制Access Token响应

在另一端,如果你需要定制令牌响应的后期处理,你需要为 DefaultRefreshTokenTokenResponseClient.setRestOperations() 提供一个自定义配置的 RestOperations。默认的 RestOperations 配置如下。

  • Java

    RestTemplate restTemplate = new RestTemplate(Arrays.asList(
    new FormHttpMessageConverter(),
    new OAuth2AccessTokenResponseHttpMessageConverter()));

    restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

|------------------------------------------------------------------------------|
| Spring MVC的 FormHttpMessageConverter 是必需的,因为它是在发送OAuth 2.0Access Token请求时使用。 |

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token 响应的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供一个自定义的 Converter<Map<String, Object>, OAuth2AccessTokenResponse>,用于将OAuth2.0 Access Token 响应参数转换为 OAuth2AccessTokenResponse。

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0 错误,如 400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 将 OAuth2.0 错误参数转换为 OAuth2Error。

无论你是定制 DefaultRefreshTokenTokenResponseClient,还是提供你自己的 OAuth2AccessTokenResponseClient 的实现,你都需要按以下方式配置它。

  • Java

    // Customize
    OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient = ...

    OAuth2AuthorizedClientProvider authorizedClientProvider =
    OAuth2AuthorizedClientProviderBuilder.builder()
    .authorizationCode()
    .refreshToken(configurer -> configurer.accessTokenResponseClient(refreshTokenTokenResponseClient))
    .build();

    ...

    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| OAuth2AuthorizedClientProviderBuilder.builder().refreshToken() 配置了一个 RefreshTokenOAuth2AuthorizedClientProvider,这是一个 OAuth2AuthorizedClientProvider 的实现,用于 Refresh Token 的授予。 |

OAuth2RefreshToken 可以选择性地在授权码和密码授予类型的 Access Token 响应中返回。如果 OAuth2AuthorizedClient.getRefreshToken() 可用,而 OAuth2AuthorizedClient.getAccessToken() 已过期,则会由 RefreshTokenOAuth2AuthorizedClientProvider 自动刷新。

三、客户端凭证

|--------------------------------------|
| 请参考 OAuth 2.0授权框架,了解关于客户端凭证授予的进一步细节。 |

1、请求 Access Token

|-----------------------------------|
| 关于客户凭证授予的进一步细节,请参见 OAuth 2.0授权框架。 |

客户凭证授予的 OAuth2AAccessTokenResponseClient 的默认实现是 DefaultClientCredentialsTokenResponseClient,它在授权服务器的令牌端点请求 Access Token 时使用 RestOperations。

DefaultClientCredentialsTokenResponseClient 很灵活,因为它可以让你定制令牌请求的预处理或令牌响应的后处理。

2、自定义 Access Token 请求

如果你需要定制令牌请求的预处理,你可以为 DefaultClientCredentialsTokenResponseClient.setRequestEntityConverter() 提供一个自定义的 Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>>。默认实现(OAuth2ClientCredentialsGrantRequestEntityConverter)为标准的 OAuth 2.0 Access Token 请求 建立了一个 RequestEntity 表示。然而,提供一个自定义的转换器可以让你扩展标准的令牌请求并添加自定义参数。

要只定制请求的参数,你可以为 OAuth2ClientCredentialsGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> 来完全覆盖随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 如果你喜欢只添加额外的参数,你可以为 OAuth2ClientCredentialsGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>>,它构造了一个聚合的 Converter。 |

|------------------------------------------------------------------------------------------|
| 自定义 Converter 必须返回一个有效的OAuth 2.0 Access Token 请求的 RequestEntity 表示,并能被预期的OAuth 2.0提供商理解。 |

3、定制 Access Token 响应

在另一端,如果你需要定制令牌响应的后期处理,你需要为 DefaultClientCredentialsTokenResponseClient.setRestOperations() 提供一个自定义配置的 RestOperations。

默认的 RestOperations 配置如下。

  • Java

  • Kotlin

    RestTemplate restTemplate = new RestTemplate(Arrays.asList(
    new FormHttpMessageConverter(),
    new OAuth2AccessTokenResponseHttpMessageConverter()));

    restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

|--------------------------------------------------------------------------------|
| Spring MVC的 FormHttpMessageConverter 是必需的,因为它是在发送OAuth 2.0 Access Token 请求时使用。 |

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token 响应的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供一个自定义的 Converter<Map<String, Object>, OAuth2AccessTokenResponse>,用于将OAuth2.0 Access Token 响应参数转换为 OAuth2AccessTokenResponse。

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0错误,如 400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 来转换 OAuth2.0 错误参数为 OAuth2Error。

无论你是定制 DefaultClientCredentialsTokenResponseClient,还是提供你自己的 OAuth2AccessTokenResponseClient 的实现,你都需要按以下方式配置它。

  • Java

    // Customize
    OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = ...

    OAuth2AuthorizedClientProvider authorizedClientProvider =
    OAuth2AuthorizedClientProviderBuilder.builder()
    .clientCredentials(configurer -> configurer.accessTokenResponseClient(clientCredentialsTokenResponseClient))
    .build();

    ...

    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| OAuth2AuthorizedClientProviderBuilder.builder().clientCredentials() 配置了一个 ClientCredentialsOAuth2AuthorizedClientProvider,这是一个 OAuth2AuthorizedClientProvider 的实现,用于客户端凭证授予。 |

4、使用 Access Token

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: client_credentials
            scope: read, write
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

进一步考虑以下 OAuth2AuthorizedClientManager @Bean。

  • Java

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
    ClientRegistrationRepository clientRegistrationRepository,
    OAuth2AuthorizedClientRepository authorizedClientRepository) {

      OAuth2AuthorizedClientProvider authorizedClientProvider =
      		OAuth2AuthorizedClientProviderBuilder.builder()
      				.clientCredentials()
      				.build();
    
      DefaultOAuth2AuthorizedClientManager authorizedClientManager =
      		new DefaultOAuth2AuthorizedClientManager(
      				clientRegistrationRepository, authorizedClientRepository);
      authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
    
      return authorizedClientManager;
    

    }

鉴于前面的属性和 Bean,你可以按以下方式获得 OAuth2AccessToken。

  • Java

    @Controller
    public class OAuth2ClientController {

      @Autowired
      private OAuth2AuthorizedClientManager authorizedClientManager;
    
      @GetMapping("/")
      public String index(Authentication authentication,
      					HttpServletRequest servletRequest,
      					HttpServletResponse servletResponse) {
    
      	OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
      			.principal(authentication)
      			.attributes(attrs -> {
      				attrs.put(HttpServletRequest.class.getName(), servletRequest);
      				attrs.put(HttpServletResponse.class.getName(), servletResponse);
      			})
      			.build();
      	OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
    
      	OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
    
      	...
    
      	return "index";
      }
    

    }

|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| HttpServletRequest 和 HttpServletResponse 都是 OPTIONAL (可选)属性。如果没有提供,通过使用 RequestContextHolder.getRequestAttributes(),它们默认为 ServletRequestAttributes。 |

四、资源所有者的密码凭证

|-------------------------------------------|
| 请参阅OAuth 2.0授权框架,了解关于 资源所有者密码凭证 授予的进一步细节。 |

1、请求 Access Token

|----------------------------------------------------|
| 参见资源所有者密码凭证授予的 Access Token Request/Response 协议流程。 |

资源所有者密码凭证授予的 OAuth2AccessTokenResponseClient 的默认实现是 DefaultPasswordTokenResponseClient,它在授权服务器的令牌端点请求 Access Token 时使用 RestOperations。

DefaultPasswordTokenResponseClient 很灵活,因为它可以让你定制令牌请求的预处理或令牌响应的后处理。

2、自定义 Access Token 请求

如果你需要定制令牌请求的预处理,你可以为 DefaultPasswordTokenResponseClient.setRequestEntityConverter() 提供一个自定义的 Converter<OAuth2PasswordGrantRequest, RequestEntity<?>>。默认实现(OAuth2PasswordGrantRequestEntityConverter)为标准的 OAuth 2.0 Access Token 请求 建立了一个 RequestEntity 表示。然而,提供一个自定义的 Converter 可以让你扩展标准的令牌请求并添加自定义参数。

要只定制请求的参数,你可以为 OAuth2PasswordGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> 来完全覆盖随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 如果你喜欢只添加额外的参数,你可以为 OAuth2PasswordGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>,构建一个聚合的 Converter。 |

|------------------------------------------------------------------------------------------|
| 自定义 Converter 必须返回一个有效的OAuth 2.0 Access Token 请求的 RequestEntity 表示,并能被预期的OAuth 2.0提供商理解。 |

3、自定义 Access Token 响应

在另一端,如果你需要定制令牌响应的后期处理,你需要向 DefaultPasswordTokenResponseClient.setRestOperations() 提供一个自定义配置的 RestOperations。默认的 RestOperations 配置如下。

  • Java

    RestTemplate restTemplate = new RestTemplate(Arrays.asList(
    new FormHttpMessageConverter(),
    new OAuth2AccessTokenResponseHttpMessageConverter()));

    restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

|--------------------------------------------------------------------------------|
| Spring MVC的 FormHttpMessageConverter 是必需的,因为它是在发送OAuth 2.0 Access Token 请求时使用。 |

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token Response 的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter() 提供一个自定义的 Converter<Map<String, String>, OAuth2AccessTokenResponse>,用来将OAuth2.0 Access Token 响应参数转换成 OAuth2AccessTokenResponse。

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0错误,如 400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 来转换OAuth2.0错误参数为 OAuth2Error。

无论你是定制 DefaultPasswordTokenResponseClient 还是提供你自己的 OAuth2AccessTokenResponseClient 的实现,你都需要按以下方式配置它。

  • Java

    // Customize
    OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...

    OAuth2AuthorizedClientProvider authorizedClientProvider =
    OAuth2AuthorizedClientProviderBuilder.builder()
    .password(configurer -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
    .refreshToken()
    .build();

    ...

    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| OAuth2AuthorizedClientProviderBuilder.builder().password() 配置了一个 PasswordOAuth2AuthorizedClientProvider,这是一个 OAuth2AuthorizedClientProvider 的实现,用于资源所有者密码凭证授予。 |

4、使用 Access Token

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: password
            scope: read, write
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

进一步考虑 OAuth2AuthorizedClientManager @Bean。

  • Java

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
    ClientRegistrationRepository clientRegistrationRepository,
    OAuth2AuthorizedClientRepository authorizedClientRepository) {

      OAuth2AuthorizedClientProvider authorizedClientProvider =
      		OAuth2AuthorizedClientProviderBuilder.builder()
      				.password()
      				.refreshToken()
      				.build();
    
      DefaultOAuth2AuthorizedClientManager authorizedClientManager =
      		new DefaultOAuth2AuthorizedClientManager(
      				clientRegistrationRepository, authorizedClientRepository);
      authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
    
      // Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
      // map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
      authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
    
      return authorizedClientManager;
    

    }

    private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() {
    return authorizeRequest -> {
    Map<String, Object> contextAttributes = Collections.emptyMap();
    HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
    String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
    String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
    if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
    contextAttributes = new HashMap<>();

      		// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
      		contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
      		contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
      	}
      	return contextAttributes;
      };
    

    }

鉴于前面的属性和Bean,你可以按以下方式获得 OAuth2AccessToken。

  • Java

    @Controller
    public class OAuth2ClientController {

      @Autowired
      private OAuth2AuthorizedClientManager authorizedClientManager;
    
      @GetMapping("/")
      public String index(Authentication authentication,
      					HttpServletRequest servletRequest,
      					HttpServletResponse servletResponse) {
    
      	OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
      			.principal(authentication)
      			.attributes(attrs -> {
      				attrs.put(HttpServletRequest.class.getName(), servletRequest);
      				attrs.put(HttpServletResponse.class.getName(), servletResponse);
      			})
      			.build();
      	OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
    
      	OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
    
      	...
    
      	return "index";
      }
    

    }

|-----------------------------------------------------------------------------------------------------------------------------------------------|
| HttpServletRequest 和 HttpServletResponse 都是 OPTIONAL 属性。如果没有提供,它们默认为 ServletRequestAttributes,使用 RequestContextHolder.getRequestAttributes()。 |

五、JWT Bearer

|------------------------------------------------------------------------|
| 关于 JWT Bearer 授予的进一步细节,请参考OAuth 2.0客户端认证和授权授予的JSON Web Token(JWT)配置文件。 |

1、请求 Access Token

|-----------------------------------------------------|
| 请参考 Access Token Request/Response 协议流程,了解JWT承载器的授予。 |

JWT Bearer 授予的 OAuth2AAccessTokenResponseClient 的默认实现是 DefaultJwtBearerTokenResponseClient,它在授权服务器的令牌端点请求 Access Token 时使用 RestOperations。

DefaultJwtBearerTokenResponseClient 是相当灵活的,因为它允许你定制令牌请求的预处理和/或令牌响应的后处理。

2、自定义 Access Token 请求

如果你需要定制令牌请求的预处理,你可以为 DefaultJwtBearerTokenResponseClient.setRequestEntityConverter() 提供一个自定义的 Converter<JwtBearerGrantRequest, RequestEntity<?>>。默认实现 JwtBearerGrantRequestEntityConverter 为 OAuth 2.0 Access Token Request 建立了一个 RequestEntity 表示。然而,提供一个自定义的 Converter,将允许你扩展令牌请求并添加自定义参数。

为了只定制请求的参数,你可以为 JwtBearerGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<JwtBearerGrantRequest, MultiValueMap<String, String>> 来完全重写随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 如果你喜欢只添加额外的参数,你可以为 JwtBearerGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<JwtBearerGrantRequest, MultiValueMap<String, String>>,它构造一个聚合的 Converter。 |

3、自定义 Access Token 响应

在另一端,如果你需要定制令牌响应的后处理,你需要为 DefaultJwtBearerTokenResponseClient.setRestOperations() 提供一个自定义配置的 RestOperations。默认的 RestOperations 配置如下。

  • Java

    RestTemplate restTemplate = new RestTemplate(Arrays.asList(
    new FormHttpMessageConverter(),
    new OAuth2AccessTokenResponseHttpMessageConverter()));

    restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

|--------------------------------------------------------------------------------|
| Spring MVC的 FormHttpMessageConverter 是必需的,因为它是在发送OAuth 2.0 Access Token 请求时使用。 |

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token 响应的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供一个自定义的 Converter<Map<String, Object>, OAuth2AccessTokenResponse>,用于将OAuth2.0 Access Token 响应参数转换为 OAuth2AccessTokenResponse。

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0的错误,例如:400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 将OAuth2.0错误参数转换为 OAuth2Error。

无论你是定制 DefaultJwtBearerTokenResponseClient 还是提供你自己的 OAuth2AccessTokenResponseClient 的实现,你都需要按照下面的例子来配置它。

  • Java

    // Customize
    OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient = ...

    JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
    jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient);

    OAuth2AuthorizedClientProvider authorizedClientProvider =
    OAuth2AuthorizedClientProviderBuilder.builder()
    .provider(jwtBearerAuthorizedClientProvider)
    .build();

    ...

    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

4、使用 Access Token

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

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: urn:ietf:params:oauth:grant-type:jwt-bearer
            scope: read
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

...​和 OAuth2AuthorizedClientManager @Bean。

  • Java

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
    ClientRegistrationRepository clientRegistrationRepository,
    OAuth2AuthorizedClientRepository authorizedClientRepository) {

      JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
      		new JwtBearerOAuth2AuthorizedClientProvider();
    
      OAuth2AuthorizedClientProvider authorizedClientProvider =
      		OAuth2AuthorizedClientProviderBuilder.builder()
      				.provider(jwtBearerAuthorizedClientProvider)
      				.build();
    
      DefaultOAuth2AuthorizedClientManager authorizedClientManager =
      		new DefaultOAuth2AuthorizedClientManager(
      				clientRegistrationRepository, authorizedClientRepository);
      authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
    
      return authorizedClientManager;
    

    }

你可以通过以下方式获得 OAuth2AccessToken。

  • Java

    @RestController
    public class OAuth2ResourceServerController {

      @Autowired
      private OAuth2AuthorizedClientManager authorizedClientManager;
    
      @GetMapping("/resource")
      public String resource(JwtAuthenticationToken jwtAuthentication) {
      	OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
      			.principal(jwtAuthentication)
      			.build();
      	OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
      	OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
    
      	...
    
      }
    

    }

|----------------------------------------------------------------------------------------------------------------------------------------------------|
| JwtBearerOAuth2AuthorizedClientProvider 默认通过 OAuth2AuthorizationContext.getPrincipal().getPrincipal() 解析Jwt断言,因此在前面的例子中使用了 JwtAuthenticationToken。 |

|------------------------------------------------------------------------------------------------------------------------------------------------|
| 如果你需要从不同的来源解析Jwt断言,你可以为 JwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver() 提供一个自定义 Function<OAuth2AuthorizationContext, Jwt>。 |

相关推荐
尘浮生5 小时前
Java项目实战II基于Spring Boot的宠物商城网站设计与实现
java·开发语言·spring boot·后端·spring·maven·intellij-idea
doc_wei6 小时前
Java小区物业管理系统
java·开发语言·spring boot·spring·毕业设计·课程设计·毕设
荆州克莱6 小时前
杨敏博士:基于法律大模型的智能法律系统
spring boot·spring·spring cloud·css3·技术
自身就是太阳7 小时前
Maven的高级特性
java·开发语言·数据库·后端·spring·maven
胡耀超10 小时前
MyBatis-Plus插入优化:降低IO操作的策略与实践
sql·spring·mybatis
api7713 小时前
1688商品详情API返回值中的售后保障与服务信息
java·服务器·前端·javascript·python·spring·pygame
自身就是太阳14 小时前
深入理解 Spring 事务管理及其配置
java·开发语言·数据库·spring
Aries26315 小时前
Spring事务传播行为详解
java·数据库·spring
陌上少年,且听这风吟15 小时前
【已解决】SpringBoot3项目整合Druid依赖:Druid监控页面404报错
java·spring boot·spring
骆晨学长16 小时前
基于springboot学生健康管理系统的设计与实现
java·开发语言·spring boot·后端·spring