# OAuth2.0 资源服务器 JWT

## JWT 的最小依赖项

大多数资源服务器支持被收集到`spring-security-oauth2-resource-server`中。然而，对 JWTS 的解码和验证的支持是`spring-security-oauth2-jose`，这意味着这两个都是必要的，以便拥有一个支持 JWT 编码的承载令牌的工作资源服务器。

## JWTS 的最小配置

当使用[Spring Boot](https://spring.io/projects/spring-boot)时，将应用程序配置为资源服务器包括两个基本步骤。首先，包括所需的依赖关系，其次，指示授权服务器的位置。

### 指定授权服务器

在 Spring 引导应用程序中，要指定使用哪个授权服务器，只需执行以下操作：

```
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://idp.example.com/issuer
```

其中`[https://idp.example.com/issuer](https://idp.example.com/issuer)`是授权服务器将发布的 JWT 令牌的`iss`声明中包含的值。Resource Server 将使用此属性进一步自我配置，发现授权服务器的公钥，并随后验证传入的 JWTS。

|   |要使用`issuer-uri`属性，还必须证明`[https://idp.example.com/issuer/.well-known/openid-configuration](https://idp.example.com/issuer/.well-known/openid-configuration)`、`[https://idp.example.com/.well-known/openid-configuration/issuer](https://idp.example.com/.well-known/openid-configuration/issuer)`或`[https://idp.example.com/.well-known/oauth-authorization-server/issuer](https://idp.example.com/.well-known/oauth-authorization-server/issuer)`中的一个是授权服务器所支持的端点。<br/>此端点被称为[提供者配置](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig)端点或[授权服务器元数据](https://tools.ietf.org/html/rfc8414#section-3)端点。|
|---| |

就这样！

### 创业期望

当使用此属性和这些依赖项时，Resource Server 将自动配置自身以验证 JWT 编码的承载令牌。

它通过一个确定性的启动过程来实现这一点：

1. 点击提供者配置或授权服务器元数据端点，处理`jwks_url`属性的响应

2. 将验证策略配置为查询`jwks_url`中的有效公钥

3. 将验证策略配置为针对`[https://idp.example.com](https://idp.example.com)`的每个 JWTS`iss`索赔进行验证。

此过程的结果是，为了使资源服务器成功启动，授权服务器必须启动并接收请求。

|   |如果在资源服务器查询时，授权服务器处于关闭状态（给定适当的超时），那么启动将失败。|
|---|-------------------------------------------------------------------------------------------------------------------------|

### 运行时期望

一旦启动应用程序，Resource Server 将尝试处理任何包含`Authorization: Bearer`报头的请求：

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

只要表明了该方案，资源服务器就会尝试根据承载令牌规范来处理请求。

给定一个格式良好的 JWT，资源服务器将：

1. 根据在启动过程中从`jwks_url`端点获得并与 JWTS 头匹配的公钥验证其签名

2. 验证 JWTS`exp`和`nbf`时间戳和 JWTS`iss`声明，并

3. 将每个作用域映射到一个前缀`SCOPE_`的权限。

|   |由于授权服务器提供了可用的新密钥， Spring 安全性将自动旋转用于验证 JWT 令牌的密钥。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------|

默认情况下，生成的`Authentication#getPrincipal`是 Spring security`Jwt`对象，并且`Authentication#getName`映射到 JWT 的`sub`属性（如果存在）。

从这里开始，考虑跳到：

[如何在不将资源服务器启动与授权服务器的可用性绑定的情况下进行配置](#webflux-oauth2resourceserver-jwt-jwkseturi)

[How to Configure without Spring Boot](#webflux-oauth2resourceserver-jwt-sansboot)

### 直接指定授权服务器 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 集 URI 不是标准化的，但通常可以在授权服务器的文档中找到。|
|---|-----------------------------------------------------------------------------------------------------------|

因此，资源服务器不会在启动时对授权服务器进行 ping。我们仍然指定`issuer-uri`，以便 Resource Server 仍然在传入的 JWTS 上验证`iss`声明。

|   |也可以在[DSL](#webflux-oauth2resourceserver-jwt-jwkseturi-dsl)上直接提供此属性。|
|---|----------------------------------------------------------------------------------------------------------|

### 覆盖或替换 Boot Auto 配置

有两个`@Bean`s， Spring boot 代表资源服务器生成。

第一个是将应用程序配置为资源服务器的`SecurityWebFilterChain`。当包含`spring-security-oauth2-jose`时，这个`SecurityWebFilterChain`看起来是这样的：

例 1。资源服务器 SecurityWebFilterchain

爪哇

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

Kotlin

```
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        authorizeExchange {
            authorize(anyExchange, authenticated)
        }
        oauth2ResourceServer {
            jwt { }
        }
    }
}
```

如果应用程序不公开`SecurityWebFilterChain` Bean，那么 Spring 引导将公开上面的默认引导。

替换它就像在应用程序中公开 Bean 一样简单：

例 2。替换 SecurityWebFilterchain

爪哇

```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		.authorizeExchange(exchanges -> exchanges
			.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
			.anyExchange().authenticated()
		)
		.oauth2ResourceServer(oauth2 -> oauth2
			.jwt(withDefaults())
		);
	return http.build();
}
```

Kotlin

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

对于任何以`/messages/`开头的 URL，上述条件要求`message:read`的范围。

`oauth2ResourceServer`DSL 上的方法也将覆盖或替换自动配置。

例如，第二个`@Bean` Spring 引导创建了一个`ReactiveJwtDecoder`，它将`String`令牌解码为`Jwt`的验证实例：

例 3。ReactiveJWTdecoder

爪哇

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

Kotlin

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

|   |调用`[ReactiveJwtDecoders#fromIssuerLocation](https://docs.spring.io/spring-security/site/docs/5.6.2/api/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.html#fromIssuerLocation-java.lang.String-)`是调用提供者配置或授权服务器元数据端点的目的，以便派生 JWK 集 URI。<br/>如果应用程序不公开`ReactiveJwtDecoder` Bean，那么 Spring 引导将公开上述默认的。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

并且其配置可以使用`jwkSetUri()`重写或使用`decoder()`替换。

#### `

可以配置授权服务器的 JWK 集 URI[作为配置属性](#webflux-oauth2resourceserver-jwt-jwkseturi)，也可以在 DSL 中提供它：

爪哇

```
@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();
}
```

Kotlin

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

使用`jwkSetUri()`优先于任何配置属性。

#### `

比`jwkSetUri()`更强大的是`decoder()`，它将完全取代`JwtDecoder`的任何引导自动配置：

爪哇

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

Kotlin

```
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        authorizeExchange {
            authorize(anyExchange, authenticated)
        }
        oauth2ResourceServer {
            jwt {
                jwtDecoder = myCustomDecoder()
            }
        }
    }
}
```

当需要更深的配置时，比如[validation](#webflux-oauth2resourceserver-jwt-validation)，这是很方便的。

#### 曝光`ReactiveJwtDecoder``@Bean`

或者，暴露`ReactiveJwtDecoder``@Bean`具有与`decoder()`相同的效果：

爪哇

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

Kotlin

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

## 配置可信算法

默认情况下，`NimbusReactiveJwtDecoder`以及资源服务器将仅使用`RS256`信任和验证令牌。

你可以通过[Spring Boot](#webflux-oauth2resourceserver-jwt-boot-algorithm)或[NimbusJWTDecoder Builder](#webflux-oauth2resourceserver-jwt-decoder-builder)对此进行自定义。

### 通过 Spring 引导

设置算法的最简单方法是作为一个属性：

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

### 使用构建器

不过，为了获得更大的功率，我们可以使用带有`NimbusReactiveJwtDecoder`的建造器：

爪哇

```
@Bean
ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
            .jwsAlgorithm(RS512).build();
}
```

Kotlin

```
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
            .jwsAlgorithm(RS512).build()
}
```

多次调用`jwsAlgorithm`将配置`NimbusReactiveJwtDecoder`来信任多个算法，如下所示：

爪哇

```
@Bean
ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
            .jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
}
```

Kotlin

```
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
            .jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
}
```

或者，你可以调用`jwsAlgorithms`:

爪哇

```
@Bean
ReactiveJwtDecoder jwtDecoder() {
    return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
            .jwsAlgorithms(algorithms -> {
                    algorithms.add(RS512);
                    algorithms.add(ES512);
            }).build();
}
```

Kotlin

```
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
            .jwsAlgorithms {
                it.add(RS512)
                it.add(ES512)
            }
            .build()
}
```

### 信任单一的非对称密钥

比支持具有 JWK 设置端点的资源服务器更简单的方法是对 RSA 公钥进行硬编码。公钥可以通过[Spring Boot](#webflux-oauth2resourceserver-jwt-decoder-public-key-boot)或[使用构建器](#webflux-oauth2resourceserver-jwt-decoder-public-key-builder)提供。

#### 通过 Spring 引导

通过 Spring 引导指定一个键非常简单。密钥的位置可以这样指定：

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

或者，为了允许更复杂的查找，你可以对`RsaKeyConversionServicePostProcessor`进行后处理：

例 4。BeanFactoryPostprocessor

爪哇

```
@Bean
BeanFactoryPostProcessor conversionServiceCustomizer() {
    return beanFactory ->
        beanFactory.getBean(RsaKeyConversionServicePostProcessor.class)
                .setResourceLoader(new CustomResourceLoader());
}
```

Kotlin

```
@Bean
fun conversionServiceCustomizer(): BeanFactoryPostProcessor {
    return BeanFactoryPostProcessor { beanFactory: ConfigurableListableBeanFactory ->
        beanFactory.getBean<RsaKeyConversionServicePostProcessor>()
                .setResourceLoader(CustomResourceLoader())
    }
}
```

指定你的密钥的位置：

```
key.location: hfds://my-key.pub
```

然后自动连接该值：

爪哇

```
@Value("${key.location}")
RSAPublicKey key;
```

Kotlin

```
@Value("\${key.location}")
val key: RSAPublicKey? = null
```

#### 使用构建器

要直接连接`RSAPublicKey`，只需使用适当的`NimbusReactiveJwtDecoder`构建器，如下所示：

爪哇

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

Kotlin

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

### 信任单一的对称密钥

使用单一的对称密钥也很简单。你可以简单地在`SecretKey`中加载，并使用适当的`NimbusReactiveJwtDecoder`构建器，如下所示：

爪哇

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

Kotlin

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

### 配置授权

从 OAuth2.0 授权服务器发出的 JWT 通常具有`scope`或`scp`属性，指示已授予的范围（或权限），例如：

`{ …​, "scope" : "messages contacts"}`

在这种情况下，Resource Server 将尝试强制将这些作用域放入一个已授予权限的列表中，并在每个作用域前加上字符串“scope\_”。

这意味着，要使用从 JWT 派生的作用域来保护端点或方法，相应的表达式应该包括以下前缀：

爪哇

```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		.authorizeExchange(exchanges -> exchanges
			.mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
			.mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
			.anyExchange().authenticated()
		)
		.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt);
    return http.build();
}
```

Kotlin

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

或类似于方法安全性：

爪哇

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

Kotlin

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

#### 手动提取权限

然而，在许多情况下，这种默认设置是不够的。例如，一些授权服务器不使用`scope`属性，而是具有自己的自定义属性。或者，在其他时候，资源服务器可能需要将属性或属性的组合调整为内在化的权限。

为此，DSL 公开`jwtAuthenticationConverter()`:

爪哇

```
@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);
}
```

Kotlin

```
@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)
}
```

它负责将`Jwt`转换为`Authentication`。作为其配置的一部分，我们可以提供一个从`Jwt`到`Collection`的辅助转换器。

最后的转换器可能类似于下面的`GrantedAuthoritiesExtractor`:

爪哇

```
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());
    }
}
```

Kotlin

```
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>>`的任何类完全替换转换器：

爪哇

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

Kotlin

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

### 配置验证

使用[minimal Spring Boot configuration](#webflux-oauth2resourceserver-jwt-minimalconfiguration)（表示授权服务器的发行者 URI），资源服务器将默认验证`iss`声明以及`exp`和`nbf`时间戳声明。

在需要定制验证的情况下，Resource Server 附带两个标准验证器，并且还接受定制的`OAuth2TokenValidator`实例。

#### 自定义时间戳验证

JWT 通常有一个有效窗口，窗口的开始在`nbf`声明中指示，结束在`exp`声明中指示。

然而，每个服务器都可能经历时钟漂移，这可能导致令牌在一台服务器上出现过期，而不是在另一台服务器上出现。随着分布式系统中协作服务器数量的增加，这可能会导致一些实现令人心烦。

Resource Server 使用`JwtTimestampValidator`来验证令牌的有效性窗口，并且可以将其配置为`clockSkew`以缓解上述问题：

爪哇

```
@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;
}
```

Kotlin

```
@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 秒。|
|---|------------------------------------------------------------------|

#### 配置自定义验证器

使用`OAuth2TokenValidator`API 添加`aud`声明的检查很简单：

爪哇

```
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);
        }
    }
}
```

Kotlin

```
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`实例：

爪哇

```
@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;
}
```

Kotlin

```
@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
}
```

[OAuth2 资源服务器](index.html)[不透明令牌](opaque-token.html)

