OAuth 2.0 ベアラートークン

ベアラートークンの解決

デフォルトでは、リソースサーバーは Authorization ヘッダーで無記名トークンを探します。ただし、これはいくつかの方法でカスタマイズできます。

カスタムヘッダーからベアラートークンを読み取る

例: カスタムヘッダーから無記名トークンを読み取る必要がある場合があります。これを実現するには、次の例に示すように、DefaultBearerTokenResolver を Bean として公開するか、インスタンスを DSL に接続します。

カスタムベアラートークンヘッダー
  • Java

  • Kotlin

  • XML

@Bean
BearerTokenResolver bearerTokenResolver() {
    DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
    bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION);
    return bearerTokenResolver;
}
@Bean
fun bearerTokenResolver(): BearerTokenResolver {
    val bearerTokenResolver = DefaultBearerTokenResolver()
    bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION)
    return bearerTokenResolver
}
<http>
    <oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver"/>
</http>

<bean id="bearerTokenResolver"
        class="org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver">
    <property name="bearerTokenHeaderName" value="Proxy-Authorization"/>
</bean>

または、プロバイダーがカスタムヘッダーと値の両方を使用している状況では、代わりに HeaderBearerTokenResolver を使用できます。

フォームパラメーターからベアラートークンを読み取る

または、以下に示すように、DefaultBearerTokenResolver を構成することで実行できるフォームパラメーターからトークンを読み取ることもできます。

フォームパラメーターベアラートークン
  • Java

  • Kotlin

  • XML

DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowFormEncodedBodyParameter(true);
http
    .oauth2ResourceServer(oauth2 -> oauth2
        .bearerTokenResolver(resolver)
    );
val resolver = DefaultBearerTokenResolver()
resolver.setAllowFormEncodedBodyParameter(true)
http {
    oauth2ResourceServer {
        bearerTokenResolver = resolver
    }
}
<http>
    <oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver"/>
</http>

<bean id="bearerTokenResolver"
        class="org.springframework.security.oauth2.server.resource.web.HeaderBearerTokenResolver">
    <property name="allowFormEncodedBodyParameter" value="true"/>
</bean>

ベアラートークンの伝播

リソースサーバーがトークンを検証したため、それをダウンストリームサービスに渡すと便利な場合があります。これは ServletBearerExchangeFilterFunction (Javadoc) を使用すると非常に簡単です。これは、次の例で確認できます。

  • Java

  • Kotlin

@Bean
public WebClient rest() {
    return WebClient.builder()
            .filter(new ServletBearerExchangeFilterFunction())
            .build();
}
@Bean
fun rest(): WebClient {
    return WebClient.builder()
            .filter(ServletBearerExchangeFilterFunction())
            .build()
}

上記の WebClient を使用してリクエストを実行すると、Spring Security は現在の Authentication を検索し、AbstractOAuth2Token (Javadoc)  資格情報を抽出します。次に、Authorization ヘッダーでそのトークンを伝搬します。

例:

  • Java

  • Kotlin

this.rest.get()
        .uri("https://other-service.example.com/endpoint")
        .retrieve()
        .bodyToMono(String.class)
        .block()
this.rest.get()
        .uri("https://other-service.example.com/endpoint")
        .retrieve()
        .bodyToMono<String>()
        .block()

other-service.example.com/endpoint (英語) を呼び出して、ベアラートークン Authorization ヘッダーを追加します。

この動作をオーバーライドする必要がある場所では、次のようにヘッダーを自分で指定するだけです。

  • Java

  • Kotlin

this.rest.get()
        .uri("https://other-service.example.com/endpoint")
        .headers(headers -> headers.setBearerAuth(overridingToken))
        .retrieve()
        .bodyToMono(String.class)
        .block()
this.rest.get()
        .uri("https://other-service.example.com/endpoint")
        .headers{  headers -> headers.setBearerAuth(overridingToken)}
        .retrieve()
        .bodyToMono<String>()
        .block()

この場合、フィルターはフォールバックし、リクエストを Web フィルターチェーンの残りの部分に単純に転送します。

OAuth 2.0 クライアントフィルター機能 (Javadoc) とは異なり、このフィルター関数は、トークンが期限切れになった場合、トークンを更新しようとしません。このレベルのサポートを取得するには、OAuth 2.0 クライアントフィルターを使用してください。

RestTemplate サポート

現在のところ、ServletBearerExchangeFilterFunction に相当する RestTemplate はありませんが、独自のインターセプターを使用して、リクエストのベアラートークンを非常に簡単に伝達できます。

  • Java

  • Kotlin

@Bean
RestTemplate rest() {
	RestTemplate rest = new RestTemplate();
	rest.getInterceptors().add((request, body, execution) -> {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if (authentication == null) {
			return execution.execute(request, body);
		}

		if (!(authentication.getCredentials() instanceof AbstractOAuth2Token)) {
			return execution.execute(request, body);
		}

		AbstractOAuth2Token token = (AbstractOAuth2Token) authentication.getCredentials();
	    request.getHeaders().setBearerAuth(token.getTokenValue());
	    return execution.execute(request, body);
	});
	return rest;
}
@Bean
fun rest(): RestTemplate {
    val rest = RestTemplate()
    rest.interceptors.add(ClientHttpRequestInterceptor { request, body, execution ->
        val authentication: Authentication? = SecurityContextHolder.getContext().authentication
        if (authentication == null) {
            return execution.execute(request, body)
        }

        if (authentication.credentials !is AbstractOAuth2Token) {
            return execution.execute(request, body)
        }

        request.headers.setBearerAuth(authentication.credentials.tokenValue)
        execution.execute(request, body)
    })
    return rest
}
OAuth 2.0 承認済みクライアントマネージャー (Javadoc) とは異なり、このフィルターインターセプターは、トークンの有効期限が切れた場合にトークンを更新しようとしません。このレベルのサポートを取得するには、OAuth 2.0 承認済みクライアントマネージャーを使用してインターセプターを作成してください。

ベアラートークンエラー

ベアラートークンは、いくつかの理由で無効になる場合があります。例: トークンはアクティブではない可能性があります。

このような状況では、リソースサーバーは InvalidBearerTokenException をスローします。他の例外と同様に、これにより OAuth 2.0 ベアラートークンエラーレスポンスが発生します。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error_code="invalid_token", error_description="Unsupported algorithm of none", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"

さらに、AuthenticationFailureBadCredentialsEvent として公開されており、次のようにアプリケーションでリッスンできます。

  • Java

  • Kotlin

@Component
public class FailureEvents {
	@EventListener
    public void onFailure(AuthenticationFailureBadCredentialsEvent badCredentials) {
		if (badCredentials.getAuthentication() instanceof BearerTokenAuthenticationToken) {
		    // ... handle
        }
    }
}
@Component
class FailureEvents {
    @EventListener
    fun onFailure(badCredentials: AuthenticationFailureBadCredentialsEvent) {
        if (badCredentials.authentication is BearerTokenAuthenticationToken) {
            // ... handle
        }
    }
}