我正在开发一个用 Spring Boot (3.2.3) 和 Spring-Security (6.2.2) 编写的应用程序,该应用程序通过 REST 连接到外部服务(使用 Open Feign Client 实现)并使用 Bearer JWT 令牌进行身份验证。
用于外部服务的 JWT 令牌必须使用 OAuth2 获取。
外部服务的文档中解释了如何使用Postman获取jwt token,如下:
使用 Postman 我成功检索了 JWT 令牌。 但是,我不确定如何用 Spring 完全实现这一点。
我的设置如下: 应用程序.yaml
spring:
security:
oauth2:
client:
registration:
provider-name:
client-id: ...
client-secret: ...
authorization-grant-type: authorization_code
redirect-uri: "https://localhost:8080/a/token"
provider:
provider-name:
authorization-uri: "https://provider-name/authorize"
token-uri: "https://provider-name/token"
我的安全配置看起来像这样:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/a/token").permitAll();
})
.oauth2Login(oauth2 ->
oauth2.authorizationEndpoint(endpoint ->
endpoint.authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository))))
.formLogin(withDefaults())
.build();
}
其中 CustomAuthorizationRequestResolver 是:
@Component
public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
private final OAuth2AuthorizationRequestResolver defaultResolver;
public CustomAuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/oauth2/authorize");
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
OAuth2AuthorizationRequest authorizationRequest = this.defaultResolver.resolve(request);
if (authorizationRequest != null) {
authorizationRequest = customizeAuthorizationRequest(authorizationRequest);
}
return authorizationRequest;
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
OAuth2AuthorizationRequest authorizationRequest = this.defaultResolver.resolve(request, clientRegistrationId);
if (authorizationRequest != null) {
authorizationRequest = customizeAuthorizationRequest(authorizationRequest);
}
return authorizationRequest;
}
private OAuth2AuthorizationRequest customizeAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest) {
if (!authorizationRequest.getAuthorizationUri().contains("provider-name")) {
return authorizationRequest;
}
return OAuth2AuthorizationRequest.from(authorizationRequest)
.additionalParameters(Map.of("token_content_type", "jwt"))
.build();
}
}
这对于设置身份验证请求的查询参数效果很好。
当我调用我的应用程序端点: https://localhost:8080/oauth2/authorize/provider-name 时,请求会正确转发给我的提供商,并且我会收到对 /a/token 的响应,其中的代码根据我的理解,我必须将其发送回 /token 端点。
但是,我不知道如何在Token Request的请求体上发送相同的键值对(token_conent_type/jwt)。 Spring Security 不应该这样做吗?如何拦截并附加我的自定义请求正文参数?
有什么建议吗?
谢谢!!
首先,Spring 似乎希望重定向 uri 定义为:
"{baseUrl}/login/oauth2/code/provider-name"
。
因为我发现不同,框架无法拦截收到的身份验证代码并调用令牌端点。
其次,我做了以下改进:
@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(new CustomRequestEntityConverter());
return accessTokenResponseClient;
}
其中 CustomRequestEntityConverter 是:
public class CustomRequestEntityConverter implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {
private OAuth2AuthorizationCodeGrantRequestEntityConverter defaultConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
RequestEntity<?> defaultRequestEntity = defaultConverter.convert(authorizationCodeGrantRequest);
if (defaultRequestEntity == null) {
return null;
}
HttpHeaders headers = defaultRequestEntity.getHeaders();
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
if (defaultRequestEntity.getBody() instanceof MultiValueMap) {
formParameters.putAll((MultiValueMap<String, String>) defaultRequestEntity.getBody());
}
formParameters.add("token_content_type", "jwt");
return new RequestEntity<>(formParameters, headers, defaultRequestEntity.getMethod(), defaultRequestEntity.getUrl());
}
}
并将其链接到安全配置,如下所示:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.oauth2Login(oauth2 ->
oauth2.authorizationEndpoint(endpoint ->
endpoint.authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository)))
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenResponseClient(accessTokenResponseClient))
)
.build();
}
就是这样。 快乐编码!