OAuth 2.0 资源服务器 JWT
JWT 的最小依赖项
大多数资源服务器的支持功能都集成在 spring-security-oauth2-resource-server 中。然而,对 JWT 的解码和验证支持位于 spring-security-oauth2-jose 模块中,这意味着要构建一个支持 JWT 编码的 Bearer Token 的资源服务器,这两个模块都是必需的。
JWT 的最小配置
在使用 Spring Boot 时,将应用程序配置为资源服务器包含两个基本步骤。首先,包含所需的依赖项。其次,指定授权服务器的位置。
指定授权服务器
在Spring Boot应用中,你需要指定使用哪个授权服务器:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/issuer
其中 [idp.example.com/issuer](https://idp.example.com/issuer) 是授权服务器颁发的 JWT 令牌中 iss 声明所包含的值。资源服务器利用此属性进行进一步的自配置、发现授权服务器的公钥,并随后验证传入的 JWT。
要使用 issuer-uri 属性,还必须满足以下条件之一:[idp.example.com/issuer/.well-known/openid-configuration](https://idp.example.com/issuer/.well-known/openid-configuration)、[idp.example.com/.well-known/openid-configuration/issuer](https://idp.example.com/.well-known/openid-configuration/issuer) 或 [idp.example.com/.well-known/oauth-authorization-server/issuer](https://idp.example.com/.well-known/oauth-authorization-server/issuer) 是授权服务器支持的端点。此端点被称为 Provider Configuration 端点或 Authorization Server Metadata 端点。
启动预期
当使用此属性及这些依赖项时,资源服务器会自动配置自身以验证JWT编码的Bearer令牌。
它通过一个确定性的启动过程来实现这一点:
-
访问提供者配置或授权服务器元数据端点,处理响应中的
jwks_url属性。 -
配置验证策略,使其查询
jwks_url以获取有效的公钥。 -
配置验证策略,使其根据
[idp.example.com](https://idp.example.com)验证每个 JWT 的iss声明。
这一过程的结果是,授权服务器必须能够接收请求,资源服务器才能成功启动。
如果资源服务器在查询授权服务器时,授权服务器处于宕机状态(在合理的超时设置下),那么启动过程将会失败。
运行时预期
应用程序启动后,资源服务器会尝试处理所有包含 Authorization: Bearer 请求头的请求:
GET / HTTP/1.1
Authorization: Bearer some-token-value # Resource Server will process this
只要此方案被指定,资源服务器就会尝试根据Bearer Token规范处理请求。
给定一个格式正确的JWT,资源服务器:
-
验证其签名,该签名与启动时从
jwks_url端点获取的公钥进行比对,并与 JWT 的头部信息匹配。 -
验证 JWT 的
exp和nbf时间戳,以及 JWT 的iss声明。 -
将每个 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 上提供此属性。
覆盖或替换启动自动配置
Spring Boot 会代表 Resource Server 生成两个 @Bean 对象。
第一个bean是一个SecurityWebFilterChain,用于将应用程序配置为资源服务器。当包含spring-security-oauth2-jose时,这个SecurityWebFilterChain的配置如下:
- Java
- Kotlin
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.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:
- Java
- Kotlin
import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.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 { }
}
}
}
上述配置要求任何以 /messages/ 开头的 URL 都需要 message:read 权限范围。
oauth2ResourceServer DSL 上的方法同样会覆盖或替换自动配置。
例如,Spring Boot 创建的第二个 @Bean 是一个 ReactiveJwtDecoder,它负责将 String 类型的令牌解码为经过验证的 Jwt 实例:
- Java
- Kotlin
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return ReactiveJwtDecoders.fromIssuerLocation(issuerUri);
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return ReactiveJwtDecoders.fromIssuerLocation(issuerUri)
}
调用 ReactiveJwtDecoders#fromIssuerLocation 会触发 Provider Configuration 或 Authorization Server 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((authorize) -> authorize
.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() 更强大,因为它完全取代了 Spring Boot 对 JwtDecoder 的任何自动配置:
- Java
- Kotlin
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.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()
}
或者,你也可以使用颁发者,让 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 构建器来自定义此行为。
使用 Spring Boot 自定义受信任算法
设置算法的最简单方式是将其作为属性:
spring:
security:
oauth2:
resourceserver:
jwt:
jws-algorithms: RS512
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
使用构建器自定义受信任算法
然而,为了获得更强大的功能,我们可以使用 NimbusReactiveJwtDecoder 自带的构建器:
- 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或使用构建器提供。
通过 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: 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
使用构建器
要直接连接 RSAPublicKey,请使用相应的 NimbusReactiveJwtDecoder 构建器:
- Java
- Kotlin
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return NimbusReactiveJwtDecoder.withPublicKey(this.key).build();
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return NimbusReactiveJwtDecoder.withPublicKey(key).build()
}
信任单个对称密钥
您也可以使用单个对称密钥。您可以加载您的 SecretKey 并使用相应的 NimbusReactiveJwtDecoder 构建器:
- 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 通常包含 scope 或 scp 属性,用于表示其被授予的权限范围(或权限)——例如:
{ ..., "scope" : "messages contacts"}
在这种情况下,资源服务器会尝试将这些作用域强制转换为已授予权限的列表,并在每个作用域前加上字符串 SCOPE_ 作为前缀。
这意味着,要使用从JWT派生的作用域来保护端点或方法,相应的表达式应包含此前缀:
- Java
- Kotlin
import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.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((authorize) -> authorize
.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配置,指定授权服务器的签发者URI时,资源服务器默认会验证iss声明以及exp和nbf时间戳声明。
当您需要自定义验证需求时,资源服务器内置了两个标准验证器,同时也支持自定义 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
}
默认情况下,Resource Server 配置了 60 秒的时钟偏移量。
配置 RFC 9068 验证
如果你需要符合 RFC 9068 规范的令牌,可以通过以下方式配置验证:
- Java
- Kotlin
@Bean
JwtDecoder jwtDecoder() {
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuerUri)
.validateTypes(false).build();
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
.audience("https://audience.example.org")
.clientId("client-identifier")
.issuer("https://issuer.example.org").build());
return jwtDecoder;
}
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuerUri)
.validateTypes(false).build()
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
.audience("https://audience.example.org")
.clientId("client-identifier")
.issuer("https://issuer.example.org").build())
return jwtDecoder
}
配置自定义验证器
你可以使用 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
}