OAuth 2.0 リソースサーバー JWT
JWT の最小依存関係
ほとんどのリソースサーバーサポートは spring-security-oauth2-resource-server
に収集されます。ただし、JWT のデコードと検証のサポートは spring-security-oauth2-jose
にあります。つまり、JWT でエンコードされたベアラートークンをサポートする作業リソースサーバーを使用するには両方が必要です。
JWT の最小構成
Spring Boot を使用する場合、アプリケーションをリソースサーバーとして構成するには、2 つの基本的な手順が必要です。最初に、必要な依存関係を含め、2 番目に認可サーバーの場所を示します。
認可サーバーの指定
Spring Boot アプリケーションで、使用する認可サーバーを指定するには、次のようにします。
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/issuer
ここで、idp.example.com/issuer (英語)
は、認可サーバーが発行する JWT トークンの iss
クレームに含まれる値です。リソースサーバーは、このプロパティを使用して、さらに自己構成を行い、認可サーバーの公開鍵を検出し、受信 JWT を検証します。
issuer-uri プロパティを使用するには、idp.example.com/issuer/.well-known/openid-configuration (英語) 、idp.example.com/.well-known/openid-configuration/issuer (英語) 、idp.example.com/.well-known/oauth-authorization-server/issuer (英語) のいずれかが認可サーバーでサポートされているエンドポイントであることも真である必要があります。このエンドポイントは、プロバイダー構成 (英語) エンドポイントまたは認可サーバーのメタデータ [IETF] (英語) エンドポイントと呼ばれます。 |
以上です!
スタートアップの期待
このプロパティとこれらの依存関係を使用すると、リソースサーバーは自動的に JWT エンコードされたベアラートークンを検証するように自身を構成します。
これは、決定論的な起動プロセスを通じてこれを実現します。
プロバイダー構成または認可サーバーのメタデータエンドポイントに
jwks_url
プロパティを照会しますサポートされているアルゴリズムについて
jwks_url
エンドポイントを照会します見つかったアルゴリズムの有効な公開鍵を
jwks_url
に照会するように検証戦略を構成しますidp.example.com (英語)
に対して各 JWTiss
クレームを検証する検証戦略を構成します。
このプロセスの結果、リソースサーバーが正常に起動するには、認可サーバーが起動してリクエストを受信する必要があります。
リソースサーバーがクエリを実行したときに認可サーバーがダウンした場合(適切なタイムアウトが与えられた場合)、起動は失敗します。 |
ランタイムの期待
アプリケーションが起動すると、リソースサーバーは Authorization: Bearer
ヘッダーを含むリクエストの処理を試みます。
GET / HTTP/1.1
Authorization: Bearer some-token-value # Resource Server will process this
このスキームが示されている限り、リソースサーバーはベアラートークン仕様に従ってリクエストの処理を試みます。
整形式の JWT が与えられると、リソースサーバーは次のことを行います。
起動時に
jwks_url
エンドポイントから取得され、JWT と照合される公開鍵に対して署名を検証しますJWT の
exp
およびnbf
タイムスタンプと、JWT のiss
クレームを検証します。各スコープを接頭辞
SCOPE_
を持つオーソリティにマップします。
認可サーバーが新しい鍵を使用できるようになると、Spring Security は JWT の検証に使用される鍵を自動的にローテーションします。 |
デフォルトでは、結果の Authentication#getPrincipal
は Spring Security Jwt
オブジェクトであり、Authentication#getName
は JWT の sub
プロパティ(存在する場合)にマップします。
ここから、次へのジャンプを検討してください。
JWT 認証の仕組み
次に、今見たような、サーブレットベースのアプリケーションで Spring Security が JWT [IETF] (英語) 認証をサポートするために使用するアーキテクチャコンポーネントを見てみましょう。
JwtAuthenticationProvider
(Javadoc) は、JwtDecoder
および JwtAuthenticationConverter
を利用して JWT を認証する AuthenticationProvider
実装です。
JwtAuthenticationProvider
が Spring Security 内でどのように機能するかを見てみましょう。この図は、ベアラートークンの読み取りの図で AuthenticationManager
がどのように機能するかの詳細を説明しています。
JwtAuthenticationProvider
の使用箇所 ベアラートークンの読み取りからの認証 Filter
は、ProviderManager
によって実装される AuthenticationManager
に BearerTokenAuthenticationToken
を渡します。
ProviderManager
は、型 JwtAuthenticationProvider
の AuthenticationProvider を使用するように構成されています。
JwtAuthenticationProvider
は、JwtDecoder
を使用して Jwt
をデコード、検証、検証します。
次に、JwtAuthenticationProvider
は JwtAuthenticationConverter
を使用して、Jwt
を許可された権限の Collection
に変換します。
認証が成功すると、返される Authentication
は型 JwtAuthenticationToken
であり、構成された JwtDecoder
によって返される Jwt
であるプリンシパルを持ちます。最終的に、返された JwtAuthenticationToken
は認証 Filter
によって SecurityContextHolder
に設定されます。
認可サーバー JWK セット Uri を直接指定する
認可サーバーが構成エンドポイントをサポートしていない場合、またはリソースサーバーが認可サーバーから独立して起動できる必要がある場合は、jwk-set-uri
も提供できます。
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com
jwk-set-uri: https://idp.example.com/.well-known/jwks.json
JWK Set uri は標準化されていませんが、通常は認可サーバーのドキュメントに記載されています |
リソースサーバーは起動時に認可サーバーに ping を実行しません。issuer-uri
を引き続き指定して、リソースサーバーが受信 JWT で iss
クレームを検証するようにします。
このプロパティは、DSL で直接指定することもできます。 |
ビューアーを供給する
すでに見たように、issuer-uri
プロパティは iss
クレームを検証します ; これが JWT の送信者です。
Boot には、aud
クレームを検証するための audiences
プロパティもあります。これが JWT の送信先です。
リソースサーバーの対象者は次のように指定できます。
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com
audiences: https://my-resource-server.example.com
必要に応じて、aud 検証をプログラムで追加することもできます。 |
その結果、JWT の iss
クレームが idp.example.com (英語)
ではなく、その aud
クレームのリストに my-resource-server.example.com (英語)
が含まれていない場合、検証は失敗します。
Boot 自動構成のオーバーライドまたは置換
Spring Boot がリソースサーバーに代わって生成する 2 つの @Bean
があります。
1 つ目は、アプリをリソースサーバーとして構成する SecurityFilterChain
です。spring-security-oauth2-jose
を含めると、この SecurityFilterChain
は次のようになります。
Java
Kotlin
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
return http.build()
}
アプリケーションが SecurityFilterChain
Bean を公開しない場合、Spring Boot は上記のデフォルトを公開します。
これを置き換えることは、アプリケーション内で Bean を公開するのと同じくらい簡単です。
Java
Kotlin
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
@Configuration
@EnableWebSecurity
public class MyCustomSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/messages/**").access(hasScope("message:read"))
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(myConverter())
)
);
return http.build();
}
}
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope
@Configuration
@EnableWebSecurity
class MyCustomSecurityConfiguration {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/messages/**", hasScope("message:read"))
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt {
jwtAuthenticationConverter = myConverter()
}
}
}
return http.build()
}
}
上記では、/messages/
で始まる URL の message:read
のスコープが必要です。
oauth2ResourceServer
DSL のメソッドも自動構成をオーバーライドまたは置き換えます。
例: 2 番目の @Bean
Spring Boot が作成する JwtDecoder
は、String
トークンを Jwt
の検証済みインスタンスにデコードします。
Java
Kotlin
@Bean
public JwtDecoder jwtDecoder() {
return JwtDecoders.fromIssuerLocation(issuerUri);
}
@Bean
fun jwtDecoder(): JwtDecoder {
return JwtDecoders.fromIssuerLocation(issuerUri)
}
JwtDecoders# 発行者ロケーション (Javadoc) を呼び出すと、プロバイダー構成または認可サーバーメタデータエンドポイントが呼び出され、JWK セット Uri が導出されます。 |
アプリケーションが JwtDecoder
Bean を公開しない場合、Spring Boot は上記のデフォルトを公開します。
そして、その構成は jwkSetUri()
を使用してオーバーライドするか、decoder()
を使用して置き換えることができます。
または、Spring Boot をまったく使用していない場合は、これらのコンポーネント(フィルターチェーンと JwtDecoder
)の両方を XML で指定できます。
フィルターチェーンは次のように指定されます。
XML
<http>
<intercept-uri pattern="/**" access="authenticated"/>
<oauth2-resource-server>
<jwt decoder-ref="jwtDecoder"/>
</oauth2-resource-server>
</http>
JwtDecoder
は次のようになります。
XML
<bean id="jwtDecoder"
class="org.springframework.security.oauth2.jwt.JwtDecoders"
factory-method="fromIssuerLocation">
<constructor-arg value="${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}"/>
</bean>
jwkSetUri()
を使用する
認可サーバーの JWK Set Uri は、構成プロパティとして構成することも、DSL で提供することもできます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class DirectlyConfiguredJwkSetUri {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwkSetUri("https://idp.example.com/.well-known/jwks.json")
)
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class DirectlyConfiguredJwkSetUri {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt {
jwkSetUri = "https://idp.example.com/.well-known/jwks.json"
}
}
}
return http.build()
}
}
<http>
<intercept-uri pattern="/**" access="authenticated"/>
<oauth2-resource-server>
<jwt jwk-set-uri="https://idp.example.com/.well-known/jwks.json"/>
</oauth2-resource-server>
</http>
jwkSetUri()
の使用は、構成プロパティよりも優先されます。
decoder()
を使用する
jwkSetUri()
よりも強力なのは decoder()
で、これは JwtDecoder
の Boot 自動構成を完全に置き換えます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class DirectlyConfiguredJwtDecoder {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.decoder(myCustomDecoder())
)
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class DirectlyConfiguredJwtDecoder {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt {
jwtDecoder = myCustomDecoder()
}
}
}
return http.build()
}
}
<http>
<intercept-uri pattern="/**" access="authenticated"/>
<oauth2-resource-server>
<jwt decoder-ref="myCustomDecoder"/>
</oauth2-resource-server>
</http>
これは、validation、mapping、または request timeouts のようなより詳細な構成が必要な場合に便利です。
JwtDecoder
の公開 @Bean
または、JwtDecoder
@Bean
を公開すると、decoder()
と同じ効果があります。次のように jwkSetUri
を使用して構築できます。
Java
Kotlin
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
}
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()
}
または、次のように、発行者を使用して、build()
が呼び出されたときに NimbusJwtDecoder
に jwkSetUri
を検索させることもできます。
Java
Kotlin
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(issuer).build();
}
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(issuer).build()
}
または、デフォルトで問題がなければ、JwtDecoders
を使用することもできます。これは、デコーダーのバリデーターの構成に加えて上記を行います。
Java
Kotlin
@Bean
public JwtDecoders jwtDecoder() {
return JwtDecoders.fromIssuerLocation(issuer);
}
@Bean
fun jwtDecoder(): JwtDecoders {
return JwtDecoders.fromIssuerLocation(issuer)
}
信頼できるアルゴリズムの構成
デフォルトでは、NimbusJwtDecoder
、リソースサーバーは、RS256
を使用したトークンのみを信頼および検証します。
これは、Spring Boot、NimbusJwtDecoder ビルダー、または JWK セットレスポンスからカスタマイズできます。
Spring Boot 経由
アルゴリズムを設定する最も簡単な方法は、プロパティとしてです:
spring:
security:
oauth2:
resourceserver:
jwt:
jws-algorithms: RS512
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
ビルダーを使用する
ただし、より強力にするには、NimbusJwtDecoder
に同梱されているビルダーを使用できます。
Java
Kotlin
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
.jwsAlgorithm(RS512).build();
}
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
.jwsAlgorithm(RS512).build()
}
jwsAlgorithm
を複数回呼び出すと、NimbusJwtDecoder
は次のように複数のアルゴリズムを信頼するように構成されます。
Java
Kotlin
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
}
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
}
または、jwsAlgorithms
を呼び出すことができます。
Java
Kotlin
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
.jwsAlgorithms(algorithms -> {
algorithms.add(RS512);
algorithms.add(ES512);
}).build();
}
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
.jwsAlgorithms {
it.add(RS512)
it.add(ES512)
}.build()
}
JWK Set レスポンスから
Spring Security の JWT サポートは Nimbus に基づいているため、その優れた機能もすべて使用できます。
例: Nimbus には、JWK Set URI レスポンスに基づいてアルゴリズムのセットを選択する JWSKeySelector
実装があります。これを使用して、NimbusJwtDecoder
を次のように生成できます。
Java
Kotlin
@Bean
public JwtDecoder jwtDecoder() {
// makes a request to the JWK Set endpoint
JWSKeySelector<SecurityContext> jwsKeySelector =
JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(this.jwkSetUrl);
DefaultJWTProcessor<SecurityContext> jwtProcessor =
new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(jwsKeySelector);
return new NimbusJwtDecoder(jwtProcessor);
}
@Bean
fun jwtDecoder(): JwtDecoder {
// makes a request to the JWK Set endpoint
val jwsKeySelector: JWSKeySelector<SecurityContext> = JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL<SecurityContext>(this.jwkSetUrl)
val jwtProcessor: DefaultJWTProcessor<SecurityContext> = DefaultJWTProcessor()
jwtProcessor.jwsKeySelector = jwsKeySelector
return NimbusJwtDecoder(jwtProcessor)
}
単一の非対称キーを信頼する
JWK Set エンドポイントでリソースサーバーをバッキングするよりも簡単なのは、RSA 公開鍵をハードコードすることです。公開鍵は、Spring Boot またはビルダーを使用するを介して提供できます。
Spring Boot 経由
Spring Boot を介したキーの指定は非常に簡単です。キーの場所は次のように指定できます。
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:my-key.pub
または、より洗練されたルックアップを可能にするために、RsaKeyConversionServicePostProcessor
を後処理できます。
Java
Kotlin
@Bean
BeanFactoryPostProcessor conversionServiceCustomizer() {
return beanFactory ->
beanFactory.getBean(RsaKeyConversionServicePostProcessor.class)
.setResourceLoader(new CustomResourceLoader());
}
@Bean
fun conversionServiceCustomizer(): BeanFactoryPostProcessor {
return BeanFactoryPostProcessor { beanFactory ->
beanFactory.getBean<RsaKeyConversionServicePostProcessor>()
.setResourceLoader(CustomResourceLoader())
}
}
キーの場所を指定します。
key.location: hfds://my-key.pub
そして、値をオートワイヤーします。
Java
Kotlin
@Value("${key.location}")
RSAPublicKey key;
@Value("\${key.location}")
val key: RSAPublicKey? = null
単一の対称キーを信頼する
単一の対称キーの使用も簡単です。次のように、SecretKey
をロードして適切な NimbusJwtDecoder
ビルダーを使用するだけです。
Java
Kotlin
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withSecretKey(this.key).build();
}
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withSecretKey(key).build()
}
認可の構成
OAuth 2.0 認可サーバーから発行される JWT は通常、scope
または scp
属性のいずれかを持ち、付与されたスコープ(または権限)を示します。例:
{ …, "scope" : "messages contacts"}
この場合、リソースサーバーはこれらのスコープを付与された権限のリストに強制し、各スコープの前に文字列 "SCOPE_" を付けようとします。
つまり、エンドポイントまたはメソッドを JWT から派生したスコープで保護するには、対応する式に次のプレフィックスを含める必要があります。
Java
Kotlin
XML
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
@Configuration
@EnableWebSecurity
public class DirectlyConfiguredJwkSetUri {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/contacts/**").access(hasScope("contacts"))
.requestMatchers("/messages/**").access(hasScope("messages"))
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
}
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
@Configuration
@EnableWebSecurity
class DirectlyConfiguredJwkSetUri {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/contacts/**", hasScope("contacts"))
authorize("/messages/**", hasScope("messages"))
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
return http.build()
}
}
<http>
<intercept-uri pattern="/contacts/**" access="hasAuthority('SCOPE_contacts')"/>
<intercept-uri pattern="/messages/**" access="hasAuthority('SCOPE_messages')"/>
<oauth2-resource-server>
<jwt jwk-set-uri="https://idp.example.org/.well-known/jwks.json"/>
</oauth2-resource-server>
</http>
または、同様にメソッドセキュリティで:
Java
Kotlin
@PreAuthorize("hasAuthority('SCOPE_messages')")
public List<Message> getMessages(...) {}
@PreAuthorize("hasAuthority('SCOPE_messages')")
fun getMessages(): List<Message> { }
権限の手動抽出
ただし、このデフォルトでは不十分な状況がいくつかあります。例: 一部の認可サーバーは scope
属性を使用せず、独自のカスタム属性を持っています。または、リソースサーバーは、属性または属性の構成を内部化されたオーソリティに適合させる必要がある場合もあります。
このため、Spring Security には Jwt
を Authentication
に変換する JwtAuthenticationConverter
が同梱されています。デフォルトでは、Spring Security は JwtAuthenticationProvider
を JwtAuthenticationConverter
のデフォルトインスタンスにワイヤリングします。
JwtAuthenticationConverter
の構成の一部として、Jwt
から認可された権限の Collection
に移行するための補助コンバーターを提供できます。
認可サーバーが authorities
と呼ばれるカスタムクレームで権限と通信するとします。その場合、JwtAuthenticationConverter
がインスペクションする必要があるクレームを次のように構成できます。
Java
Kotlin
XML
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
@Bean
fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
val grantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities")
val jwtAuthenticationConverter = JwtAuthenticationConverter()
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter)
return jwtAuthenticationConverter
}
<http>
<intercept-uri pattern="/contacts/**" access="hasAuthority('SCOPE_contacts')"/>
<intercept-uri pattern="/messages/**" access="hasAuthority('SCOPE_messages')"/>
<oauth2-resource-server>
<jwt jwk-set-uri="https://idp.example.org/.well-known/jwks.json"
jwt-authentication-converter-ref="jwtAuthenticationConverter"/>
</oauth2-resource-server>
</http>
<bean id="jwtAuthenticationConverter"
class="org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter">
<property name="jwtGrantedAuthoritiesConverter" ref="jwtGrantedAuthoritiesConverter"/>
</bean>
<bean id="jwtGrantedAuthoritiesConverter"
class="org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter">
<property name="authoritiesClaimName" value="authorities"/>
</bean>
権限のプレフィックスを異なるように構成することもできます。各権限の前に SCOPE_
を付ける代わりに、次のように ROLE_
に変更できます。
Java
Kotlin
XML
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
@Bean
fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
val grantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_")
val jwtAuthenticationConverter = JwtAuthenticationConverter()
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter)
return jwtAuthenticationConverter
}
<http>
<intercept-uri pattern="/contacts/**" access="hasAuthority('SCOPE_contacts')"/>
<intercept-uri pattern="/messages/**" access="hasAuthority('SCOPE_messages')"/>
<oauth2-resource-server>
<jwt jwk-set-uri="https://idp.example.org/.well-known/jwks.json"
jwt-authentication-converter-ref="jwtAuthenticationConverter"/>
</oauth2-resource-server>
</http>
<bean id="jwtAuthenticationConverter"
class="org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter">
<property name="jwtGrantedAuthoritiesConverter" ref="jwtGrantedAuthoritiesConverter"/>
</bean>
<bean id="jwtGrantedAuthoritiesConverter"
class="org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter">
<property name="authorityPrefix" value="ROLE_"/>
</bean>
または、JwtGrantedAuthoritiesConverter#setAuthorityPrefix("")
を呼び出して、プレフィックスを完全に削除できます。
柔軟性を高めるため、DSL はコンバーターを Converter<Jwt, AbstractAuthenticationToken>
を実装するクラスに完全に置き換えることをサポートしています。
Java
Kotlin
static class CustomAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
public AbstractAuthenticationToken convert(Jwt jwt) {
return new CustomAuthenticationToken(jwt);
}
}
// ...
@Configuration
@EnableWebSecurity
public class CustomAuthenticationConverterConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(new CustomAuthenticationConverter())
)
);
return http.build();
}
}
internal class CustomAuthenticationConverter : Converter<Jwt, AbstractAuthenticationToken> {
override fun convert(jwt: Jwt): AbstractAuthenticationToken {
return CustomAuthenticationToken(jwt)
}
}
// ...
@Configuration
@EnableWebSecurity
class CustomAuthenticationConverterConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt {
jwtAuthenticationConverter = CustomAuthenticationConverter()
}
}
}
return http.build()
}
}
検証の構成
認可サーバーの発行者 URI を示す最小限の Spring Boot 構成を使用して、リソースサーバーは、iss
クレームと exp
および nbf
タイムスタンプクレームをデフォルトで検証します。
検証をカスタマイズする必要がある状況では、リソースサーバーには 2 つの標準バリデーターが付属しており、カスタム OAuth2TokenValidator
インスタンスも受け入れます。
タイムスタンプ検証のカスタマイズ
通常、JWT には有効期間があり、ウィンドウの開始は nbf
クレームで示され、終了は exp
クレームで示されます。
ただし、すべてのサーバーでクロックドリフトが発生する可能性があります。これにより、あるサーバーではトークンが期限切れになり、別のサーバーでは期限切れになります。これにより、分散システムでコラボレーションサーバーの数が増えると、実装の胸焼けが発生する可能性があります。
リソースサーバーは JwtTimestampValidator
を使用してトークンの有効期間を検証し、clockSkew
で構成して上記の問題を軽減できます。
Java
Kotlin
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(Duration.ofSeconds(60)),
new JwtIssuerValidator(issuerUri));
jwtDecoder.setJwtValidator(withClockSkew);
return jwtDecoder;
}
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder: NimbusJwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri) as NimbusJwtDecoder
val withClockSkew: OAuth2TokenValidator<Jwt> = DelegatingOAuth2TokenValidator(
JwtTimestampValidator(Duration.ofSeconds(60)),
JwtIssuerValidator(issuerUri))
jwtDecoder.setJwtValidator(withClockSkew)
return jwtDecoder
}
デフォルトでは、ResourceServer は 60 秒のクロックスキューを構成します。 |
カスタム検証ツールの構成
OAuth2TokenValidator
API を使用すると、aud
クレームのチェックを簡単に追加できます。
Java
Kotlin
OAuth2TokenValidator<Jwt> audienceValidator() {
return new JwtClaimValidator<List<String>>(AUD, aud -> aud.contains("messaging"));
}
fun audienceValidator(): OAuth2TokenValidator<Jwt?> {
return JwtClaimValidator<List<String>>(AUD) { aud -> aud.contains("messaging") }
}
または、より制御するために、独自の OAuth2TokenValidator
を実装できます。
Java
Kotlin
static class AudienceValidator implements OAuth2TokenValidator<Jwt> {
OAuth2Error error = new OAuth2Error("custom_code", "Custom error message", null);
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
if (jwt.getAudience().contains("messaging")) {
return OAuth2TokenValidatorResult.success();
} else {
return OAuth2TokenValidatorResult.failure(error);
}
}
}
// ...
OAuth2TokenValidator<Jwt> audienceValidator() {
return new AudienceValidator();
}
internal class AudienceValidator : OAuth2TokenValidator<Jwt> {
var error: OAuth2Error = OAuth2Error("custom_code", "Custom error message", null)
override fun validate(jwt: Jwt): OAuth2TokenValidatorResult {
return if (jwt.audience.contains("messaging")) {
OAuth2TokenValidatorResult.success()
} else {
OAuth2TokenValidatorResult.failure(error)
}
}
}
// ...
fun audienceValidator(): OAuth2TokenValidator<Jwt> {
return AudienceValidator()
}
次に、リソースサーバーに追加するには、JwtDecoder
インスタンスを指定するだけです。
Java
Kotlin
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> audienceValidator = audienceValidator();
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder: NimbusJwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri) as NimbusJwtDecoder
val audienceValidator = audienceValidator()
val withIssuer: OAuth2TokenValidator<Jwt> = JwtValidators.createDefaultWithIssuer(issuerUri)
val withAudience: OAuth2TokenValidator<Jwt> = DelegatingOAuth2TokenValidator(withIssuer, audienceValidator)
jwtDecoder.setJwtValidator(withAudience)
return jwtDecoder
}
前述したように、代わりに Boot で aud 検証を構成できます。 |
クレームセットマッピングの構成
Spring Security は、Nimbus (英語) ライブラリを使用して、JWT の構文解析と署名の検証を行います。Spring Security は、各フィールド値の Nimbus の解釈と、それぞれを Java 型に強制する方法の対象となります。
例: Nimbus は Java 7 と互換性があるため、タイムスタンプフィールドを表すために Instant
を使用しません。
また、別のライブラリを使用したり、JWT 処理に使用したりすることもできます。これにより、調整が必要な独自の強制決定を行うことができます。
または、非常に単純に、リソースサーバーはドメイン固有の理由で JWT にクレームを追加または削除したい場合があります。
これらの目的のために、リソースサーバーは MappedJwtClaimSetConverter
を使用した JWT クレームセットのマッピングをサポートしています。
単一クレームの変換のカスタマイズ
デフォルトでは、MappedJwtClaimSetConverter
はクレームを次の型に強制しようとします。
クレーム | Java 型 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MappedJwtClaimSetConverter.withDefaults
を使用して、個々のクレームの変換戦略を構成できます。
Java
Kotlin
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.singletonMap("sub", this::lookupUserIdBySub));
jwtDecoder.setClaimSetConverter(converter);
return jwtDecoder;
}
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build()
val converter = MappedJwtClaimSetConverter
.withDefaults(mapOf("sub" to this::lookupUserIdBySub))
jwtDecoder.setClaimSetConverter(converter)
return jwtDecoder
}
これにより、sub
のデフォルトクレームコンバーターがオーバーライドされることを除き、すべてのデフォルトが保持されます。
クレームを追加する
MappedJwtClaimSetConverter
は、たとえば既存のシステムに適応するために、カスタムクレームを追加するためにも使用できます。
Java
Kotlin
MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("custom", custom -> "value"));
MappedJwtClaimSetConverter.withDefaults(mapOf("custom" to Converter<Any, String> { "value" }))
クレームの削除
また、同じ API を使用して、クレームを削除することも簡単です。
Java
Kotlin
MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("legacyclaim", legacy -> null));
MappedJwtClaimSetConverter.withDefaults(mapOf("legacyclaim" to Converter<Any, Any> { null }))
クレームの名前を変更する
一度に複数のクレームを参照したり、クレームの名前を変更したりするような、より洗練されたシナリオでは、リソースサーバーは Converter<Map<String, Object>, Map<String,Object>>
を実装するクラスを受け入れます。
Java
Kotlin
public class UsernameSubClaimAdapter implements Converter<Map<String, Object>, Map<String, Object>> {
private final MappedJwtClaimSetConverter delegate =
MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
public Map<String, Object> convert(Map<String, Object> claims) {
Map<String, Object> convertedClaims = this.delegate.convert(claims);
String username = (String) convertedClaims.get("user_name");
convertedClaims.put("sub", username);
return convertedClaims;
}
}
class UsernameSubClaimAdapter : Converter<Map<String, Any?>, Map<String, Any?>> {
private val delegate = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap())
override fun convert(claims: Map<String, Any?>): Map<String, Any?> {
val convertedClaims = delegate.convert(claims)
val username = convertedClaims["user_name"] as String
convertedClaims["sub"] = username
return convertedClaims
}
}
そして、インスタンスは通常のように提供できます:
Java
Kotlin
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
jwtDecoder.setClaimSetConverter(new UsernameSubClaimAdapter());
return jwtDecoder;
}
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder: NimbusJwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build()
jwtDecoder.setClaimSetConverter(UsernameSubClaimAdapter())
return jwtDecoder
}
タイムアウトの構成
デフォルトでは、リソースサーバーは認可サーバーとの調整にそれぞれ 30 秒の接続およびソケットタイムアウトを使用します。
これはいくつかのシナリオでは短すぎるかもしれません。さらに、バックオフや発見などのより高度なパターンは考慮されません。
リソースサーバーが認可サーバーに接続する方法を調整するために、NimbusJwtDecoder
は RestOperations
のインスタンスを受け入れます。
Java
Kotlin
@Bean
public JwtDecoder jwtDecoder(RestTemplateBuilder builder) {
RestOperations rest = builder
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(rest).build();
return jwtDecoder;
}
@Bean
fun jwtDecoder(builder: RestTemplateBuilder): JwtDecoder {
val rest: RestOperations = builder
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.build()
return NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(rest).build()
}
また、デフォルトでは、リソースサーバーは認可サーバーの JWK セットを 5 分間メモリにキャッシュします。これは、調整する必要がある場合があります。さらに、エビクションや共有キャッシュの使用など、より高度なキャッシュパターンは考慮されません。
リソースサーバーが JWK セットをキャッシュする方法を調整するために、NimbusJwtDecoder
は Cache
のインスタンスを受け入れます。
Java
Kotlin
@Bean
public JwtDecoder jwtDecoder(CacheManager cacheManager) {
return NimbusJwtDecoder.withIssuerLocation(issuer)
.cache(cacheManager.getCache("jwks"))
.build();
}
@Bean
fun jwtDecoder(cacheManager: CacheManager): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(issuer)
.cache(cacheManager.getCache("jwks"))
.build()
}
Cache
を指定すると、リソースサーバーは JWK Set Uri をキーとして使用し、JWK Set JSON を値として使用します。
Spring はキャッシュプロバイダーではないため、spring-boot-starter-cache やお気に入りのキャッシュプロバイダーなどの適切な依存関係を含める必要があります。 |
ソケットタイムアウトかキャッシュタイムアウトかに関係なく、代わりに Nimbus を直接操作したい場合があります。そのためには、NimbusJwtDecoder に Nimbus の JWTProcessor を使用するコンストラクターが同梱されていることを覚えておいてください。 |