Spring Security 5.1クライアントでの承認とトークン要求のカスタマイズ

Spring Security 5.1クライアントでの承認とトークンリクエストのカスタマイズ

1. 概要

OAuth2 APIが標準と少し異なる場合があります。その場合、標準のOAuth2リクエストをカスタマイズする必要があります。

Spring Security 5.1は、OAuth2認証とトークンリクエストのカスタマイズをサポートします。

このチュートリアルでは、リクエストパラメータとレスポンス処理をカスタマイズする方法を説明します。

2. カスタム承認リクエスト

まず、OAuth2認証リクエストをカスタマイズします。 必要に応じて、標準パラメーターを変更し、承認リクエストに追加パラメーターを追加できます。

そのためには、we need to implement our own 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を実装したら、それをセキュリティ構成に追加する必要があります。

@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は必要なだけ変更できます。

手始めに、we can modify a standard parameter for each authorization request.

たとえば、独自の“state”パラメータを生成できます。

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

4. 承認リクエストの追加パラメータ

OAuth2AuthorizationRequestadditionalParameters()メソッドを使用し、Map:を渡すWe can also add extra parameters to our OAuth2AuthorizationRequest

private OAuth2AuthorizationRequest customizeAuthorizationRequest(
  OAuth2AuthorizationRequest req) {
    Map extraParams = new HashMap();
    extraParams.putAll(req.getAdditionalParameters());
    extraParams.put("test", "extra");

    return OAuth2AuthorizationRequest
      .from(req)
      .additionalParameters(extraParams)
      .build();
}

また、新しいadditionalParametersを追加する前に、古いadditionalParametersが含まれていることを確認する必要があります。

Okta Authorization Serverで使用される承認リクエストをカスタマイズして、より実用的な例を見てみましょう。

4.1. カスタムOkta承認リクエスト

Oktaには、ユーザーにより多くの機能を提供するための承認リクエスト用の追加のオプションパラメータがあります。 たとえば、IDプロバイダーを示すidp

IDプロバイダーはデフォルトでOktaですが、idpパラメーターを使用してカスタマイズできます。

private OAuth2AuthorizationRequest customizeOktaReq(OAuth2AuthorizationRequest req) {
    Map extraParams = new HashMap();
    extraParams.putAll(req.getAdditionalParameters());
    extraParams.put("idp", "https://idprovider.com");
    return OAuth2AuthorizationRequest
      .from(req)
      .additionalParameters(extraParams)
      .build();
}

5. カスタムトークンリクエスト

次に、OAuth2トークンリクエストをカスタマイズする方法を説明します。

OAuth2AccessTokenResponseClientをカスタマイズすることで、トークンリクエストをカスタマイズできます。

OAuth2AccessTokenResponseClientのデフォルトの実装はDefaultAuthorizationCodeTokenResponseClientです。

カスタムRequestEntityConverterを提供することでトークン要求自体をカスタマイズできます。また、DefaultAuthorizationCodeTokenResponseClientRestOperationsをカスタマイズすることでトークン応答の処理をカスタマイズすることもできます。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.tokenEndpoint()
          .accessTokenResponseClient(accessTokenResponseClient())
            //...
    }

    @Bean
    public OAuth2AccessTokenResponseClient 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;
    }
}

tokenEndpoint().accessTokenResponseClient().を使用して独自のOAuth2AccessTokenResponseClient を注入できます

トークンリクエストパラメータをカスタマイズするには、CustomRequestEntityConverter.を実装します。同様に、トークンレスポンスの処理をカスタマイズするには、CustomTokenResponseConverter.を実装します。

次のセクションでは、CustomRequestEntityConverterCustomTokenResponseConverterの両方について説明します。

6. トークンリクエストの追加パラメータ

次に、カスタムConverterを作成して、トークンリクエストにパラメータを追加する方法を説明します。

public class CustomRequestEntityConverter implements
  Converter> {

    private OAuth2AuthorizationCodeGrantRequestEntityConverter defaultConverter;

    public CustomRequestEntityConverter() {
        defaultConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
    }

    @Override
    public RequestEntity convert(OAuth2AuthorizationCodeGrantRequest req) {
        RequestEntity entity = defaultConverter.convert(req);
        MultiValueMap params = (MultiValueMap) entity.getBody();
        params.add("test2", "extra2");
        return new RequestEntity<>(params, entity.getHeaders(),
          entity.getMethod(), entity.getUrl());
    }

}

ConverterOAuth2AuthorizationCodeGrantRequestRequestEntity. に変換します

デフォルトのコンバーターOAuth2AuthorizationCodeGrantRequestEntityConverterを使用して基本機能を提供し、RequestEntityの本体にパラメーターを追加しました。

7. カスタムトークン応答処理

次に、トークン応答の処理をカスタマイズします。

デフォルトのトークン応答コンバーターOAuth2AccessTokenResponseHttpMessageConverterを開始点として使用できます。

“scope”パラメータを異なる方法で処理するためにCustomTokenResponseConverterを実装します。

public class CustomTokenResponseConverter implements
  Converter, OAuth2AccessTokenResponse> {
    private static final Set 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 tokenResponseParameters) {
        String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);

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

}

トークン応答コンバーターは、MapOAuth2AccessTokenResponse.に変換します

この例では、“scope”パラメーターをスペース区切りのString.ではなくコンマ区切りとして解析しました。

LinkedInを認証サーバーとして使用してトークン応答をカスタマイズすることにより、別の実用的な例を見てみましょう。

7.1. LinkedInトークンの応答処理

最後に、LinkedInトークン応答を処理する方法を見てみましょう。 これにはaccess_tokenexpires_in,のみが含まれますが、token_type.も必要です。

独自のトークン応答コンバーターを実装し、token_typeを手動で設定するだけです。

public class LinkedinTokenResponseConverter
  implements Converter, OAuth2AccessTokenResponse> {

    @Override
    public OAuth2AccessTokenResponse convert(Map 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の承認とトークンリクエストをカスタマイズする方法を学びました。

例の完全なソースコードは、over on GitHubで入手できます。