欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JWT(Json Web Token)

程序员文章站 2022-06-13 20:06:40
...

什么是JWT(Json网络令牌)

JWT(JSON 网络令牌)是一个开源的标准(RFC 7519)。这个标准定义了一个简洁并且自包含的方法,以便在系统各个部分之间安全地传送JSON对象格式的信息。因为通过JWT传递的信息是被数字签名过的,所以是可以被证实和信任的。我们可以使用单一**来加密 JWT 的签名,比如 HMAC 算法;也可以使用 RSA 的公私**对来加密 JWT 的签名。

让我们更深入地来解释一下这个定义中的一些概念:

  • 简洁:因为 JWT 体积比较小,我们可以通过 URL,POST参数或者 HTTP 报头来发送 JWT。另外,较小的体积意味着发送速度比较快。
  • 自包含:装载的数据包含和用户相关的所有需要的信息,避免了额外再查询一次数据库的需求。

什么时候使用JWT

  • 身份验证:这是 JWT 最常用的场景。一旦用户登录进系统,每一个后继的请求都会包含 JWT。系统允许用户访问那些令牌有权限的路由,服务和资源。单点登录是一个在当代广泛使用 JWT 的特性,这是因为下面两点原因:其一是 JWT 的系统开销很小;其二是 JWT 能够轻而易举地应用到跨域的场景中。
  • 信息交换:JWT 是一种在系统各个部分之间安全地传送信息的方法。这是因为只要 JWT 可以被数字签名,比如使用公私**对,你就可以信任 JWT 中包含的发送者身份信息。除此之外,当签名在计算的时候,使用了 JWT 的头部部分和负载部分,你也可以验证信息的内容有没有被篡改过。

与Session区别

  • Session 通过 cookie 传输,存储在服务器,服务器通过 cookie 中的 sessionID 获取当前会话用户,对于单台服务器没问题,但多服务器就涉及到共享 Session ,而且认证用户多时 Session 会占用大量服务器内存
  • JWT 存储在客户端,服务器不需要存储 JWT ,JWT 里有用户 ID,服务器拿到 JWT 后验证可以获得用户信息,也就实现了 Session 功能,但是是无状态的,只要签名秘钥足够安全就能保证 JWT 可靠性

JWT下发流程

JWT(Json Web Token)

  1. 用户使用用户名/密码,向认证服务器请求。
  2. 认证服务器认证通过后,生成JWT token返回给用户
  3. 用户向应用服务器调用API请求资源时都需添加JWT token
  4. 应用服务器接收请求后先取出JWT token进行校验,通过后返回有效资源,否则返回错误。

JWT何时下发

  • 登录下发新token,原token实际上并没有失效,保证多端登录没问题.
  • Token剩余有效时长大于可续期时长(根据业务平台自己定)时重新下发.举个栗子针对WEB端Token24小时有效,当有效时间小于12小时刷新Token,也就是当用户连续12小时没操作网站才会被退出.
  • 修改或者重置密码时下发新Token,并吊销之前的Token

JWT吊销

JWT不需要在服务端存储,因此吊销是个大问题,无法吊销的话就会出现用户密码被盗,即使用户修改了密码,其他人也并不会立即失效,这点在安全性很高的地方几乎是不允许的情况.因此吊销是必要的.

JWT的结构

JWT包含了使用.分隔的三部分:

  • Header 头部
  • Payload 负载
  • Signature 签名

其结构看起来是这样的

Header.Payload.Signature

Header
jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法,通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

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

接下来对这部分内容使用 Base64Url 编码组成了JWT结构的第一部分

Payload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解码.

私有的声明 :

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为是base64编码,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
    "name":"demo",
    "age":"28"
}

上述的负载需要经过Base64Url编码后作为JWT结构的第二部分。

Signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

创建签名需要使用编码后的header和payload以及一个秘钥,使用header中指定签名算法进行签名。

例如如果希望使用HMAC SHA256算法,那么签名应该使用下列方式创建:

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

签名用于验证消息的发送者以及消息是没有经过篡改的。 完整的JWT 完整的JWT格式的输出是以. 分隔的三段Base64编码,与SAML等基于XML的标准相比,JWT在HTTP和HTML环境中更容易传递。

下列的JWT展示了一个完整的JWT格式,它拼接了之前的Header, Payload以及秘钥签名:
JWT(Json Web Token)

**secret是保存在服务端的,服务端会根据这个**进行生成token和验证,所以需要保护好。

JAVA实现JWT

添加依赖

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

<!-- 时间处理,不是必须的,这里只是为了方便时间处理 -->
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
</dependency>

java代码

package jwt;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import org.joda.time.DateTime;
import org.junit.Test;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

/**
 * 
 * @ClassName: TokenTest
 * @Description: 测试
 * @author cheng
 * @date 2018年4月11日 下午3:34:41
 */
public class TokenTest {

    /**
     * 自定义**
     */
    private String secret = "AHUT";

    @Test
    public void testToken() throws IllegalArgumentException, JWTCreationException, UnsupportedEncodingException {
        /**
         * 头部
         */
        Map<String, Object> map = new HashMap<>();
        map.put("typ", "JWT");
        map.put("alg", "HS256");

        /**
         * 当前时间
         */
        DateTime now = DateTime.now();

        String token = JWT.create()
                .withHeader(map)
                .withClaim("name", "rick")
                .withClaim("age", "23")
                .withExpiresAt(now.plusSeconds(10).toDate())
                .withIssuedAt(now.toDate())
                .sign(Algorithm.HMAC256(secret));

        System.out.println(token);
    }

    @Test
    public void testToken1() throws IllegalArgumentException, UnsupportedEncodingException {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicmljayIsImV4cCI6MTUyMzQzNTY0NSwiaWF0IjoxNTIzNDM1NjM1LCJhZ2UiOiIyMyJ9.Lk5e1K9HmumBM4auM-Y7UX-_miMCvnO7R_f8A547EGI";

        JWTVerifier build = JWT.require(Algorithm.HMAC256(secret)).build();
        DecodedJWT jwt = null;
        try {
            jwt = build.verify(token);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("凭证已经过期");
        }
        Map<String, Claim> map = jwt.getClaims();
        for (String key : map.keySet()) {
            Claim claim = map.get(key);
            System.out.println(key + ":" + claim.asString());
        }
    }

}

JWT优点

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

JWT安全

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
相关标签: jwt