OAuth 2.0 資源伺服器 JWT

JWT 的最小依賴

大多數資源伺服器支援都收集在 spring-security-oauth2-resource-server 中。然而,解碼和驗證 JWT 的支援在 spring-security-oauth2-jose 中,這表示兩者都是擁有支援 JWT 編碼 Bearer 令牌的運作資源伺服器所必需的。

JWT 的最小組態

當使用 Spring Boot 時,將應用程式組態為資源伺服器包含兩個基本步驟。首先,包含所需的依賴。其次,指出授權伺服器的位置。

指定授權伺服器

在 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-configurationidp.example.com/.well-known/openid-configuration/issueridp.example.com/.well-known/oauth-authorization-server/issuer 是授權伺服器的支援端點。此端點稱為 提供者組態 端點或 授權伺服器 Metadata 端點。

啟動期望

當使用此屬性和這些依賴時,資源伺服器會自動組態自身以驗證 JWT 編碼 Bearer 令牌。

它透過確定性的啟動程序來達成此目的

  1. 命中提供者組態或授權伺服器 Metadata 端點,處理回應以取得 jwks_url 屬性。

  2. 組態驗證策略以查詢 jwks_url 以取得有效的公開金鑰。

  3. 組態驗證策略以針對 idp.example.com 驗證每個 JWT 的 iss 宣告。

此程序的結果是授權伺服器必須正在接收請求,資源伺服器才能成功啟動。

如果授權伺服器在資源伺服器查詢時關閉(給予適當的逾時),則啟動會失敗。

執行階段期望

一旦應用程式啟動,資源伺服器會嘗試處理任何包含 Authorization: Bearer 標頭的請求

GET / HTTP/1.1
Authorization: Bearer some-token-value # Resource Server will process this

只要指出此方案,資源伺服器就會嘗試根據 Bearer 令牌規格處理請求。

給定格式正確的 JWT,資源伺服器

  1. 針對在啟動期間從 jwks_url 端點取得並與 JWT 標頭比對的公開金鑰驗證其簽章。

  2. 驗證 JWT 的 expnbf 時間戳記以及 JWT 的 iss 宣告。

  3. 將每個 scope 對應到具有前綴 SCOPE_ 的授權。

當授權伺服器提供新的金鑰時,Spring Security 會自動輪換用於驗證 JWT 令牌的金鑰。

依預設,產生的 Authentication#getPrincipal 是 Spring Security Jwt 物件,而 Authentication#getName 對應到 JWT 的 sub 屬性(如果存在)。

從這裡,考慮跳到

直接指定授權伺服器 JWK Set 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 上提供此屬性。

覆寫或取代 Boot 自動組態

Spring Boot 代表資源伺服器產生兩個 @Bean 物件。

第一個 bean 是 SecurityWebFilterChain,它將應用程式組態為資源伺服器。當包含 spring-security-oauth2-jose 時,此 SecurityWebFilterChain 看起來像

資源伺服器 SecurityWebFilterChain
  • Java

  • Kotlin

@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		.authorizeExchange(exchanges -> exchanges
			.anyExchange().authenticated()
		)
		.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt)
	return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        authorizeExchange {
            authorize(anyExchange, authenticated)
        }
        oauth2ResourceServer {
            jwt { }
        }
    }
}

如果應用程式未公開 SecurityWebFilterChain bean,Spring Boot 會公開預設的 bean(如先前的清單所示)。

若要取代它,請在應用程式中公開 @Bean

取代 SecurityWebFilterChain
  • Java

  • Kotlin

import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;

@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		.authorizeExchange(exchanges -> exchanges
			.pathMatchers("/message/**").access(hasScope("message:read"))
			.anyExchange().authenticated()
		)
		.oauth2ResourceServer(oauth2 -> oauth2
			.jwt(withDefaults())
		);
	return http.build();
}
import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope

@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        authorizeExchange {
            authorize("/message/**", hasScope("message:read"))
            authorize(anyExchange, authenticated)
        }
        oauth2ResourceServer {
            jwt { }
        }
    }
}

先前的組態需要 message:read scope 用於任何以 /messages/ 開頭的 URL。

oauth2ResourceServer DSL 上的方法也會覆寫或取代自動組態。

例如,Spring Boot 建立的第二個 @BeanReactiveJwtDecoder,它將 String 令牌解碼為 Jwt 的已驗證實例

ReactiveJwtDecoder
  • Java

  • Kotlin

@Bean
public ReactiveJwtDecoder jwtDecoder() {
    return ReactiveJwtDecoders.fromIssuerLocation(issuerUri);
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return ReactiveJwtDecoders.fromIssuerLocation(issuerUri)
}

呼叫 ReactiveJwtDecoders#fromIssuerLocation 會叫用提供者組態或授權伺服器 Metadata 端點以衍生 JWK Set URI。如果應用程式未公開 ReactiveJwtDecoder bean,Spring Boot 會公開上述預設的 bean。

可以使用 jwkSetUri() 覆寫其組態,或使用 decoder() 取代。

使用 jwkSetUri()

您可以組態授權伺服器的 JWK Set URI 作為組態屬性 或在 DSL 中提供它

  • Java

  • Kotlin

@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		.authorizeExchange(exchanges -> exchanges
			.anyExchange().authenticated()
		)
		.oauth2ResourceServer(oauth2 -> oauth2
			.jwt(jwt -> jwt
				.jwkSetUri("https://idp.example.com/.well-known/jwks.json")
			)
		);
	return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        authorizeExchange {
            authorize(anyExchange, authenticated)
        }
        oauth2ResourceServer {
            jwt {
                jwkSetUri = "https://idp.example.com/.well-known/jwks.json"
            }
        }
    }
}

使用 jwkSetUri() 優先於任何組態屬性。

使用 decoder()

decoder()jwkSetUri() 更強大,因為它完全取代了 JwtDecoder 的任何 Spring Boot 自動組態

  • Java

  • Kotlin

@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		.authorizeExchange(exchanges -> exchanges
			.anyExchange().authenticated()
		)
		.oauth2ResourceServer(oauth2 -> oauth2
			.jwt(jwt -> jwt
				.decoder(myCustomDecoder())
			)
		);
    return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        authorizeExchange {
            authorize(anyExchange, authenticated)
        }
        oauth2ResourceServer {
            jwt {
                jwtDecoder = myCustomDecoder()
            }
        }
    }
}

當您需要更深入的組態時,例如 驗證,這非常方便。

公開 ReactiveJwtDecoder @Bean

或者,公開 ReactiveJwtDecoder @Bean 具有與 decoder() 相同的效果:您可以使用 jwkSetUri 建構一個,如下所示

  • Java

  • Kotlin

@Bean
public ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build();
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build()
}

或者您可以使用 issuer,並讓 NimbusReactiveJwtDecoder 在叫用 build() 時查閱 jwkSetUri,如下所示

  • Java

  • Kotlin

@Bean
public ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build();
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build()
}

或者,如果預設值對您有效,您也可以使用 JwtDecoders,它除了組態解碼器的驗證器之外,還會執行上述操作

  • Java

  • Kotlin

@Bean
public ReactiveJwtDecoder jwtDecoder() {
    return ReactiveJwtDecoders.fromIssuerLocation(issuer);
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return ReactiveJwtDecoders.fromIssuerLocation(issuer)
}

組態信任演算法

依預設,NimbusReactiveJwtDecoder,因此資源伺服器,僅信任和驗證使用 RS256 的令牌。

您可以使用 Spring Boot 或使用 NimbusJwtDecoder Builder 自訂此行為。

使用 Spring Boot 自訂信任演算法

設定演算法最簡單的方法是作為屬性

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jws-algorithms: RS512
          jwk-set-uri: https://idp.example.org/.well-known/jwks.json

使用 Builder 自訂信任演算法

為了獲得更大的能力,我們可以使用 NimbusReactiveJwtDecoder 隨附的 builder

  • Java

  • Kotlin

@Bean
ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
            .jwsAlgorithm(RS512).build();
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
            .jwsAlgorithm(RS512).build()
}

多次呼叫 jwsAlgorithm 會組態 NimbusReactiveJwtDecoder 以信任多個演算法

  • Java

  • Kotlin

@Bean
ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
            .jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
            .jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
}

或者,您可以呼叫 jwsAlgorithms

  • Java

  • Kotlin

@Bean
ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withIssuerLocation(this.jwkSetUri)
            .jwsAlgorithms(algorithms -> {
                    algorithms.add(RS512);
                    algorithms.add(ES512);
            }).build();
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withIssuerLocation(this.jwkSetUri)
            .jwsAlgorithms {
                it.add(RS512)
                it.add(ES512)
            }
            .build()
}

信任單一非對稱金鑰

比使用 JWK Set 端點支援資源伺服器更簡單的方法是硬式編碼 RSA 公開金鑰。可以使用 Spring Boot 或透過 使用 Builder 提供公開金鑰。

透過 Spring Boot

您可以使用 Spring Boot 指定金鑰

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          public-key-location: classpath:my-key.pub

或者,為了允許更複雜的查閱,您可以後處理 RsaKeyConversionServicePostProcessor

BeanFactoryPostProcessor
  • Java

  • Kotlin

@Bean
BeanFactoryPostProcessor conversionServiceCustomizer() {
    return beanFactory ->
        beanFactory.getBean(RsaKeyConversionServicePostProcessor.class)
                .setResourceLoader(new CustomResourceLoader());
}
@Bean
fun conversionServiceCustomizer(): BeanFactoryPostProcessor {
    return BeanFactoryPostProcessor { beanFactory: ConfigurableListableBeanFactory ->
        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

使用 Builder

若要直接連線 RSAPublicKey,請使用適當的 NimbusReactiveJwtDecoder builder

  • Java

  • Kotlin

@Bean
public ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withPublicKey(this.key).build();
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withPublicKey(key).build()
}

信任單一對稱金鑰

您也可以使用單一對稱金鑰。您可以載入 SecretKey 並使用適當的 NimbusReactiveJwtDecoder builder

  • Java

  • Kotlin

@Bean
public ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withSecretKey(this.key).build();
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withSecretKey(this.key).build()
}

組態授權

從 OAuth 2.0 授權伺服器發出的 JWT 通常具有 scopescp 屬性,指示已授予它的 scope(或授權) — 例如

{ ..., "scope" : "messages contacts"}

在這種情況下,資源伺服器會嘗試強制將這些 scope 轉換為授權清單,並為每個 scope 添加字串前綴 SCOPE_

這表示,若要使用從 JWT 衍生的 scope 保護端點或方法,對應的運算式應包含此前綴

  • Java

  • Kotlin

import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;

@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		.authorizeExchange(exchanges -> exchanges
			.mvcMatchers("/contacts/**").access(hasScope("contacts"))
			.mvcMatchers("/messages/**").access(hasScope("messages"))
			.anyExchange().authenticated()
		)
		.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt);
    return http.build();
}
import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope

@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        authorizeExchange {
            authorize("/contacts/**", hasScope("contacts"))
            authorize("/messages/**", hasScope("messages"))
            authorize(anyExchange, authenticated)
        }
        oauth2ResourceServer {
            jwt { }
        }
    }
}

您可以使用方法安全執行類似的操作

  • Java

  • Kotlin

@PreAuthorize("hasAuthority('SCOPE_messages')")
public Flux<Message> getMessages(...) {}
@PreAuthorize("hasAuthority('SCOPE_messages')")
fun getMessages(): Flux<Message> { }

手動提取授權

然而,在許多情況下,此預設值不足。例如,某些授權伺服器不使用 scope 屬性。相反地,它們有自己的自訂屬性。在其他時候,資源伺服器可能需要調整屬性或屬性的組合以成為內部授權。

為此,DSL 公開 jwtAuthenticationConverter()

  • Java

  • Kotlin

@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		.authorizeExchange(exchanges -> exchanges
			.anyExchange().authenticated()
		)
		.oauth2ResourceServer(oauth2 -> oauth2
			.jwt(jwt -> jwt
				.jwtAuthenticationConverter(grantedAuthoritiesExtractor())
			)
		);
	return http.build();
}

Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
    JwtAuthenticationConverter jwtAuthenticationConverter =
            new JwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter
            (new GrantedAuthoritiesExtractor());
    return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        authorizeExchange {
            authorize(anyExchange, authenticated)
        }
        oauth2ResourceServer {
            jwt {
                jwtAuthenticationConverter = grantedAuthoritiesExtractor()
            }
        }
    }
}

fun grantedAuthoritiesExtractor(): Converter<Jwt, Mono<AbstractAuthenticationToken>> {
    val jwtAuthenticationConverter = JwtAuthenticationConverter()
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(GrantedAuthoritiesExtractor())
    return ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter)
}

jwtAuthenticationConverter() 負責將 Jwt 轉換為 Authentication。作為其組態的一部分,我們可以提供輔助轉換器,以從 Jwt 轉換為授權的 Collection

最終轉換器可能類似於以下的 GrantedAuthoritiesExtractor

  • Java

  • Kotlin

static class GrantedAuthoritiesExtractor
        implements Converter<Jwt, Collection<GrantedAuthority>> {

    public Collection<GrantedAuthority> convert(Jwt jwt) {
        Collection<?> authorities = (Collection<?>)
                jwt.getClaims().getOrDefault("mycustomclaim", Collections.emptyList());

        return authorities.stream()
                .map(Object::toString)
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
}
internal class GrantedAuthoritiesExtractor : Converter<Jwt, Collection<GrantedAuthority>> {
    override fun convert(jwt: Jwt): Collection<GrantedAuthority> {
        val authorities: List<Any> = jwt.claims
                .getOrDefault("mycustomclaim", emptyList<Any>()) as List<Any>
        return authorities
                .map { it.toString() }
                .map { SimpleGrantedAuthority(it) }
    }
}

為了獲得更大的彈性,DSL 支援完全取代轉換器,可以使用任何實作 Converter<Jwt, Mono<AbstractAuthenticationToken>> 的類別

  • Java

  • Kotlin

static class CustomAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
    public AbstractAuthenticationToken convert(Jwt jwt) {
        return Mono.just(jwt).map(this::doConversion);
    }
}
internal class CustomAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthenticationToken>> {
    override fun convert(jwt: Jwt): Mono<AbstractAuthenticationToken> {
        return Mono.just(jwt).map(this::doConversion)
    }
}

組態驗證

使用 最小 Spring Boot 組態,指出授權伺服器的 issuer URI,資源伺服器預設為驗證 iss 宣告以及 expnbf 時間戳記宣告。

在您需要自訂驗證需求的情況下,資源伺服器隨附兩個標準驗證器,並且也接受自訂 OAuth2TokenValidator 實例。

自訂時間戳記驗證

JWT 實例通常具有有效性視窗,視窗的開始在 nbf 宣告中指示,而結束在 exp 宣告中指示。

然而,每台伺服器都可能遇到時鐘漂移,這可能會導致令牌在某台伺服器上看起來已過期,但在另一台伺服器上則未過期。當分散式系統中協作伺服器的數量增加時,這可能會導致一些實作上的問題。

資源伺服器使用 JwtTimestampValidator 來驗證令牌的有效性視窗,您可以將其組態為 clockSkew 以減輕時鐘漂移問題

  • Java

  • Kotlin

@Bean
ReactiveJwtDecoder jwtDecoder() {
     NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder)
             ReactiveJwtDecoders.fromIssuerLocation(issuerUri);

     OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator<>(
            new JwtTimestampValidator(Duration.ofSeconds(60)),
            new IssuerValidator(issuerUri));

     jwtDecoder.setJwtValidator(withClockSkew);

     return jwtDecoder;
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    val jwtDecoder = ReactiveJwtDecoders.fromIssuerLocation(issuerUri) as NimbusReactiveJwtDecoder
    val withClockSkew: OAuth2TokenValidator<Jwt> = DelegatingOAuth2TokenValidator(
            JwtTimestampValidator(Duration.ofSeconds(60)),
            JwtIssuerValidator(issuerUri))
    jwtDecoder.setJwtValidator(withClockSkew)
    return jwtDecoder
}

依預設,資源伺服器組態了 60 秒的時鐘偏差。

組態自訂驗證器

您可以使用 OAuth2TokenValidator API 新增 aud 宣告的檢查

  • Java

  • Kotlin

public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
    OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);

    public OAuth2TokenValidatorResult validate(Jwt jwt) {
        if (jwt.getAudience().contains("messaging")) {
            return OAuth2TokenValidatorResult.success();
        } else {
            return OAuth2TokenValidatorResult.failure(error);
        }
    }
}
class AudienceValidator : OAuth2TokenValidator<Jwt> {
    var error: OAuth2Error = OAuth2Error("invalid_token", "The required audience is missing", null)
    override fun validate(jwt: Jwt): OAuth2TokenValidatorResult {
        return if (jwt.audience.contains("messaging")) {
            OAuth2TokenValidatorResult.success()
        } else {
            OAuth2TokenValidatorResult.failure(error)
        }
    }
}

然後,若要新增到資源伺服器中,您可以指定 ReactiveJwtDecoder 實例

  • Java

  • Kotlin

@Bean
ReactiveJwtDecoder jwtDecoder() {
    NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder)
            ReactiveJwtDecoders.fromIssuerLocation(issuerUri);

    OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator();
    OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
    OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

    jwtDecoder.setJwtValidator(withAudience);

    return jwtDecoder;
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    val jwtDecoder = ReactiveJwtDecoders.fromIssuerLocation(issuerUri) as NimbusReactiveJwtDecoder
    val audienceValidator: OAuth2TokenValidator<Jwt> = AudienceValidator()
    val withIssuer: OAuth2TokenValidator<Jwt> = JwtValidators.createDefaultWithIssuer(issuerUri)
    val withAudience: OAuth2TokenValidator<Jwt> = DelegatingOAuth2TokenValidator(withIssuer, audienceValidator)
    jwtDecoder.setJwtValidator(withAudience)
    return jwtDecoder
}