JWS + JWK in a Spring Security OAuth2 Application – Spring Security OAuth2应用程序中的JWS + JWK

最后修改: 2019年 7月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll learn about JSON Web Signature (JWS), and how it can be implemented using the JSON Web Key (JWK) specification on applications configured with Spring Security OAuth2.

在本教程中,我们将了解JSON Web签名(JWS),以及如何在使用Spring Security OAuth2配置的应用程序上使用JSON Web密钥(JWK)规范来实现它。

We should keep in mind that even though Spring is working to migrate all the Spring Security OAuth features to the Spring Security framework, this guide is still a good starting point to understand the basic concepts of these specifications and it should come in handy at the time of implementing them on any framework.

我们应该记住,即使Spring正在努力将所有Spring Security OAuth功能迁移到Spring Security框架中,本指南仍然是了解这些规范的基本概念的一个良好起点,在任何框架上实施这些规范时,它应该会很有用。

First, we’ll try to understand the basic concepts; like what’s JWS and JWK, their purpose and how we can easily configure a Resource Server to use this OAuth solution.

首先,我们将尝试了解基本概念;比如什么是JWS和JWK,它们的目的,以及我们如何轻松配置资源服务器来使用这个OAuth解决方案。

Then we’ll go deeper, we’ll analyze the specifications in detail by analyzing what OAuth2 Boot is doing behind the scenes, and by setting up an Authorization Server to use JWK.

然后我们会更深入,我们会通过分析OAuth2 Boot在幕后做什么,以及建立一个使用JWK的授权服务器来详细分析这些规范。

2. Understanding the Big Picture of JWS and JWK

2.了解JWS和JWK的大背景

bael 1239 image simple 1

bael 1239 image simple 1

Before starting, it’s important that we understand correctly some basic concepts. It’s advisable to go through our OAuth and our JWT articles first since these topics are not part of the scope of this tutorial.

在开始之前,我们必须正确理解一些基本概念。建议先阅读我们的OAuthJWT文章,因为这些主题不属于本教程的范畴。

JWS is a specification created by the IETF that describes different cryptographic mechanisms to verify the integrity of data, namely the data in a JSON Web Token (JWT). It defines a JSON structure that contains the necessary information to do so.

JWS是由IETF创建的规范,描述了不同的加密机制来验证数据的完整性,即JSON Web令牌(JWT)中的数据。它定义了一个包含必要信息的JSON结构,以做到这一点。

It’s a key aspect in the widely-used JWT spec since the claims need to be either signed or encrypted in order to be considered effectively secured.

这是广泛使用的JWT规范中的一个关键方面,因为索赔需要被签署或加密,以便被认为是有效的安全。

In the first case, the JWT is represented as a JWS. While if it’s encrypted, the JWT will be encoded in a JSON Web Encryption (JWE) structure.

在第一种情况下,JWT被表示为一个JWS。而如果是加密的,JWT将被编码为JSON Web Encryption(JWE)结构。

The most common scenario when working with OAuth is having just signed JWTs. This is because we don’t usually need to “hide” information but simply verify the integrity of the data.

在使用OAuth时,最常见的情况是刚刚签署了JWTs。这是因为我们通常不需要 “隐藏 “信息,只需要验证数据的完整性。

Of course, whether we’re handling signed or encrypted JWTs, we need formal guidelines to be able to transmit public keys efficiently.

当然,无论我们是处理签名的还是加密的JWT,我们都需要正式的准则,以便能够有效地传输公钥。

This is the purpose of JWK, a JSON structure that represents a cryptographic key, defined also by the IETF.

这就是JWK的目的,这是一个代表加密密钥的JSON结构,也由IETF定义。

Many Authentication providers offer a “JWK Set” endpoint, also defined in the specifications. With it, other applications can find information on public keys to process JWTs.

许多认证供应商提供了一个 “JWK Set “端点,也是在规范中定义的。通过它,其他应用程序可以找到有关公钥的信息,以处理JWTs。

For instance, a Resource Server uses the kid (Key Id) field present in the JWT to find the correct key in the JWK set.

例如,资源服务器使用JWT中的kid(Key Id)字段来查找JWK集中的正确密钥。

2.1. Implementing a Solution Using JWK

2.1.使用JWK实现一个解决方案

Commonly, if we want our application to serve resource in a secure manner, like by using a standard security protocol such as OAuth 2.0, we’ll need to follow the next steps:

通常,如果我们想让我们的应用程序以安全的方式提供资源,比如通过使用标准的安全协议,如OAuth 2.0,我们需要遵循接下来的步骤。

  1. Register Clients in an Authorization Server – either in our own service, or in a well-known provider like Okta, Facebook or Github
  2. These Clients will request an access token from the Authorization Server, following any of the OAuth strategies we might have configured
  3. They will then try to access the resource presenting the token (in this case, as a JWT) to the Resource Server
  4. The Resource Server has to verify that the token hasn’t been manipulated by checking its signature as well as validate its claims
  5. And finally, our Resource Server retrieves the resource, now being sure that the Client has the correct permissions

3. JWK and the Resource Server Configuration

3.JWK和资源服务器配置

Later on, we’ll see how to set up our own Authorization server that serves JWTs and a ‘JWK Set’ endpoint.

稍后,我们将看到如何设置我们自己的授权服务器,为JWT和 “JWK Set “端点服务。

At this point, though, we’ll focus on the simplest – and probably most common – scenario where we’re pointing at an existing Authorization server.

不过在这一点上,我们将专注于最简单的–也可能是最常见的–场景,即我们指向现有的授权服务器。

All we have to do is indicate how the service has to validate the access token it receives, like what public key it should use to verify the JWT’s signature.

我们所要做的就是指出服务必须如何验证它所收到的访问令牌,比如它应该使用什么公钥来验证JWT的签名。

We’ll use Spring Security OAuth’s Autoconfig features to achieve this in a simple and clean way, using only application properties.

我们将使用Spring Security OAuth的Autoconfig功能,以简单明了的方式实现这一目标,只使用应用程序属性。

3.1. Maven Dependency

3.1.Maven的依赖性

We’ll need to add the OAuth2 auto-configuration dependency to our Spring application’s pom file:

我们需要将OAuth2自动配置的依赖关系添加到我们Spring应用程序的pom文件中。

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.6.RELEASE</version>
</dependency>

As usual, we can check the latest version of the artifact in Maven Central.

像往常一样,我们可以在Maven中心检查工件的最新版本。

Note that this dependency is not managed by Spring Boot, and therefore we need to specify its version.

注意,这个依赖关系不是由Spring Boot管理的,因此我们需要指定其版本。

It should match the version of Spring Boot we’re using anyway.

反正它应该与我们所使用的Spring Boot版本相匹配。

3.2. Configuring the Resource Server

3.2.配置资源服务器

Next, we’ll have to enable the Resource Server features in our application with the @EnableResourceServer annotation:

接下来,我们必须用@EnableResourceServer注解在我们的应用程序中启用资源服务器功能。

@SpringBootApplication
@EnableResourceServer
public class ResourceServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResourceServerApplication.class, args);
    }
}

Now we need to indicate how our application can obtain the public key necessary to validate the signature of the JWTs it receives as Bearer tokens.

现在我们需要说明我们的应用程序如何获得必要的公钥,以验证它作为承载令牌收到的JWT的签名。

OAuth2 Boot offers different strategies to verify the token.

OAuth2 Boot提供了不同的策略来验证令牌。

As we said before, most Authorization servers expose a URI with a collection of keys that other services can use to validate the signature.

正如我们之前所说,大多数授权服务器公开了一个带有密钥集合的URI,其他服务可以用它来验证签名。

We’ll configure the JWK Set endpoint of a local Authorization Server we’ll work on further ahead.

我们将配置一个本地授权服务器的JWK Set端点,我们将进一步开展工作。

Let’s add the following in our application.properties:

让我们在我们的application.properties中添加以下内容。

security.oauth2.resource.jwk.key-set-uri=
  http://localhost:8081/sso-auth-server/.well-known/jwks.json

We’ll have a look at other strategies as we analyze this subject in detail.

在我们详细分析这个问题时,我们会看一下其他的策略。

Note: the new Spring Security 5.1 Resource Server only supports JWK-signed JWTs as authorization, and Spring Boot also offers a very similar property to configure the JWK Set endpoint:

注意:新的Spring Security 5.1资源服务器只支持JWK签名的JWT作为授权,而Spring Boot也提供了一个非常类似的属性来配置JWK Set端点。

spring.security.oauth2.resourceserver.jwk-set-uri=
  http://localhost:8081/sso-auth-server/.well-known/jwks.json

3.3. Spring Configurations Under the Hood

3.3.引擎盖下的Spring配置

The property we added earlier translates in the creation of a couple of Spring beans.

我们之前添加的属性在创建几个Spring Bean的过程中得到了转化。

More precisely, OAuth2 Boot will create:

更准确地说,OAuth2 Boot将创建。

  • a JwkTokenStore with the only ability to decode a JWT and verifying its signature
  • DefaultTokenServices instance to use the former TokenStore

4. The JWK Set Endpoint in the Authorization Server

4.授权服务器中的JWK设置端点

Now we’ll go deeper on this subject, analyzing some key aspects of JWK and JWS as we configure an Authorization Server that issues JWTs and serves its JWK Set endpoint.

现在我们将深入探讨这个问题,分析JWK和JWS的一些关键方面,因为我们配置了一个发布JWT和服务其JWK Set端点的授权服务器。

Note that since Spring Security doesn’t yet offer features to set up an Authorization Server, creating one using Spring Security OAuth capabilities is the only option at this stage. It will be compatible with Spring Security Resource Server, though.

请注意,由于Spring Security还没有提供设置授权服务器的功能,使用Spring Security OAuth功能创建一个授权服务器是现阶段唯一的选择。不过,它将与Spring Security资源服务器兼容。

4.1. Enabling Authorization Server Features

4.1.启用授权服务器功能

The first step is configuring our Authorization server to issue access tokens when required.

第一步是配置我们的授权服务器,以便在需要时发放访问令牌。

We’ll also add the spring-security-oauth2-autoconfigure dependency as we did with Resource Server.

我们还将添加spring-security-oauth2-autoconfigure 依赖项,就像我们在资源服务器上做的那样。

First, we’ll use the @EnableAuthorizationServer annotation to configure the OAuth2 Authorization Server mechanisms:

首先,我们将使用@EnableAuthorizationServer标注来配置OAuth2授权服务器机制。

@Configuration
@EnableAuthorizationServer
public class JwkAuthorizationServerConfiguration {

    // ...

}

And we’ll register an OAuth 2.0 Client using properties:

而我们将使用属性注册一个OAuth 2.0客户端。

security.oauth2.client.client-id=bael-client
security.oauth2.client.client-secret=bael-secret

With this, our application will retrieve random tokens when requested with the corresponding credentials:

有了这个,我们的应用程序将在请求时用相应的凭证检索随机令牌。

curl bael-client:bael-secret\
  @localhost:8081/sso-auth-server/oauth/token \
  -d grant_type=client_credentials \
  -d scope=any

As we can see, Spring Security OAuth retrieves a random string value by default, not JWT-encoded:

我们可以看到,Spring Security OAuth默认检索的是一个随机字符串值,而不是JWT编码的:

"access_token": "af611028-643f-4477-9319-b5aa8dc9408f"

4.2. Issuing JWTs

4.2.发布JWTs

We can easily change this by creating a JwtAccessTokenConverter bean in the context:

我们可以通过在上下文中创建一个JwtAccessTokenConverterbean来轻松改变这一点。

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    return new JwtAccessTokenConverter();
}

and using it in a JwtTokenStore instance:

并在一个JwtTokenStore实例中使用它。

@Bean
public TokenStore tokenStore() {
    return new JwtTokenStore(accessTokenConverter());
}

So with these changes, let’s request a new access token, and this time we’ll obtain a JWT, encoded as a JWS, to be accurate.

因此,有了这些变化,让我们请求一个新的访问令牌,这次我们将获得一个JWT,为了准确起见,编码为一个JWS。

We can easily identify JWSs; their structure consists of three fields (header, payload, and signature) separated by a dot:

我们可以很容易地识别JWS;它们的结构由三个字段组成(标题、有效载荷和签名),用一个点隔开。

"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  .
  eyJzY29wZSI6WyJhbnkiXSwiZXhwIjoxNTYxOTcy...
  .
  XKH70VUHeafHLaUPVXZI9E9pbFxrJ35PqBvrymxtvGI"

By default, Spring signs the header and payload using a Message Authentication Code (MAC) approach.

默认情况下,Spring使用消息验证码(MAC)方法对头和有效载荷进行签名。

We can verify this by analyzing the JWT in one of the many JWT decoder/verifier online tools we can find out there.

我们可以通过在许多JWT解码器/验证器在线工具中分析JWT来验证这一点,我们可以在那里找到。

If we decode the JWT we obtained, we’ll see that the value of the alg attribute is HS256, which indicates an HMAC-SHA256 algorithm was used to sign the token.

如果我们解码我们获得的JWT,我们会看到alg属性的值是HS256,这表明HMAC-SHA256算法被用来签署该令牌。

In order to understand why we don’t need JWKs with this approach, we have to understand how MAC hashing function works.

为了理解为什么我们用这种方法不需要JWK,我们必须了解MAC散列函数是如何工作的。

4.3. The Default Symmetric Signature

4.3.默认的对称签名

MAC hashing uses the same key to sign the message and to verify its integrity; it’s a symmetric hashing function.

MAC散列使用相同的密钥来签署信息并验证其完整性;它是一个对称散列函数。

Therefore, for security purposes, the application can’t publicly share its signing key.

因此,出于安全考虑,应用程序不能公开分享其签名密钥。

Only for academic reasons, we’ll make public the Spring Security OAuth /oauth/token_key endpoint:

只是出于学术原因,我们将公开Spring Security OAuth /oauth/token_key 端点。

security.oauth2.authorization.token-key-access=permitAll()

And we’ll customize the signing key value when we configure the JwtAccessTokenConverter bean:

而我们将在配置JwtAccessTokenConverterbean时自定义签名密钥值。

converter.setSigningKey("bael");

To know exactly which symmetric key is being used.

要准确了解正在使用的是哪种对称密钥。

Note: even if we don’t publish the signing key, setting up a weak signing key is a potential threat to dictionary attacks.

注意:即使我们不公布签名密钥,设置一个弱的签名密钥也是对字典攻击的一个潜在威胁。

Once we know the signing key, we can manually verify the token integrity using the online tool we mentioned before.

一旦我们知道了签名密钥,我们就可以使用我们之前提到的在线工具手动验证令牌的完整性。

The Spring Security OAuth library also configures a /oauth/check_token endpoint which validates and retrieves the decoded JWT.

Spring Security OAuth库还配置了一个/oauth/check_token端点,用于验证和检索解码的JWT。

This endpoint is also configured with a denyAll() access rule and should be secured consciously. For this purpose, we could use the security.oauth2.authorization.check-token-access property as we did for the token key before.

这个端点也被配置了denyAll() 访问规则,应该有意识地保护它。为此,我们可以使用security.oauth2.authorization.check-token-access属性,就像我们之前对token key做的那样。

4.4. Alternatives for the Resource Server Configuration

4.4.资源服务器配置的替代方案

Depending on our security needs, we might consider that securing one of the recently mentioned endpoints properly – whilst making them accessible to the Resource Servers – is enough.

根据我们的安全需要,我们可能会考虑适当地保护最近提到的一个端点–同时使它们能够被资源服务器访问–就足够了。

If that’s the case, then we can leave the Authorization Server as-is, and choose another approach for the Resource Server.

如果是这样的话,那么我们可以让授权服务器保持原样,而为资源服务器选择另一种方法。

The Resource Server will expect the Authorization Server to have secured endpoints, so for starters, we’ll need to provide the client credentials, with the same properties we used in the Authorization Server:

资源服务器将期望授权服务器有安全的端点,所以对于初学者来说,我们需要提供客户端凭证,其属性与我们在授权服务器中使用的相同。

security.oauth2.client.client-id=bael-client
security.oauth2.client.client-secret=bael-secret

Then we can choose to use the /oauth/check_token endpoint (a.k.a. the introspection endpoint) or obtain a single key from /oauth/token_key:

然后我们可以选择使用/oauth/check_token端点(又称自省端点)或从/oauth/token_key中获得一个单一的密钥。

## Single key URI:
security.oauth2.resource.jwt.key-uri=
  http://localhost:8081/sso-auth-server/oauth/token_key
## Introspection endpoint:
security.oauth2.resource.token-info-uri=
  http://localhost:8081/sso-auth-server/oauth/check_token

Alternatively, we can just configure the key that will be used to verify the token in the Resource Service:

另外,我们可以直接配置用于验证资源服务中的令牌的密钥。

## Verifier Key
security.oauth2.resource.jwt.key-value=bael

With this approach, there will be no interaction with the Authorization Server, but of course, this means less flexibility on changes with the Token signing configuration.

采用这种方法,将不会与授权服务器互动,但当然,这意味着在改变令牌签署配置方面的灵活性较低。

As with the key URI strategy, this last approach might be recommended only for asymmetric signing algorithms.

与钥匙URI策略一样,最后一种方法可能只推荐给非对称签名算法。

4.5. Creating a Keystore File

4.5.创建一个钥匙库文件

Let’s not forget our final objective. We want to provide a JWK Set endpoint as the most well-known providers do.

让我们不要忘记我们的最终目标。我们希望像最知名的供应商那样提供一个JWK Set终端。

If we’re going to share keys, it’ll be better if we use asymmetric cryptography (particularly, digital signature algorithms) to sign the tokens.

如果我们要共享密钥,最好使用非对称加密技术(尤其是数字签名算法)来签署令牌。

The first step towards this is creating a keystore file.

这方面的第一步是创建一个keystore文件。

One easy way to achieve this is:

实现这一目标的一个简单方法是。

  1. open the command line in the /bin directory of any JDK or JRE you have in handy:
cd $JAVA_HOME/bin
  1. run the keytool command, with the corresponding parameters:
./keytool -genkeypair \
  -alias bael-oauth-jwt \
  -keyalg RSA \
  -keypass bael-pass \
  -keystore bael-jwt.jks \
  -storepass bael-pass

Notice we used an RSA algorithm here, which is asymmetric.

注意我们在这里使用的是RSA算法,它是不对称的。

  1. answer the interactive questions and generate the keystore file

4.6. Adding the Keystore File to Our Application

4.6.将钥匙库文件添加到我们的应用程序中

We have to add the keystore to our project resources.

我们必须把钥匙库添加到我们的项目资源中。

This is a simple task, but keep in mind this is a binary file. That means it can’t be filtered, or it’ll become corrupted.

这是一个简单的任务,但请记住这是一个二进制文件。这意味着它不能被过滤,否则会被破坏。

If we’re using Maven, one alternative is to put the text files in a separate folder and configure the pom.xml accordingly:

如果我们使用Maven,一个替代方案是把文本文件放在一个单独的文件夹里,并相应地配置pom.xml

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources/filtered</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

4.7. Configuring the TokenStore

4.7.配置TokenStore

The next step is configuring our TokenStore with the pair of keys; the private to sign the tokens, and the public to validate the integrity.

下一步是用一对密钥配置我们的TokenStore;私人密钥用于签署令牌,而公共密钥用于验证完整性。

We’ll create a KeyPair instance employing the keystore file in the classpath, and the parameters we used when we created the .jks file:

我们将创建一个KeyPair 实例,采用classpath中的keystore文件,以及我们在创建.jks 文件时使用的参数。

ClassPathResource ksFile =
  new ClassPathResource("bael-jwt.jks");
KeyStoreKeyFactory ksFactory =
  new KeyStoreKeyFactory(ksFile, "bael-pass".toCharArray());
KeyPair keyPair = ksFactory.getKeyPair("bael-oauth-jwt");

And we’ll configure it in our JwtAccessTokenConverter bean, removing any other configuration:

我们将在我们的JwtAccessTokenConverterbean中配置它,删除任何其他配置。

converter.setKeyPair(keyPair);

We can request and decode a JWT again to check the alg parameter changed.

我们可以再次请求并解码JWT,以检查alg参数的变化。

If we have a look at the Token Key endpoint, we’ll see the public key obtained from the keystore.

如果我们看一下Token Key端点,我们会看到从钥匙库中获得的公钥。

It’s easily identifiable by the PEM “Encapsulation Boundary” header; the string starting with “—–BEGIN PUBLIC KEY—–.

它很容易被PEM“封装边界 “标头所识别;以”–BEGIN PUBLIC KEY–.开头的字符串。

4.8. The JWK Set Endpoint Dependencies

4.8.JWK设置端点的依赖性

The Spring Security OAuth library doesn’t support JWK out of the box.

Spring Security OAuth库不支持开箱即用的JWK。

Consequently, we’ll need to add another dependency to our project, nimbus-jose-jwt which provides some basic JWK implementations:

因此,我们需要向我们的项目添加另一个依赖项,nimbus-jose-jwt,它提供了一些基本的JWK实现。

<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>7.3</version>
</dependency>

Remember we can check the latest version of the library using the Maven Central Repository Search Engine.

记住,我们可以使用Maven Central Repository搜索引擎来检查库的最新版本。

4.9. Creating the JWK Set Endpoint

4.9.创建JWK设置端点

Let’s start by creating a JWKSet bean using the KeyPair instance we configured previously:

让我们先用我们之前配置的KeyPair实例创建一个JWKSetbean。

@Bean
public JWKSet jwkSet() {
    RSAKey.Builder builder = new RSAKey.Builder((RSAPublicKey) keyPair().getPublic())
      .keyUse(KeyUse.SIGNATURE)
      .algorithm(JWSAlgorithm.RS256)
      .keyID("bael-key-id");
    return new JWKSet(builder.build());
}

Now creating the endpoint is quite simple:

现在,创建端点是非常简单的。

@RestController
public class JwkSetRestController {

    @Autowired
    private JWKSet jwkSet;

    @GetMapping("/.well-known/jwks.json")
    public Map<String, Object> keys() {
        return this.jwkSet.toJSONObject();
    }
}

The Key Id field we configured in the JWKSet instance translates into the kid parameter.

我们在JWKSet实例中配置的Key Id字段转化为kid参数。

This kid is an arbitrary alias for the key, and it’s usually used by the Resource Server to select the correct entry from the collection since the same key should be included in the JWT Header.

这个kid是密钥的任意别名,而且它通常被资源服务器用来从集合中选择正确的条目,因为同一个密钥应该包含在JWT头中。

We face a new problem now; since Spring Security OAuth doesn’t support JWK, the issued JWTs won’t include the kid Header.

我们现在面临一个新的问题;由于Spring Security OAuth不支持JWK,所以发布的JWTs不会包括kid头。

Let’s find a workaround to solve this.

让我们找到一个解决这个问题的变通办法。

4.10. Adding the kid Value to the JWT Header

4.10.将kid值添加到JWT头中

We’ll create a new class extending the JwtAccessTokenConverter we’ve been using, and that allows adding header entries to the JWTs:

我们将创建一个新的,扩展我们一直在使用的JwtAccessTokenConverter,它允许向JWTs添加头条。

public class JwtCustomHeadersAccessTokenConverter
  extends JwtAccessTokenConverter {

    // ...

}

First of all, we’ll need to:

首先,我们需要。

  • configure the parent class as we’ve been doing, setting up the KeyPair we configured
  • obtain a Signer object that uses the private key from the keystore
  • of course, a collection of custom headers we want to add to the structure

Let’s configure the constructor based on this:

让我们在此基础上配置构造函数。

private Map<String, String> customHeaders = new HashMap<>();
final RsaSigner signer;

public JwtCustomHeadersAccessTokenConverter(
  Map<String, String> customHeaders,
  KeyPair keyPair) {
    super();
    super.setKeyPair(keyPair);
    this.signer = new RsaSigner((RSAPrivateKey) keyPair.getPrivate());
    this.customHeaders = customHeaders;
}

Now we’ll override the encode method. Our implementation will be the same as the parent one, with the only difference that we’ll also pass the custom headers when creating the String token:

现在我们将覆盖encode方法。我们的实现将与父方法相同,唯一不同的是,在创建String标记时,我们也将传递自定义头信息。

private JsonParser objectMapper = JsonParserFactory.create();

@Override
protected String encode(OAuth2AccessToken accessToken,
  OAuth2Authentication authentication) {
    String content;
    try {
        content = this.objectMapper
          .formatMap(getAccessTokenConverter()
          .convertAccessToken(accessToken, authentication));
    } catch (Exception ex) {
        throw new IllegalStateException(
          "Cannot convert access token to JSON", ex);
    }
    String token = JwtHelper.encode(
      content,
      this.signer,
      this.customHeaders).getEncoded();
    return token;
}

Let’s use this class now when creating the JwtAccessTokenConverter bean:

现在让我们在创建JwtAccessTokenConverterbean时使用这个类。

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    Map<String, String> customHeaders =
      Collections.singletonMap("kid", "bael-key-id");
    return new  JwtCustomHeadersAccessTokenConverter(
      customHeaders,
      keyPair());
}

We’re ready to go. Remember to change the Resource Server’s properties back. We need to use only the key-set-uri property we set up at the beginning of the tutorial.

我们已经准备好了。记住要把资源服务器的属性改回来。我们只需要使用我们在教程开始时设置的key-set-uri属性。

We can ask for an Access Token, check it’s kid value, and use it to request a resource.

我们可以要求一个访问令牌,检查它的kid值,并使用它来请求一个资源。

Once the public key is retrieved, the Resource Server stores it internally, mapping it to the Key Id for future requests.

一旦检索到公钥,资源服务器就会将其存储在内部,并将其映射到密钥标识上,供今后的请求使用。

5. Conclusion

5.总结

We’ve learned quite a lot in this comprehensive guide about JWT, JWS, and JWK. Not only Spring-specific configurations, but also general Security concepts, seeing them in action with a practical example.

在这份关于JWT、JWS和JWK的综合指南中,我们学到了不少东西。不仅是Spring特有的配置,还有一般的安全概念,通过一个实际的例子看到它们的作用。

We’ve seen the basic configuration of a Resource Server that handles JWTs using a JWK Set endpoint.

我们已经看到了一个资源服务器的基本配置,它使用JWK Set端点处理JWT。

Lastly, we’ve extended the basic Spring Security OAuth features, by setting up an Authorization Server exposing a JWK Set endpoint efficiently.

最后,我们扩展了Spring Security OAuth的基本功能,有效地设置了一个授权服务器,暴露了一个JWK Set端点。

We can find both services in our OAuth Github repo, as always.

我们可以一如既往地在我们的OAuth Github repo中找到这两项服务。