Перейти к основному содержимому

Настройка запросов авторизации и токенов с помощью клиента Spring Security 5.1

· 5 мин. чтения

1. Обзор

Иногда API-интерфейсы OAuth2 могут немного отличаться от стандартных, и в этом случае нам необходимо внести некоторые изменения в стандартные запросы OAuth2.

Spring Security 5.1 поддерживает настройку авторизации OAuth2 и запросов токенов.

В этом руководстве мы увидим, как настроить параметры запроса и обработку ответов.

2. Пользовательский запрос авторизации

Во-первых, мы настроим запрос авторизации 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) {
// ...
}

}

Мы добавим наши настройки позже, используя наш метод customAuthorizationRequest() , как мы обсудим в следующих разделах.

После реализации нашего пользовательского OAuth2AuthorizationRequestResolver нам нужно добавить его в нашу конфигурацию безопасности:

@Configuration
public 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.

3. Настройка стандартных параметров запроса авторизации

Теперь давайте обсудим фактическую настройку. Мы можем изменить OAuth2AuthorizationRequest столько, сколько захотим.

Для начала мы можем изменить стандартный параметр для каждого запроса авторизации.

Мы можем, например, сгенерировать собственный параметр «состояние» :

private OAuth2AuthorizationRequest customizeAuthorizationRequest(
OAuth2AuthorizationRequest req) {
return OAuth2AuthorizationRequest
.from(req).state("xyz").build();
}

** 4. Дополнительные параметры ** запроса авторизации ****

Мы также можем добавить дополнительные параметры в наш запрос OAuth2AuthorizationRequest, используя метод AdditionalParameters () запроса OAuth2AuthorizationRequest и передав карту:

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();
}

Мы также должны убедиться, что мы включили старые дополнительные параметры , прежде чем добавлять новые.

Давайте рассмотрим более практический пример, настроив запрос на авторизацию, используемый с сервером авторизации Okta.

4.1. Пользовательский запрос авторизации 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();
}

5. Пользовательский запрос токена

Теперь мы увидим, как настроить запрос токена OAuth2.

Мы можем настроить запрос токена, настроив OAuth2AccessTokenResponseClient .

Реализация по умолчанию для OAuth2AccessTokenResponseClientDefaultAuthorizationCodeTokenResponseClient .

Мы можем настроить сам запрос токена, предоставив пользовательский RequestEntityConverter , и мы можем даже настроить обработку ответа токена, настроив DefaultAuthorizationCodeTokenResponseClient RestOperations :

@Configuration
public 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, используя tokenEndpoint().accessTokenResponseClient().

Чтобы настроить параметры запроса токена, мы реализуем CustomRequestEntityConverter. Точно так же, чтобы настроить обработку ответа токена, мы реализуем CustomTokenResponseConverter.

Мы обсудим как CustomRequestEntityConverter, так и CustomTokenResponseConverter в следующих разделах.

6. Дополнительные параметры запроса токена

Теперь мы увидим, как добавить дополнительные параметры к нашему запросу токена, создав собственный конвертер :

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());
}

}

Наш конвертер преобразует OAuth2AuthorizationCodeGrantRequest в RequestEntity.

Мы использовали преобразователь по умолчанию OAuth2AuthorizationCodeGrantRequestEntityConverter для обеспечения базовой функциональности и добавили дополнительные параметры в тело RequestEntity .

7. Обработка ответа пользовательского токена

Теперь мы настроим обработку ответа токена.

В качестве отправной точки мы можем использовать преобразователь ответа токена по умолчанию 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» как строку с разделителями-запятыми, а не с разделителями-пробелами .

Давайте рассмотрим еще один практический пример, настроив ответ токена, используя LinkedIn в качестве сервера авторизации.

7.1. Обработка ответов на токены 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();
}
}

8. Заключение

В этой статье мы узнали, как настроить авторизацию OAuth2 и запросы токенов, добавив или изменив параметры запроса.

Полный исходный код примеров доступен на GitHub .