65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 JJWT 库的 Java JWT 令牌教程

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2018年7月25日

MIT

8分钟阅读

viewsIcon

38097

downloadIcon

516

本文介绍如何使用 JJWT 库、密钥库、私钥/公钥来加密和解密 JWT 令牌。

引言

在本文中,我将提供一个非常简单的教程,介绍如何创建 JWT 令牌、如何加密令牌以及如何解密令牌。本文将简单有趣。虽然我不是主题专家,但我将展示以下内容

  • 如何创建 Java 密钥库
  • 如何从密钥库文件中提取公钥
  • 如何创建 JWT 令牌
  • 如何使用密钥库中的私钥加密 JWT 令牌
  • 如何使用导出的公钥解密加密的 JWT 令牌

你可能会问,这样做有什么目的?我有一个个人项目,将实现为一套微服务。其中一个微服务将是一个认证服务。当请求进入我的微服务生态系统时,请求将首先得到认证。用户凭据将首先发送到认证和授权服务,该服务可以在数据库中查找用户并验证凭据,然后发送一个包含用户信息的加密 JWT 令牌。负责处理请求的服务将在请求被处理之前解密 JWT 令牌并验证用户的授权。授权检查将查看用户的角色以及用户角色是否足以处理请求。

为了做到这一切,我们需要一对密钥——RSA 私钥/公钥对。认证服务使用私钥来加密令牌。处理用户请求的服务需要解密令牌,可以使用公钥来完成。在此过程中,我们需要找到一种方法来创建未加密的 JWT 令牌。所有这些将在本文中进行解释。

创建 Java 密钥库

Java 密钥库文件是证书(公钥)和相应私钥的存储库。它们可用于 SSL 传输。您可能会注意到的一个 Java 密钥库文件的例子是,J2EE 应用程序容器(如 Jetty、Glassfish、TomCat)可以通过某些 XML 配置使用它。然后,您将为这些应用程序容器启用 HTTPS。

对于本教程,我们只需要知道如何创建一个 Java 密钥库文件,其中包含一个 RSA 证书及其对应的私钥。要创建一个新的 Java 密钥库文件,请使用以下命令

keytool -genkey -alias hanbotest -keyalg RSA -keystore hanbotest.jks -keysize 2048

上述命令的作用是创建一个包含一个 RSA 密钥的密钥库。密钥的别名是“hanbotest”。用于创建密钥的算法是 RSA。密钥的大小是 2048 位。密钥库的默认格式是 JKS。

键入命令并按 Enter 键后,它会提示您输入以下信息

  1. 密钥库的密码,我使用的是“123test321”。
  2. 再次输入相同的密码。
  3. 名字和姓氏,我输入的是“Han Bo”。
  4. 组织单位名称,我输入的是“Hanbo Enterprise”。
  5. 组织名称,我再次输入“Hanbo Enterprise”。
  6. 城市名称,我输入的是“Chicago”。
  7. 州或省名称,我输入的是“IL”。
  8. 国家的两个字符,我输入的是“US”。
  9. 最后一个是“[no]”,我输入“yes”并按 Enter 键。
  10. 最后的最后是证书和私钥的密码。我将其与密钥库的密码设置为相同。只需按 Enter 键。
  11. 查看目标目录,您将看到密钥库文件。

不用担心您输入的信息。这是一个模拟证书,仅用于测试。我已经将这个模拟密钥库包含在示例源代码中。一旦获得它,您就可以将其替换为您自己的密钥库文件并进行源代码的演示。

导出公钥/证书

现在您有了一个密钥库文件。下一步是将公钥导出为一个单独的实体。私钥是需要保护的,而公钥可以与其他方共享。要提取公钥,请使用以下命令

keytool -export -keystore hanbotest.jks -alias hanbotest -file hanbotest.cer

此命令的作用是指定 jks 文件、密钥的别名以及输出文件,该文件名为“hanbotest.cer”。键入命令并按 Enter 键后,它会提示输入密码。输入密码“123test321”。.cer 文件将成功创建。

现在它们可用于 JWT 令牌的加密和解密。

加密和解密 JWT 令牌

我编写了一个简单的 Java 控制台应用程序,演示了使用私钥加密 JWT 令牌。加密后它将是一个string。然后我取string,并使用公钥解密加密的令牌。

POM 文件

由于这是一个简单的 Java 控制台程序,POM 文件非常直观。最重要的是依赖项。它们在这里

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.1</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>${jackson.version}</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>${jackson.version}</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>${jackson.version}</version>
</dependency>

jackson 库的版本是 2.7.3。我在同一 pom 文件中的一个单独部分中定义了它,如下所示

<properties>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <jackson.version>2.7.3</jackson.version>
</properties>

关于这一点还有一件事,在 JJWT 官方库教程中,它说不需要包含任何其他依赖项。这是不正确的。我不得不包含三个 jackson jar 文件。否则应用程序将无法工作。

主程序

主程序非常简单。它是一个类,带有一个主入口和一个static辅助方法。它所做的就是创建 JWT 令牌,加密,然后解密。目的是演示 JWT 令牌的创建和保护方式。

我使用JJWT库。创建 JWT 令牌非常容易。这是代码

String compactJws = Jwts.builder()
   .setSubject("Joe")
   .setAudience("testAudienceId")
   .setExpiration(dateExp)
   .setIssuedAt(dateNow)
   .setId("testuserid")
   .signWith(SignatureAlgorithm.RS512, key)
   .compact();
   
System.out.println(compactJws);

有两件事您必须注意

  • setExpiration()应该设置一个比setIssuedAt()Date对象更晚的Date对象。
  • signWith()使用私钥对 JWT 令牌进行签名。这就是我们应该使用私钥的地方。

问题是,我们如何加载私钥。结果发现,这并不难

String jksPassword = "123test321";
         
KeyStore ks  = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream("C:\\DevJunk\\jwt\\keystore\\hanbotest.jks"), jksPassword.toCharArray());
Key key = ks.getKey("hanbotest", jksPassword.toCharArray());

第一行定义了代表密码的string。第二行获取一个KeyStore实例,并指定使用的类型是默认类型。JKS 有许多不同的类型,您可以使用。除了 Java 开箱即用的默认类型外,还有 PKCS12 格式以及许多其他格式。在这种情况下,我只是使用默认类型。第三行从 JKS 文件加载KeyStore。要加载,必须提供与 JKS 文件关联的密码。最后一行从KeyStore对象中提取私钥。名为“key”的对象是在前面的代码示例中signWith()使用的私钥。

如果只运行带有这两段代码的程序,您将看到类似以下的输出

eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJKb2UiLCJhdWQiOiJ0ZXN0QXVkaWVuY2VJZCIsImV4cCI6MTUzMjM5OTI4NCwiaWF0I
joxNTMyMTQwMDg0LCJqdGkiOiJ0ZXN0dXNlcmlkIn0.L9w54tGTWGa720T1TqCf7T5c-JnT0RI1kyK1vUmuuEfcX8wmwWnvZk2
Ybm4eEyXZHP5tBLWHtCVdZOPqLwdjDzZpr10Q5AmjasTE9FJqnzQVLvd5cJOKFK9VAXAc_hA4ZxP6mTM802vssNGfuVseqFg1a
r08uD-jbRRnN8DqegzEV9z0yJC-TNuUUkQBo5Rj2GKVKWyTi5MkPPL2aPsY7JExDj7QraYvaKl57XBwT2XRGefOaA0mXuCxcJj
xZv638LupQl9c1HcS7qTOSlV7V-h-QLqanDXZkKt-xhwR1hilEPCYjVyGPSDMWVYlLtxSNWXvFji1TI2baFD7i9fpOQ

string包含三个加密部分。它们由两个“.”分隔。这些部分是令牌头、正文和签名。最后一部分可用于验证 JWT 令牌是由合法的私钥生成的。

程序的下一部分是将string解密回 JWT 令牌对象。这是使用公钥完成的。为了做到这一点,我们首先需要加载公钥。这是本教程中唯一困难的部分。我写了一个非常简单的辅助方法

public static PublicKey loadPublicKey(String filename)
   throws Exception
{
   CertificateFactory cf = CertificateFactory.getInstance("X.509");
   Certificate cert = cf.generateCertificate(new FileInputStream(filename));
   PublicKey retVal = cert.getPublicKey();
   return retVal;
}

此辅助方法接受.cer 文件(我刚刚创建的)的完整文件路径,并返回一个PublicKey对象。该方法有 4 行

  1. 首先,创建一个CertificateFactory对象。证书的类型由string指定。这可能是所有中最难的。类型实际上是“X.509”。
  2. 下一行获取实际.cer 文件的FileInputStream,并使用CertificateFile对象创建Certificate对象。
  3. 第三行从Certificate对象中提取PublicKey对象。
  4. 最后一行将PublicKey对象返回给调用者。

一旦有了PublicKey对象,我们就可以获取加密的string并使用 JJWT 库尝试对其进行解码。以下是操作方法

PublicKey publicKey = loadPublicKey("C:\\DevJunk\\jwt\\keystore\\hanbotest.cer");

Jws<Claims> x = Jwts.parser()
   .setSigningKey(publicKey)
   .parseClaimsJws(compactJws);

这里只有两行。第一行使用辅助方法从.cer 文件获取公钥。第二行使用JJWT库方法解密 JWT 令牌。返回的对象是Jws<Claims>类型。compactJws是保存加密 JWT 令牌的string

令牌被解密后,将有三个不同的部分。

  • JWT 令牌的头
  • JWT 令牌的正文——用户凭据存储在这里
  • JWT 令牌签名

如果令牌可以正确解密,那么我们关心的是令牌的正文部分。以下是我输出并检查其是否正确解密的方法

String id = x.getBody().getId();
System.out.println("id: " + id);
System.out.println("audience: " + x.getBody().getAudience());
System.out.println("audience: " + x.getBody().getSubject());

id、受众和主题的输出与创建 JWT 令牌时使用的输入值相同。

关注点

正如我所承诺的,我保持了这篇文章的简短易懂、简单有趣。我在本文中所做的所有事情都是

  • 创建一个仅包含一个 RSA 密钥的 JKS 密钥库。
  • 将 JKS 密钥库文件中的公钥证书导出为.cer 文件。
  • 一个示例程序,该程序创建 JWT 令牌,使用私钥对其进行加密,并使用公钥(导出的证书)解密令牌。

这并不算多,但它可以扩展并集成到实际的 Web 应用程序中,其中将有一个服务专门用于身份验证和创建 JWT 令牌,而其他服务可以消费和验证 JWT 令牌。它只是让 Web 应用程序的开发变得更加有趣。

历史

  • 2018年7月21日 - 初稿
© . All rights reserved.