OAuth 2.0 Resource Server JWT

Minimal Dependencies for JWT

大部分资源服务器支持收集到 spring-security-oauth2-resource-server 中。然而,解码和验证 JWT 的支持在 spring-security-oauth2-jose 中,这意味着两者对于拥有支持 JWT 编码承载令牌的可用资源服务器都是必需的。

Most Resource Server support is collected into spring-security-oauth2-resource-server. However, the support for decoding and verifying JWTs is in spring-security-oauth2-jose, meaning that both are necessary in order to have a working resource server that supports JWT-encoded Bearer Tokens.

Minimal Configuration for JWTs

当使用 Spring Boot 时,将应用程序配置为资源服务器包括两个基本步骤。首先,包含所需的依赖项,其次,指示授权服务器的位置。

When using Spring Boot, configuring an application as a resource server consists of two basic steps. First, include the needed dependencies and second, indicate the location of the authorization server.

Specifying the Authorization Server

在Spring Boot应用中,若要指定要使用哪一个授权服务器,只需执行以下操作:

In a Spring Boot application, to specify which authorization server to use, simply do:

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

其中,`https://idp.example.com/issuer`是授权服务器要颁发的JWT令牌中包含的`iss`声明中的值。资源服务器会使用此属性进行进一步的自我配置、发现授权服务器的公钥并随后验证入站JWT。

Where https://idp.example.com/issuer is the value contained in the iss claim for JWT tokens that the authorization server will issue. Resource Server will use this property to further self-configure, discover the authorization server’s public keys, and subsequently validate incoming JWTs.

To use the issuer-uri property, it must also be true that one of https://idp.example.com/issuer/.well-known/openid-configuration, https://idp.example.com/.well-known/openid-configuration/issuer, or https://idp.example.com/.well-known/oauth-authorization-server/issuer is a supported endpoint for the authorization server. This endpoint is referred to as a Provider Configuration endpoint or a Authorization Server Metadata endpoint.

这就是全部!

And that’s it!

Startup Expectations

当使用此属性和这些依赖项时,资源服务器将自动配置其自身来验证经过JWT编码的Bearer令牌。

When this property and these dependencies are used, Resource Server will automatically configure itself to validate JWT-encoded Bearer Tokens.

它通过确定性的启动过程实现这一点:

It achieves this through a deterministic startup process:

  1. Query the Provider Configuration or Authorization Server Metadata endpoint for the jwks_url property

  2. Query the jwks_url endpoint for supported algorithms

  3. Configure the validation strategy to query jwks_url for valid public keys of the algorithms found

  4. Configure the validation strategy to validate each JWTs iss claim against https://idp.example.com.

这个过程的后果是授权服务器必须处于启动状态并接收请求,以便资源服务器成功启动。

A consequence of this process is that the authorization server must be up and receiving requests in order for Resource Server to successfully start up.

如果资源服务器在查询它(给定适当的超时)时,授权服务器关闭,然后启动将失败。

If the authorization server is down when Resource Server queries it (given appropriate timeouts), then startup will fail.

Runtime Expectations

应用程序启动后,资源服务器将尝试处理包含`Authorization: Bearer`标头的任何请求:

Once the application is started up, Resource Server will attempt to process any request containing an Authorization: Bearer header:

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

只要指出了此方案,资源服务器会尝试根据Bearer令牌规范处理请求。

So long as this scheme is indicated, Resource Server will attempt to process the request according to the Bearer Token specification.

鉴于经过格式良好的JWT,资源服务器将:

Given a well-formed JWT, Resource Server will:

  1. Validate its signature against a public key obtained from the jwks_url endpoint during startup and matched against the JWT

  2. Validate the JWT’s exp and nbf timestamps and the JWT’s iss claim, and

  3. Map each scope to an authority with the prefix SCOPE_.

当授权服务器提供新密钥时,Spring Security 将自动轮换用于验证 JWT 的密钥。

As the authorization server makes available new keys, Spring Security will automatically rotate the keys used to validate JWTs.

产生的`Authentication#getPrincipal`在默认情况下是Spring Security`Jwt`对象,如果存在,`Authentication#getName`映射到JWT的`sub`属性。

The resulting Authentication#getPrincipal, by default, is a Spring Security Jwt object, and Authentication#getName maps to the JWT’s sub property, if one is present.

从此处,考虑跳转到:

From here, consider jumping to:

How JWT Authentication Works

接下来,让我们看看 Spring Security 用于支持类似于我们刚才看到的基于 servlet 的应用程序中的 JWT 身份验证的体系结构组件。

Next, let’s see the architectural components that Spring Security uses to support JWT Authentication in servlet-based applications, like the one we just saw.

{security-api-url}org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.html[JwtAuthenticationProvider] 是一种 AuthenticationProvider 实现,它利用 <<`JwtDecoder`,oauth2resourceserver-jwt-decoder>> 和 <<`JwtAuthenticationConverter`,oauth2resourceserver-jwt-authorization-extraction>> 对 JWT 进行身份验证。

{security-api-url}org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.html[JwtAuthenticationProvider] is an AuthenticationProvider implementation that leverages a <<`JwtDecoder`,oauth2resourceserver-jwt-decoder>> and <<`JwtAuthenticationConverter`,oauth2resourceserver-jwt-authorization-extraction>> to authenticate a JWT.

让我们来看看 JwtAuthenticationProvider 如何在 Spring Security 中工作。此图说明了 Reading the Bearer Token 图中的 AuthenticationManager 如何工作的详细信息。

Let’s take a look at how JwtAuthenticationProvider works within Spring Security. The figure explains details of how the AuthenticationManager in figures from oauth2resourceserver-authentication-bearertokenauthenticationfilter works.

jwtauthenticationprovider
Figure 1. JwtAuthenticationProvider Usage

number 1 来自 Reading the Bearer Token 的身份验证 FilterAuthenticationManager 传递了 BearerTokenAuthenticationToken,而 ProviderManager 实现了 AuthenticationManager

number 1 The authentication Filter from oauth2resourceserver-authentication-bearertokenauthenticationfilter passes a BearerTokenAuthenticationToken to the AuthenticationManager which is implemented by ProviderManager.

number 2 已配置为使用类型为 JwtAuthenticationProviderAuthenticationProvider

number 2 The ProviderManager is configured to use an AuthenticationProvider of type JwtAuthenticationProvider.

number 3 JwtAuthenticationProvider 使用 <<`JwtDecoder`,oauth2resourceserver-jwt-decoder>> 解码、验证和验证 Jwt

number 3 JwtAuthenticationProvider decodes, verifies, and validates the Jwt using a <<`JwtDecoder`,oauth2resourceserver-jwt-decoder>>.

number 4 JwtAuthenticationProvider 然后使用 <<`JwtAuthenticationConverter`,oauth2resourceserver-jwt-authorization-extraction>> 将 Jwt 转换为被授予权限的 Collection

number 4 JwtAuthenticationProvider then uses the <<`JwtAuthenticationConverter`,oauth2resourceserver-jwt-authorization-extraction>> to convert the Jwt into a Collection of granted authorities.

number 5 当身份验证成功时,返回的 AuthenticationJwtAuthenticationToken 类型,并且有一个主体,即由已配置的 JwtDecoder 返回的 Jwt。最终,返回的 JwtAuthenticationToken 将由身份验证 Filter 设置在 SecurityContextHolder 上。

number 5 When authentication is successful, the Authentication that is returned is of type JwtAuthenticationToken and has a principal that is the Jwt returned by the configured JwtDecoder. Ultimately, the returned JwtAuthenticationToken will be set on the SecurityContextHolder by the authentication Filter.

Specifying the Authorization Server JWK Set Uri Directly

如果授权服务器不支持任何配置端点,或者如果资源服务器必须能够独立于授权服务器启动,那么可以提供`jwk-set-uri`:

If the authorization server doesn’t support any configuration endpoints, or if Resource Server must be able to start up independently from the authorization server, then the jwk-set-uri can be supplied as well:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://idp.example.com
          jwk-set-uri: https://idp.example.com/.well-known/jwks.json

JWK 集 uri 没有标准化,但通常可以在授权服务器的文档中找到它

The JWK Set uri is not standardized, but can typically be found in the authorization server’s documentation

因此,Resource Server 没有在启动时检查授权服务器。我们仍然指定 issuer-uri,以便 Resource Server 仍然验证传入 JWT 的 iss 声明。

Consequently, Resource Server will not ping the authorization server at startup. We still specify the issuer-uri so that Resource Server still validates the iss claim on incoming JWTs.

此属性也可以直接在 DSL 上提供。

This property can also be supplied directly on the oauth2resourceserver-jwt-jwkseturi-dsl.

Supplying Audiences

如已经看到,<<`issuer-uri` 属性验证 iss 声明,_指定_授权服务器>>; 这是发送 JWT 的对象。

As already seen, the <<`issuer-uri` property validates the iss claim,_specifying_the_authorization_server>>; this is who sent the JWT.

Boot 也具有 audiences 属性用于验证 aud 声明;这是 JWT 的发送对象。

Boot also has the audiences property for validating the aud claim; this is who the JWT was sent to.

可以如下所示指示资源服务器的受众:

A resource server’s audience can be indicated like so:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://idp.example.com
          audiences: https://my-resource-server.example.com

如果需要,你也可以添加 the aud validation programmatically

You can also add oauth2resourceserver-jwt-validation-custom, if needed.

结果为,如果 JWT 的 iss 声明不是 https://idp.example.com,并且它的 aud 声明在列表中不包含 https://my-resource-server.example.com,则验证将失败。

The result will be that if the JWT’s iss claim is not https://idp.example.com, and its aud claim does not contain https://my-resource-server.example.com in its list, then validation will fail.

Overriding or Replacing Boot Auto Configuration

有 Spring Boot 从 Resource Server 创建的两个 @Bean

There are two `@Bean`s that Spring Boot generates on Resource Server’s behalf.

第一个是将应用程序配置为资源服务器的 SecurityFilterChain。在包含 spring-security-oauth2-jose 时,此 SecurityFilterChain 如下所示:

The first is a SecurityFilterChain that configures the app as a resource server. When including spring-security-oauth2-jose, this SecurityFilterChain looks like:

Default JWT Configuration
  • 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 会公开上述默认值。

If the application doesn’t expose a SecurityFilterChain bean, then Spring Boot will expose the above default one.

替换它就像在应用程序中公开 bean:

Replacing this is as simple as exposing the bean within the application:

Custom JWT Configuration
  • 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 作用域。

The above requires the scope of message:read for any URL that starts with /messages/.

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

Methods on the oauth2ResourceServer DSL will also override or replace auto configuration.

例如,Spring Boot 创建的第二个 @BeanJwtDecoder,它 decodes String tokens into validated instances of Jwt

For example, the second @Bean Spring Boot creates is a JwtDecoder, which oauth2resourceserver-jwt-architecture-jwtdecoder:

JWT Decoder
  • Java

  • Kotlin

@Bean
public JwtDecoder jwtDecoder() {
    return JwtDecoders.fromIssuerLocation(issuerUri);
}
@Bean
fun jwtDecoder(): JwtDecoder {
    return JwtDecoders.fromIssuerLocation(issuerUri)
}

调用 {security-api-url}org/springframework/security/oauth2/jwt/JwtDecoders.html#fromIssuerLocation-java.lang.String-[JwtDecoders#fromIssuerLocation] 是为了调用提供方配置或授权服务器元数据端点以导出 JWK 集 URI。

Calling {security-api-url}org/springframework/security/oauth2/jwt/JwtDecoders.html#fromIssuerLocation-java.lang.String-[JwtDecoders#fromIssuerLocation] is what invokes the Provider Configuration or Authorization Server Metadata endpoint in order to derive the JWK Set Uri.

如果应用程序不公开 JwtDecoder bean,则 Spring Boot 会公开上述默认值。

If the application doesn’t expose a JwtDecoder bean, then Spring Boot will expose the above default one.

并且可以使用 jwkSetUri() 替换配置或使用 decoder()

And its configuration can be overridden using jwkSetUri() or replaced using decoder().

或者,如果您根本不使用 Spring Boot,则可以在 XML 中指定这两个组件 - 过滤器链和 JwtDecoder

Or, if you’re not using Spring Boot at all, then both of these components - the filter chain and a JwtDecoder can be specified in XML.

过滤器链是这样指定的:

The filter chain is specified like so:

Default JWT Configuration
  • Xml

<http>
    <intercept-uri pattern="/**" access="authenticated"/>
    <oauth2-resource-server>
        <jwt decoder-ref="jwtDecoder"/>
    </oauth2-resource-server>
</http>

并且 JwtDecoder 如下所示:

And the JwtDecoder like so:

JWT Decoder
  • 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>

Using jwkSetUri()

授权服务器的 JWK 设置 URI 可以在 as a configuration property 配置,或者它可以在 DSL 中提供:

An authorization server’s JWK Set Uri can be configured oauth2resourceserver-jwt-jwkseturi or it can be supplied in the DSL:

JWK Set Uri Configuration
  • 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() 优先于任何配置属性。

Using jwkSetUri() takes precedence over any configuration property.

Using decoder()

jwkSetUri() 更强大的是 decoder(),它将完全替换 <<`JwtDecoder`,oauth2resourceserver-jwt-architecture-jwtdecoder>> 的任何 Boot 自动配置:

More powerful than jwkSetUri() is decoder(), which will completely replace any Boot auto configuration of <<`JwtDecoder`,oauth2resourceserver-jwt-architecture-jwtdecoder>>:

JWT Decoder Configuration
  • 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>

当需要深入配置,诸如 validationmappingrequest timeouts 时,这非常方便。

Exposing a JwtDecoder @Bean

或者,公开一个 <<`JwtDecoder`,oauth2resourceserver-jwt-architecture-jwtdecoder>> @Bean 的效果与 decoder() 相同。您可以用 jwkSetUri 构造一个,如下所示:

Or, exposing a <<`JwtDecoder`,oauth2resourceserver-jwt-architecture-jwtdecoder>> @Bean has the same effect as decoder(). You can construct one with a jwkSetUri like so:

  • Java

  • Kotlin

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

或者,您可以使用颁发者并让 NimbusJwtDecoder 在调用 build() 时查找 jwkSetUri,如下所示:

or you can use the issuer and have NimbusJwtDecoder look up the jwkSetUri when build() is invoked, like the following:

  • Java

  • Kotlin

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

或者,如果默认设置对您有效,您还可以使用 JwtDecoders,它除了配置解码器的验证器外还会执行上述操作:

Or, if the defaults work for you, you can also use JwtDecoders, which does the above in addition to configuring the decoder’s validator:

  • Java

  • Kotlin

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

Configuring Trusted Algorithms

默认情况下,NimbusJwtDecoder 及由此及出的资源服务器只会使用 RS256 信任并验证令牌。

By default, NimbusJwtDecoder, and hence Resource Server, will only trust and verify tokens using RS256.

您可以通过 Spring Bootthe NimbusJwtDecoder builderJWK Set response 来自定义此操作。

Via Spring Boot

设置算法的最简单方法是用作属性:

The simplest way to set the algorithm is as a property:

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

Using a Builder

然而,为了获得更大的能力,我们可以使用 NimbusJwtDecoder 附带的生成器:

For greater power, though, we can use a builder that ships with 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 以信任一种以上的算法,如下所示:

Calling jwsAlgorithm more than once will configure NimbusJwtDecoder to trust more than one algorithm, like so:

  • 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

Or, you can call 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()
}

From JWK Set response

由于 Spring Security 的 JWT 支持基于 Nimbus,因此您也可以使用它所有出色的功能。

Since Spring Security’s JWT support is based off of Nimbus, you can use all it’s great features as well.

例如,Nimbus 有一个 JWSKeySelector 实现,它将根据 JWK 设置 URI 响应选择一组算法。您可以用它生成 NimbusJwtDecoder,如下所示:

For example, Nimbus has a JWSKeySelector implementation that will select the set of algorithms based on the JWK Set URI response. You can use it to generate a NimbusJwtDecoder like so:

  • 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)
}

Trusting a Single Asymmetric Key

比使用 JWK 设置端点支持资源服务器的更简单的做法是硬编码 RSA 公钥。可以通过 Spring Boot 或通过 Using a Builder 提供公钥。

Simpler than backing a Resource Server with a JWK Set endpoint is to hard-code an RSA public key. The public key can be provided via oauth2resourceserver-jwt-decoder-public-key-boot or by oauth2resourceserver-jwt-decoder-public-key-builder.

Via Spring Boot

通过 Spring Boot 指定密钥非常简单。密钥的位置可以如下指定:

Specifying a key via Spring Boot is quite simple. The key’s location can be specified like so:

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

或者,为了允许更复杂的查找,您可以对 RsaKeyConversionServicePostProcessor 进行后处理:

Or, to allow for a more sophisticated lookup, you can post-process the 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())
    }
}

指定您的密钥位置:

Specify your key’s location:

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

然后自动装配值:

And then autowire the value:

  • Java

  • Kotlin

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

Using a Builder

要直接接线 RSAPublicKey,您只需使用适当的 NimbusJwtDecoder 生成器,如下所示:

To wire an RSAPublicKey directly, you can simply use the appropriate NimbusJwtDecoder builder, like so:

  • Java

  • Kotlin

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

Trusting a Single Symmetric Key

使用单一对称密钥也很简单。您只需加载 SecretKey 并使用适当的 NimbusJwtDecoder 生成器,如下所示:

Using a single symmetric key is also simple. You can simply load in your SecretKey and use the appropriate NimbusJwtDecoder builder, like so:

  • Java

  • Kotlin

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

Configuring Authorization

OAuth 2.0 授权服务器颁发的 JWT 通常具有 scopescp 特性,表示已授予的范围(或权限),例如:

A JWT that is issued from an OAuth 2.0 Authorization Server will typically either have a scope or scp attribute, indicating the scopes (or authorities) it’s been granted, for example:

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

如果是这种情况,资源服务器将尝试强制将这些作用域转换为已授予权限列表,用字符串“SCOPE_”作为每个作用域的前缀。

When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the string "SCOPE_".

这意味着,要使用源自 JWT 的作用域保护端点或方法,相应的表达式应包括此前缀:

This means that to protect an endpoint or method with a scope derived from a JWT, the corresponding expressions should include this prefix:

Authorization Configuration
  • 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(OAuth2ResourceServerConfigurer::jwt);
        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>

或者与此类似的方法安全性:

Or similarly with method security:

  • Java

  • Kotlin

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

Extracting Authorities Manually

但是,在多种情况下,此默认值都是不够的。例如,一些授权服务器未使用 scope 属性,而是有自己的自定义属性。或者,在其他时候,资源服务器可能需要根据内部化权限调整属性或一组属性。

However, there are a number of circumstances where this default is insufficient. For example, some authorization servers don’t use the scope attribute, but instead have their own custom attribute. Or, at other times, the resource server may need to adapt the attribute or a composition of attributes into internalized authorities.

为此,Spring Security 附带了 JwtAuthenticationConverter,它负责 converting a Jwt into an Authentication。默认情况下,Spring Security 会用 JwtAuthenticationConverter 的默认实例连接 JwtAuthenticationProvider

To this end, Spring Security ships with JwtAuthenticationConverter, which is responsible for oauth2resourceserver-jwt-architecture-jwtauthenticationconverter. By default, Spring Security will wire the JwtAuthenticationProvider with a default instance of JwtAuthenticationConverter.

作为配置 JwtAuthenticationConverter 的一部分,您可以提供一个从 JwtCollection 的已授予权限的子转换器。

As part of configuring a JwtAuthenticationConverter, you can supply a subsidiary converter to go from Jwt to a Collection of granted authorities.

假设您的授权服务器在名为 authorities 的自定义声明中传达权限。在这种情况下,您可以配置 <<`JwtAuthenticationConverter`,oauth2resourceserver-jwt-architecture-jwtauthenticationconverter>> 应检查的声明,如下所示:

Let’s say that that your authorization server communicates authorities in a custom claim called authorities. In that case, you can configure the claim that <<`JwtAuthenticationConverter`,oauth2resourceserver-jwt-architecture-jwtauthenticationconverter>> should inspect, like so:

Authorities Claim Configuration
  • 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_,如下所示,而不仅仅是用 SCOPE_ 作为每个权限的前缀:

You can also configure the authority prefix to be different as well. Instead of prefixing each authority with SCOPE_, you can change it to ROLE_ like so:

Authorities Prefix Configuration
  • 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("") 完全移除前缀。

Or, you can remove the prefix altogether by calling JwtGrantedAuthoritiesConverter#setAuthorityPrefix("").

为了更灵活,DSL 支持完全用任何实现 Converter<Jwt, AbstractAuthenticationToken> 的类替换转换器:

For more flexibility, the DSL supports entirely replacing the converter with any class that implements 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()
    }
}

Configuring Validation

通过使用 minimal Spring Boot configuration 指出授权服务器的发行人 URI,资源服务器将默认验证 iss 声明以及 expnbf 时间戳声明。

Using oauth2resourceserver-jwt-minimalconfiguration, indicating the authorization server’s issuer uri, Resource Server will default to verifying the iss claim as well as the exp and nbf timestamp claims.

在需要对验证进行自定义的情况下,资源服务器附带两个标准验证器,并且还接受自定义 OAuth2TokenValidator 实例。

In circumstances where validation needs to be customized, Resource Server ships with two standard validators and also accepts custom OAuth2TokenValidator instances.

Customizing Timestamp Validation

JWT 通常有一个有效期窗口,窗口的开始用 nbf 声明表示,窗口的结束用 exp 声明表示。

JWT’s typically have a window of validity, with the start of the window indicated in the nbf claim and the end indicated in the exp claim.

但是,每个服务器都可能遇到时钟漂移,这可能导致某个服务器认为令牌已过期,而另一个服务器却没有。这可能会导致一些实现感到心烦,随着协作服务器的数量在分布式系统中增加,可能会出现这种情况。

However, every server can experience clock drift, which can cause tokens to appear expired to one server, but not to another. This can cause some implementation heartburn as the number of collaborating servers increases in a distributed system.

资源服务器使用 JwtTimestampValidator 验证令牌的有效期窗口,并且可以为其配置 clockSkew 以缓解上述问题:

Resource Server uses JwtTimestampValidator to verify a token’s validity window, and it can be configured with a clockSkew to alleviate the above problem:

  • 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
}

默认情况下,资源服务器配置为时钟偏移 60 秒。

By default, Resource Server configures a clock skew of 60 seconds.

Configuring a Custom Validator

使用 OAuth2TokenValidator API,只需添加一个对 the aud claim 的检查非常简单:

Adding a check for _supplying_audiences is simple with the OAuth2TokenValidator API:

  • 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

Or, for more control you can implement your own 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`,oauth2resourceserver-jwt-architecture-jwtdecoder>> 实例即可:

Then, to add into a resource server, it’s a matter of specifying the <<`JwtDecoder`,oauth2resourceserver-jwt-architecture-jwtdecoder>> instance:

  • 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
}

如前所述,你可以改为 configure aud validation in Boot

As stated earlier, you can instead _supplying_audiences.

Configuring Claim Set Mapping

Spring Security 使用 Nimbus 库来解析 JWT 并验证其签名。因此,Spring Security 受 Nimbus 对每个字段值的解释以及如何将其强制转换为 Java 类型的限制。

Spring Security uses the Nimbus library for parsing JWTs and validating their signatures. Consequently, Spring Security is subject to Nimbus’s interpretation of each field value and how to coerce each into a Java type.

例如,因为 Nimbus 仍然兼容 Java 7,所以它不使用 Instant 来表示时间戳字段。

For example, because Nimbus remains Java 7 compatible, it doesn’t use Instant to represent timestamp fields.

而且完全有可能使用不同的库或进行 JWT 处理,这可能会做出需要调整的自己的强制转换决策。

And it’s entirely possible to use a different library or for JWT processing, which may make its own coercion decisions that need adjustment.

或者,从根本上来说,资源服务器可能希望基于特定于域的原因向 JWT 添加或从中移除声明。

Or, quite simply, a resource server may want to add or remove claims from a JWT for domain-specific reasons.

出于这些目的,资源服务器支持使用 MappedJwtClaimSetConverter 映射 JWT 声明集。

For these purposes, Resource Server supports mapping the JWT claim set with MappedJwtClaimSetConverter.

Customizing the Conversion of a Single Claim

默认情况下,MappedJwtClaimSetConverter 将尝试将声明强制转换为以下类型:

By default, MappedJwtClaimSetConverter will attempt to coerce claims into the following types:

Claim

Java Type

aud

Collection<String>

exp

Instant

iat

Instant

iss

String

jti

String

nbf

Instant

sub

String

可以使用 MappedJwtClaimSetConverter.withDefaults 配置单个声明的转换策略:

An individual claim’s conversion strategy can be configured using 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 的默认声明转换器。

This will keep all the defaults, except it will override the default claim converter for sub.

Adding a Claim

MappedJwtClaimSetConverter 也可以用于添加自定义声明,例如,以适应现有系统:

MappedJwtClaimSetConverter can also be used to add a custom claim, for example, to adapt to an existing system:

  • Java

  • Kotlin

MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("custom", custom -> "value"));
MappedJwtClaimSetConverter.withDefaults(mapOf("custom" to Converter<Any, String> { "value" }))

Removing a Claim

并且移除声明也很简单,使用同样的 API:

And removing a claim is also simple, using the same API:

  • Java

  • Kotlin

MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("legacyclaim", legacy -> null));
MappedJwtClaimSetConverter.withDefaults(mapOf("legacyclaim" to Converter<Any, Any> { null }))

Renaming a Claim

在更加复杂的情景中,比如同时查阅多个声明或重命名一个声明,资源服务器接受实现 Converter<Map<String, Object>, Map<String,Object>> 的任何类:

In more sophisticated scenarios, like consulting multiple claims at once or renaming a claim, Resource Server accepts any class that implements 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
    }
}

然后,可以像往常一样提供实例:

And then, the instance can be supplied like normal:

  • 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
}

Configuring Timeouts

默认情况下,资源服务器针对协调授权服务器分别使用 30 秒的连接和套接字超时。

By default, Resource Server uses connection and socket timeouts of 30 seconds each for coordinating with the authorization server.

在某些情况下这可能太短。此外,它没有考虑到更复杂的模式,如回退和发现。

This may be too short in some scenarios. Further, it doesn’t take into account more sophisticated patterns like back-off and discovery.

要调整资源服务器连接到授权服务器的方式,NimbusJwtDecoder 接受一个 RestOperations 实例:

To adjust the way in which Resource Server connects to the authorization server, NimbusJwtDecoder accepts an instance of 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 分钟,这可能是你希望调整的。此外,它没有考虑到更复杂的缓存模式,比如逐出或使用共享缓存。

Also by default, Resource Server caches in-memory the authorization server’s JWK set for 5 minutes, which you may want to adjust. Further, it doesn’t take into account more sophisticated caching patterns like eviction or using a shared cache.

要调整资源服务器缓存 JWK 集的方式,NimbusJwtDecoder 接受一个 Cache 实例:

To adjust the way in which Resource Server caches the JWK set, NimbusJwtDecoder accepts an instance of 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 集 Uri 作为键,使用 JWK 集 JSON 作为值。

When given a Cache, Resource Server will use the JWK Set Uri as the key and the JWK Set JSON as the value.

Spring 不是缓存提供程序,所以你需要确保包含适当的依赖项,如 spring-boot-starter-cache 和你喜欢的缓存提供程序。

Spring isn’t a cache provider, so you’ll need to make sure to include the appropriate dependencies, like spring-boot-starter-cache and your favorite caching provider.

无论是套接字还是缓存超时,你可能反而想直接使用 Nimbus。为此,请记住 NimbusJwtDecoder 附带用于获取 Nimbus JWTProcessor 的构造函数。

Whether it’s socket or cache timeouts, you may instead want to work with Nimbus directly. To do so, remember that NimbusJwtDecoder ships with a constructor that takes Nimbus’s JWTProcessor.