JWT(Json Web Token)

标签: jwt

什么是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下发流程

这里写图片描述

  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以及秘钥签名:
这里写图片描述

密钥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协议),从而避免敏感信息被嗅探。
版权声明:本文为qq_28988969原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_28988969/article/details/79899126