原文链接:https://www.baeldung.com/spring-security-custom-oauth-requests
作者:baeldung
译者:Darren Luo
有时 OAuth2 API 可能与标准有一些不同,在这种情况家,我们需要对标准 OAuth2 请求进行一些自定义。
Spring Security 5.1 支持自定义 OAuth2 授权和令牌请求。
在本教程,我们将了解人如何自定义请求参数和相应处理。
首先,我们自定义 OAuth2 授权请求。我们可以根据需要修改标准参数并添加额外的参数到授权请求中。
为此,我们需要实现我们自己的 OAuth2AuthorizationRequestResolver:
public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { private OAuth2AuthorizationRequestResolver defaultResolver; public CustomAuthorizationRequestResolver(
ClientRegistrationRepository repo, String authorizationRequestBaseUri) {
defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(repo, authorizationRequestBaseUri);
} // ...}
请注意,我们使用 DefaultOAuth2AuthorizationRequestResolver 来提供基本功能。
我们还将覆盖 resolve() 方法来添加我们自定义逻辑:
public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { //...
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
OAuth2AuthorizationRequest req = defaultResolver.resolve(request); if(req != null) {
req = customizeAuthorizationRequest(req);
} return req;
} @Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
OAuth2AuthorizationRequest req = defaultResolver.resolve(request, clientRegistrationId); if(req != null) {
req = customizeAuthorizationRequest(req);
} return req;
} private OAuth2AuthorizationRequest customizeAuthorizationRequest(
OAuth2AuthorizationRequest req) { // ...
}
}
我们稍后将使用我们的 customizeAuthorizationRequest() 方法添加我们的自定义,我们将在下一节中讨论。
实现我们的自定义 OAuth2AuthorizationRequestResolver 后,我们需要将其添加到我们的安全配置:
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login()
.authorizationEndpoint()
.authorizationRequestResolver( new CustomAuthorizationRequestResolver(
clientRegistrationRepository(), "/oauth2/authorize-client")) //...
}
}
这里我们使用 oauth2Login().authorizationEndpoint().authorizationRequestResolver() 来注入我们的自定义OAuth2AuthorizationRequestResolver。
现在,我们来讨论世纪的自定义。我们可以根据需要修改 OAuth2AuthorizationRequest。
对于初学者,我们可以修改每个授权请求的标准参数。
例如,我们可以生成我们自己的“state”参数:
private OAuth2AuthorizationRequest customizeAuthorizationRequest(
OAuth2AuthorizationRequest req) { return OAuth2AuthorizationRequest
.from(req).state("xyz").build();
}
我们也可以添加额外的参数到我们的 OAuth2AuthorizationRequest ,使用 OAuth2AuthorizationRequest 的 additionalParameters() 方法并传入一个 Map:
private OAuth2AuthorizationRequest customizeAuthorizationRequest(
OAuth2AuthorizationRequest req) {
Map<String,Object> extraParams = new HashMap<String,Object>();
extraParams.putAll(req.getAdditionalParameters());
extraParams.put("test", "extra"); return OAuth2AuthorizationRequest
.from(req)
.additionalParameters(extraParams)
.build();
}
我们添加我们新的参数之前,还必须保证我们包含旧的 additionalParameters。
让我们通过为 Okta 授权服务自定义授权请求来查看更实际的示例。
Okta 为授权请求提供了额外的可选参数,以便为用户提供更多功能。例如,idp 表明身份提供者。
默认情况下,身份提供者是 Okta,但我们可以使用 idp 参数自定义它:
private OAuth2AuthorizationRequest customizeOktaReq(OAuth2AuthorizationRequest req) {
Map<String,Object> extraParams = new HashMap<String,Object>();
extraParams.putAll(req.getAdditionalParameters());
extraParams.put("idp", "https://idprovider.com"); return OAuth2AuthorizationRequest
.from(req)
.additionalParameters(extraParams)
.build();
}
现在,我们将了解如何自定义 OAuth2 令牌请求。
我们可以通过自定义 OAuth2AccessTokenResponseClient 自定义令牌请求。
OAuth2AccessTokenResponseClient 的默认实现是 DefaultAuthorizationCodeTokenResponseClient。
我们可以通过提供一个自定义 RequestEntityConverter 来自定义令牌请求本身,我们甚至可以通过自定义 DefaultAuthorizationCodeTokenResponseClient RestOperations 来自定义令牌响应处理:
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception {
http.tokenEndpoint()
.accessTokenResponseClient(accessTokenResponseClient()) //...
} @Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient(){
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(new CustomRequestEntityConverter());
OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
tokenResponseHttpMessageConverter.setTokenResponseConverter(new CustomTokenResponseConverter());
RestTemplate restTemplate = new RestTemplate(Arrays.asList( new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
accessTokenResponseClient.setRestOperations(restTemplate);
return accessTokenResponseClient;
}
}
我们可以注入我们自己的 OAuth2AccessTokenResponseClient using tokenEndpoint().accessTokenResponseClient()。
要自定义令牌请求参数,我们将实现 CustomRequestEntityConverter。同样,为了自定义处理令牌响应,我们将实现 CustomTokenResponseConverter。
我们将在接下来的部分讨论 CustomRequestEntityConverter 和 CustomTokenResponseConverter。
现在,我们将看到如何通过构建自定义 Converter 来添加额外的参数到我们的令牌请求:
public class CustomRequestEntityConverter implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> { private OAuth2AuthorizationCodeGrantRequestEntityConverter defaultConverter; public CustomRequestEntityConverter() {
defaultConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
} @Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest req) {
RequestEntity<?> entity = defaultConverter.convert(req);
MultiValueMap<String, String> params = (MultiValueMap<String,String>) entity.getBody();
params.add("test2", "extra2"); return new RequestEntity<>(params, entity.getHeaders(),
entity.getMethod(), entity.getUrl());
}
}
我们的 Converter 将 OAuth2AuthorizationCodeGrantRequest 转换为 RequestEntity。
我们使用默认转换器 OAuth2AuthorizationCodeGrantRequestEntityConverter 来提供基本功能,并向 RequestEntity 添加了额外的参数。
现在,我们将自定义处理令牌响应。
我们可以使用默认令牌响应转换器 OAuth2AccessTokenResponseHttpMessageConverter 作为起点。
我们将实现 CustomTokenResponseConverter 以不同方式处理“scope”参数:
public class CustomTokenResponseConverter implements Converter<Map<String, String>, OAuth2AccessTokenResponse> { private static final Set<String> TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of(
OAuth2ParameterNames.ACCESS_TOKEN,
OAuth2ParameterNames.TOKEN_TYPE,
OAuth2ParameterNames.EXPIRES_IN,
OAuth2ParameterNames.REFRESH_TOKEN,
OAuth2ParameterNames.SCOPE).collect(Collectors.toSet()); @Override
public OAuth2AccessTokenResponse convert(Map<String, String> tokenResponseParameters) {
String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);
Set<String> scopes = Collections.emptySet(); if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE);
scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, ","))
.collect(Collectors.toSet());
} //...
return OAuth2AccessTokenResponse.withToken(accessToken)
.tokenType(accessTokenType)
.expiresIn(expiresIn)
.scopes(scopes)
.refreshToken(refreshToken)
.additionalParameters(additionalParameters)
.build();
}
}
令牌响应转换器将 Map 转换为 OAuth2AccessTokenResponse。
在此示例中,我们将“scope”参数解析为逗号分割而不是空格风格的 String。
让我们查看另一个通过使用 LinkedIn 作为授权服务器自定义令牌响应的示例。
最后,让我们看看如何处理 LinkedIn 令牌响应。它只包含了 access_token 和 expires_in,但我们还需要 token_type。
我们可以简单的实现我们的令牌响应转换器并手动设置 token_type:
public class LinkedinTokenResponseConverter
implements Converter<Map<String, String>, OAuth2AccessTokenResponse> { @Override
public OAuth2AccessTokenResponse convert(Map<String, String> tokenResponseParameters) {
String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN); long expiresIn = Long.valueOf(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN));
OAuth2AccessToken.TokenType accessTokenType = OAuth2AccessToken.TokenType.BEARER; return OAuth2AccessTokenResponse.withToken(accessToken)
.tokenType(accessTokenType)
.expiresIn(expiresIn)
.build();
}
}
在本文,我们学习了如何通过添加或修改请求参数来自定义 OAuth2 授权和令牌请求。
GitHub(https://github.com/eugenp/tutorials/tree/master/spring-5-security-oauth) 上提供了这些示例的完整源代码。
本文分享自 A周立SpringCloud 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!