JWT详解及项目实战

567 阅读5分钟

目录

本文将从以下几点来详解JWT

  • 什么是JWT?
  • 为什么要用到JWT?
  • HTTP无状态协议
  • Cookie-Session机制
  • Cookie-Session和JWT对比?
  • JWT的组成?
  • Header
  • Payload
  • Signature
  • JWT代码使用
  • 引入依赖
  • 生成JWT
  • SHA算法生成JWT
  • RSA算法生成JWT
  • 总结

1、什么是JWT?

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.

JSON Web Token(JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对等进行签名。

2、为什么要用到JWT?

2.1、HTTP无状态协议

HTTP是无状态的协议,对于交互场景没有记忆能力。 这就好比小明去银行取钱,银行柜员会要求小明提供银行卡,并会要求小明输入密码,小明才能取到钱,下次小明还想取钱时还是要提供银行卡和密码才能取到钱。程序中亦是如此,银行卡相当于登录网站的用户名,密码相当于用户密码,登陆完成,如果不通过一些方法让HTTP “有记忆能力”,下次的请求还是要提供账号密码才知道是谁在操作,当然我们不会让程序如此。

2.2、Cookie-Session机制

在之前,这种记忆能力是通过Cookie-Session机制实现的。用户密码验证成功后,服务端生成sessionId,并且在session对象中存储当前用户信息,然后服务端将sessionId写入客户端cookie中,当客户端下次访问服务器端时cookie会被自动发送给服务器端,服务器端在cookie中拿到sessionId然后在服务器端的session对象中查找sessionId进行验证,验证成功说明用户是登陆状态,则可以实现所谓的记忆能力

2.3、Cookie-Session和JWT对比?

  • sessionId是存储在服务端的,现在分布式的流行使得如果使用sessionId就要实现sessionId多机数据共享,即通过一些手段使得每个微服务中都能获取到这个sessionId对应的用户信息。JWT是存储在客户端无状态的,分布式环境下有明显的优势。
  • 如果客户端不支持cookie机制(用户可以手动禁用),则无法携带sessionId,Cookie-Session机制则无法使用。JWT是用请求头的方式传递,不存在这个问题。
  • sessionId可以实现续期的,而JWT是一次性的无法修改,除非再次颁发JWT
  • cookie是有作用域的(当然一级域名和二级域名之间是允许共享使用),JWT没有这个限制。
  • CSRF(Cross Site Request Forgery)一般被翻译为 跨站请求伪造,是依赖cookie的一种网络攻击,JWT一般是存储在浏览器的localStorage,所以可以避免CSRF

总之,二者各有千秋。二者的劣势也可以通过一些设计来解决。比如sessionId多机共享可以通过存储Redis来解决;JWT一经颁发无法过期,也可以通过存储Redis设置过期时间来解决。

3、JWT的组成?

JWT是由 . 分割的三部分,分别是标头(Header)、载体(Payload)和签名(Signature)。

Header.Payload.Signature

3.1、Header

标头通常由两部分组成:令牌的类型(即 JWT)和正在使用的签名算法,例如 HMAC SHA256 或 RSA。

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

然后对表头进行 Base64Url 编码以形成JWT的第一部分。

3.2、Payload

JWT的第二部分是载体,其中包含声明。声明是有关实体(通常是用户)和其他数据的语句。有三种类型的声明:已注册、公共和私人声明。

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

请注意,声明名称只有三个字符长,因为 JWT 应该是紧凑的。

  • 公共声明:这些可以由使用 JWT 的人随意定义。但为了避免冲突,这些可以由使用 JWT 的人随意定义。但为了避免冲突,它们应该在 IANA JSON Web 令牌注册表中定义,或者定义为包含抗冲突命名空间的 URI。
  • 专用声明:这些是创建的自定义声明,用于在同意使用它们的各方之间共享信息,既不是注册声明也不是公共声明。
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

然后对载体进行 Base64Url 编码以形成JWT的第二部分。

3.3、Signature

将头部和载体部分通过标头中的算法和密钥签名后形成第三部分签名

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

例如,如果要使用 HMAC SHA256 算法,将按以下方式创建签名:

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

然后对签名进行 Base64Url 编码以形成JWT的第三部分。

将这三部分用 . 连接后获取到一个字符串,这个就是JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

4、JWT代码使用

4.1 引入依赖

JWT的不同实现,可以参考JWT详细介绍

这里使用的maven依赖是Les Haziewood实现的 jjwt。

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

4.2 生成JWT

演示两种算法生成JWT的例子。

4.2.1 SHA算法生成JWT

@Test
void shaJwt() {
    // 使用SHA-256算法
    Map<String,String> claimMap = new HashMap<>();
    claimMap.put("sub","Joe");
    String jwtStr = Jwts.builder()
            .setClaims(claimMap)
            .signWith(Keys.secretKeyFor(SignatureAlgorithm.HS256), SignatureAlgorithm.HS256)
            .compact();
    System.out.println(jwtStr);
}

生成结果:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.8JvQJJ9mZpWowpOe3ppzApRZkps_kLa8nmdfDxpq9jE

4.2.2 RSA算法生成JWT

@Test
    void rsaJwt() {
        // 使用RSA-256算法
        Map<String,String> claimMap = new HashMap<>();
        claimMap.put("sub","Joe");
        String jwtStr = Jwts.builder()
                .setClaims(claimMap)
                .signWith(Keys.keyPairFor(SignatureAlgorithm.RS256).getPrivate(), SignatureAlgorithm.RS256)
                .compact();
        System.out.println(jwtStr);
}

生成结果:

eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJKb2UifQ.uy9jxz-MHWBrsb0aIjR-iY6a-rYUcXH_IsTmJAAHpAgqyJmGYRp7-stcqbu_qmnGwImfHPmhInSpXcuZ5bxutMCSJOFHIfOel5Y_4BlQgnf_vBwqP3iEULDhwIW-zCLuIDe3w6lqavxZYxFD4uDgYpZ6anbjHKBJal_qpG648qcVkaL66EI8TXhFSAAn7Cw9GZajc_Fv7xW9FEk-4U90zGSgkeSi2DWqgAJ0Ow0KRzd5VZ_moWp2vuV_39PDVmlXgDdcVvz3LEThVUqTQcdC30tHeJmEyzflXtDrbJweenmJcY5MPT69vcosIAMXHrsJ6vSFsAisgpuOqnsjE_8fYQ

5、总结

  • JWT是由标头、载体和签名三部分组成。
  • JWT是无状态的,一经颁发无法修改。
  • JWT可以跨域。
  • JWT可以防护CSRF攻击。
  • JWT可扩展性强。