前言

该文主要带你了解什么是 JWT,以及JWT 定义和先关概念的介绍,并通过简单Demo 带你了解如何使用 SpringBoot 2 整合 JWT。
介绍前在这里我们来探讨一下如何学习一门新的技术,我个人总结为 RSA

  1. R:read 去读官方文档 。
  2. S:search 谷歌或百度先关技术文章或 github 去搜索先关信息。
  3. A:ask 可以向技术大牛请教或和自己的同事同学进行探讨。
关于 RSA 仅仅代码个人的学习观点,只是给读者一个不成熟的小建议哈

JWT 介绍

官网介绍如下:

What is JSON Web Token?
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.

中文翻译:
JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为JSON对象安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。JWTS可以使用秘密(使用HMAC算法)或公钥/私钥对使用RSA或ECDSA来签名。
虽然JWTS可以加密,但也提供保密各方之间,我们将重点放在签名令牌。签名的令牌可以验证包含在其中的声明的完整性,而加密的令牌隐藏这些声明以防其他各方。当令牌使用公钥/私钥对签名时,签名也证明只有持有私钥的方才是签名的方。

JWT 先关概念介绍

使用 JWT 前需要先了解三块内容:

  1. 头部信息
  2. 载荷信息
  3. 签名信息

头部信息

 头部信息由2部分组成

  1. 令牌的类型,即 JWT
  2. 使用的签名算法 ,例如HMACSHA256或RSA。

头部信息 JSON 代码如下:

{
  "alg": "HS256",
  "typ": "JWT"
}

然后,这个JSON被编码为 Base64Url,形成 JWT 的第一部分。

载荷信息

其中包含声明(claims),声明可以存放实体(通常是用户)和其他数据的声明,声明包括3种类型

  1. 已注册声明
  2. 公开声明
  3. 私有声明

已注册声明
这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。其中一些是: iss(发行人), exp(到期时间),sub(主题), aud(观众)

公开声明
可以参考 IANA JSON Web令牌注册表https://www.iana.org/assignme... 查看公共的声明

私有声明
根据根据自己的业务需要自定义的一些数据格式。
示例有效负载可以是:

{

"sub": "1234567890",

"name": "John Doe",

"admin": true

}

图片

签名信息

 这个部分需要 base64 加密后的 头部信息(header) 和 base64 加密后的载荷信息(payload),使用连接组成的字符串,然后通过头部信息(header)中声明的加密方式进行加盐 secret 组合在加密,然后就构成了 JWT 的第三部分。
例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

JWT 简单实用演示介绍

首先去 JWT 官网一探究竟,访问 https://jwt.io 进入 JWT 官网。接下来我们开始学习如果使用JWT,通过点击上图中 LEARN MORE ABOUT JWT 显示如下图:然后点击START USING THE TOOL。

图片![图片]

图片![图片]

如下图所示 选择对应的语言的 JWT 使用教程。我们这里介绍是是 标注有:maven:com.auth0 java-jwt 的版本。

图片

查看 JWT GitHub 示例教程和源码。 https://github.com/auth0/java-jwt

图片

在README.md 文件中有使用介绍和示例程序。

图片

图片

GitHub 上的示例相对简单些,接下来我带大家写一个相对详细的Demo。

第一步在SpringBoot 应用中引入JWT 依赖到pom.xml中

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.1</version>
</dependency>

我们通过三个方法来演示如何使用JWT

  1. createToken() 创建不携带自定义信息的 token
  2. createTokenWithClaim() 创建携带自定义信息的 token
  3. verifyToken() 验证我们的token信息并解析token中的内容

生成不携带自定义信息 JWT token

第一步:构建头部信息

Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");

第二步:构建密钥信息 

Algorithm algorithm = Algorithm.HMAC256("secret");

 第三步:我们通过定义注册和自定义声明 并组合头部信息和密钥信息生成jwt token

String token = JWT.create()
   .withHeader(map)// 设置头部信息 Header 
   .withIssuer("SERVICE")//设置 载荷 签名是有谁生成 例如 服务器
   .withSubject("this is test token")//设置 载荷 签名的主题
   // .withNotBefore(new Date())//设置 载荷 定义在什么时间之前,该jwt都是不可用的.
   .withAudience("APP")//设置 载荷 签名的观众 也可以理解谁接受签名的
   .withIssuedAt(nowDate) //设置 载荷 生成签名的时间
   .withExpiresAt(expireDate)//设置 载荷 签名过期的时间
   .sign(algorithm);//签名 Signature

详细代码如下:

   @Test
   public void createToken() {

       String secret = "secret";// token 密钥
       Algorithm algorithm = Algorithm.HMAC256("secret");

       // 头部信息
       Map<String, Object> map = new HashMap<String, Object>();
       map.put("alg", "HS256");
       map.put("typ", "JWT");

       Date nowDate = new Date();
       Date expireDate = getAfterDate(nowDate, 0, 0, 0, 2, 0, 0);// 2小过期
       
       String token = JWT.create()
           .withHeader(map)// 设置头部信息 Header 
           .withIssuer("SERVICE")//设置 载荷 签名是有谁生成 例如 服务器
           .withSubject("this is test token")//设置 载荷 签名的主题
           // .withNotBefore(new Date())//设置 载荷 定义在什么时间之前,该jwt都是不可用的.
           .withAudience("APP")//设置 载荷 签名的观众 也可以理解谁接受签名的
           .withIssuedAt(nowDate) //设置 载荷 生成签名的时间
           .withExpiresAt(expireDate)//设置 载荷 签名过期的时间
           .sign(algorithm);//签名 Signature
       Assert.assertTrue(token.length() > 0);
   }

生成携带自定义信息 JWT token

自定义信息通过 withClaim 方法进行添加,具体操作如下:

JWT.create()
   .withHeader(map)
   .withClaim("loginName", "zhuoqianmingyue")
   .withClaim("userName", "张三")
   .withClaim("deptName", "技术部")

生成携带自定义信息 JWT token 详细代码如下:

    @Test
    public String createTokenWithChineseClaim() {

        Date nowDate = new Date();
        Date expireDate = getAfterDate(nowDate, 0, 0, 0, 2, 0, 0);// 2小过期

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        Algorithm algorithm = Algorithm.HMAC256("secret");
        String token = JWT.create().withHeader(map)
                /* 设置 载荷 Payload */
                .withClaim("loginName", "zhuoqianmingyue").withClaim("userName", "张三").withClaim("deptName", "技术部")
                .withIssuer("SERVICE")// 签名是有谁生成 例如 服务器
                .withSubject("this is test token")// 签名的主题
                // .withNotBefore(new Date())//定义在什么时间之前,该jwt都是不可用的
                .withAudience("APP")// 签名的观众 也可以理解谁接受签名的
                .withIssuedAt(nowDate) // 生成签名的时间
                .withExpiresAt(expireDate)// 签名过期的时间
                /* 签名 Signature */
                .sign(algorithm);
        
        Assert.assertTrue(token.length() > 0);
        return token;

验证 JWT Token

第一步:构建密钥信息

 Algorithm algorithm = Algorithm.HMAC256("secret");

 第二步:通过密钥信息和签名的发布者的信息生成JWTVerifier (JWT验证类) 

JWTVerifier verifier = JWT.require(algorithm)
               .withIssuer("SERVICE")
               .build();
Algorithm algorithm = Algorithm.HMAC256("secret");
不添加 .withIssuer("SERVICE") 也是可以获取 JWTVerifier 。

第三步:通过JWTVerifier 的verify获取 token中的信息。 

DecodedJWT jwt = verifier.verify(token);

如下面代码所示就可以获取到我们之前生成 token 的 签名的主题,观众 和自定义的声明信息。

String subject = jwt.getSubject();
List<String> audience = jwt.getAudience();
Map<String, Claim> claims = jwt.getClaims();
for (Entry<String, Claim> entry : claims.entrySet()) {
   String key = entry.getKey();
   Claim claim = entry.getValue();
   System.out.println("key:"+key+" value:"+claim.asString());
}

验证 JWT Token 详细代码如下:

@Test
   public void verifyToken() throws UnsupportedEncodingException {
       String token = createTokenWithChineseClaim2();
       
       Algorithm algorithm = Algorithm.HMAC256("secret");
       JWTVerifier verifier = JWT.require(algorithm).withIssuer("SERVICE").build(); // Reusable verifier instance
       DecodedJWT jwt = verifier.verify(token);
       
       String subject = jwt.getSubject();
       List<String> audience = jwt.getAudience();
       Map<String, Claim> claims = jwt.getClaims();
       for (Entry<String, Claim> entry : claims.entrySet()) {
           String key = entry.getKey();
           Claim claim = entry.getValue();
           log.info("key:" + key + " value:" + claim.asString());
       }
       Claim claim = claims.get("loginName");

       log.info(claim.asString());
       log.info(subject);
       log.info(audience.get(0));

   }
   public String createTokenWithChineseClaim2() throws UnsupportedEncodingException {

       Date nowDate = new Date();
       Date expireDate = getAfterDate(nowDate, 0, 0, 0, 2, 0, 0);// 2小过期

       Map<String, Object> map = new HashMap<String, Object>();
       map.put("alg", "HS256");
       map.put("typ", "JWT");

       User user = new User();
       user.setUserNaem("张三");
       user.setDeptName("技术部");
       Gson gson = new Gson();
       String userJson = gson.toJson(user);

       String userJsonBase64 = BaseEncoding.base64().encode(userJson.getBytes());

       Algorithm algorithm = Algorithm.HMAC256("secret");
       String token = JWT.create().withHeader(map)

               .withClaim("loginName", "zhuoqianmingyue").withClaim("user", userJsonBase64).withIssuer("SERVICE")// 签名是有谁生成
               .withSubject("this is test token")// 签名的主题
               // .withNotBefore(new Date())//该jwt都是不可用的时间
               .withAudience("APP")// 签名的观众 也可以理解谁接受签名的
               .withIssuedAt(nowDate) // 生成签名的时间
               .withExpiresAt(expireDate)// 签名过期的时间
               .sign(algorithm);//签名 Signature

       return token;
   }

小结

JWT 就是一个生成 Token 的工具,如果不使用 JWT 我们也可以根据自己加密规则生成 Token。只不过 JWT 规范了生成 Token 定义了一个标准而已。JWT 的核心的功能就是:生成Token、解析Token。在 玩转 SpringBoot 2 之整合 JWT 下篇中将带大家通过一个接口登录的案例简单介绍 JWT 实战操作。

代码示例

具体代码示例请查看我的GitHub 仓库 springbootexamples 中的 spring-boot-2.x-jwt 下 src/test/java JWTDemo.java文件。

GitHub:https://github.com/zhuoqianmi...

参考文献

https://jwt.io/introduction/
https://github.com/auth0/java...


桌前明月
19 声望2 粉丝