1. 概要

OAuth2 APIが標準から少し異なる場合があります。その場合、標準の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) {
        // ...
    }

}

次のセクションで説明するように、後でメソッド 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は必要に応じて変更できます。

手始めに、 承認リクエストごとに標準パラメータを変更できます。

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

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

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

OAuth2AuthorizationRequestadditionalParameters()メソッドを使用し、 Map:を渡すことで、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();
}

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

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

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

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

IDプロバイダーはデフォルトで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 をカスタマイズすることで、トークンリクエストをカスタマイズできます。

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

カスタム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;
    }
}

tokenEndpoint()。accessTokenResponseClient()。を使用して、独自のOAuth2AccessTokenResponseClientを挿入できます。

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

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

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

次に、カスタム 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());
    }

}

私たちのコンバータ変換します 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();
    }

}

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

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

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

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

最後に、LinkedInトークンの応答を処理する方法を見てみましょう。 これには、 access_tokenexpires_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から入手できます。