写于:2018-12-24 08:52:37

# JSON Web Token

JWT 规范

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties.

The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as

the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

JWT 是一种双方 URL 安全信息传输的协议(约定)。 JWT 是一串加密的 JSON 编码,可以是一串用作 web 认证的json串,可以是一串进行认证的明文,也可以是一串加密的认证信息。

# 传统 Session 认证

在服务请求中,我们通过 HTTP 协议进行 服务器资源,信息,数据的请求。而 HTTP 属于无状态的请求,换句话说就是,HTTP 的每一次请求对于 服务器来说都是一致,服务器无法区分 HTTP 请求来源。 为了解决 区分每次 HTTP 请求属于哪个 用户, 服务器通过维护一个列表数据,列表数据主要由两部分组成:用户身份信息 以及 凭证。 通过 cookie 的方式将凭证返回给客户端,客户端每次请求携带该凭证,服务通过 凭证 校验并获取 用户身份信息。

# JWT 认证

在 REST 成为 主流,无状态的开发 也 渐渐成为了主流。JWT 和 HTTP 一样 都是一种无状态 的协议。在用户第一次进行身份认证之后,服务器 返回给 用户一个唯一的加密的身份标识,用户每次请求携带这个标识进行访问。

# 对比 Session 认证 和 JWT 认证

对于 JWT 来说,用户身份凭证 token 一旦签发,就无法收回,在限定的优先期限内该token仍然有效,并且 服务器只认 token ,也就是如果 token 被窃取,别人仍然可以访问。

对session 来说,session 需要维护一个身份列表。

在分布式环境中,由于 seesion 认证 需要存放一个 身份认证列表,所有 涉及到的,如:session 共享的问题。 而 JWT 在分布式环境中不存在这类问题。

其实对于技术而言,并没有所谓的好坏之分,技术只有使用和不适用。 针对于 例如 session 共享的问题,企业应用中 也已经有了很多的解决方案,例如通过单点登录等方式进行解决。而 对于安全而言,并没有所谓的 铜墙铁壁,针对于 session 中 cookiie 的丢失导致的跨站请求伪造的攻击。 其实 JWT 一样存在安全问题,例如 加解密 秘钥的泄露等。而针对于 token 的丢失问题导致的安全问题,并不是 JWT 的安全问题,而是网络存在的不确定的安全因素导致的。就是像是,你的钥匙丢了,导致你家失窃了,总不能怪锁是不安全的吧。 我们能做的就是尽可能保证安全,例如 通过,https 访问 等措施来进行有效的防范。

# JWT 结构构成

根据协议,JWT 结构通过 “.” 进行分隔,就像: xxxxx.yyyyy.zzzzz 。而其对应的数据结构

Header : xxxxx payload : yyyyy Signature : zzzzz

  • Header

    {
      "alg": "HS256",	// 声明 签名算法
      "typ": "JWT"		// 声明 token 类型为 JWT
    }
    
  • payload

    用来承载token相关声明信息,包括注册声明、公共声明,私有声明。

    {
      // 公共声明 - > 可以添加任何信息,该信息能够在客户端解密
      "author":"qguofeng",
      
    
      // 私有声明 - > 
    
      "userId": true,		//
      
      //=============== 注册声明
      "sub": "1234567890",	// Subject
      "iss": "",			// Issuer
      "aud":"",				// Audience
      "exp":"",				// Expiration Time
      "nbf":"",				// Not Before
      "iat":"",				// Issued At
      "jti":"",				// WT ID
    }
    
  • Signature

    签证信息,这个签证信息由三部分组成

    base64后的 Header base64后的 payload secret

    这个部分需要base64加密后的header和base64加密后的payload使用“.”连接组成的字符串,然后通过header中声明的加密方式进行加secret组合加密,然后就构成了jwt的第三部分

# DEMO

/**
 * @Description <p>JWT 生成工具类</p>
 * @Author QGUOFENG
 */
public class JwtToken {
    /** token秘钥,qguofneg */
    public static final String SECRET = "qguofneg";
    /** token 过期时间:  */
    public static final int calendarField = Calendar.SECOND;
    public static final int calendarInterval = 60;

    /**
     * JWT生成Token.<br/>
     * JWT构成: header, payload, signature
     * @param userId
     */
    public static String createToken(Long userId) throws Exception {
        Date iatDate = new Date();
        // expire time
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(calendarField, calendarInterval);
        Date expiresDate = nowTime.getTime();

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

        // build token
        // param backups {iss:Service, aud:APP}
        String token = JWT.create().withHeader(map)     // header
                .withClaim("iss", "Service") // payload
                .withClaim("aud", "APP")
                .withClaim("userId", null == userId ? null : userId.toString())
                .withIssuedAt(iatDate)                  // sign time
                .withExpiresAt(expiresDate)             // expire time
                .sign(Algorithm.HMAC256(SECRET));       // signature
        return token;
    }

    /**
     * 解密Token
     *
     * @param token
     * @return
     * @throws Exception
     */
    public static Map<String, Claim> verifyToken(String token) {
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            jwt = verifier.verify(token);
        } catch (Exception e) {
        }
        // 如果 token 已经过期,则 返回为空
        if(jwt == null){
            // 暂时返回空,可以进行其他的操作:例如 抛出自定义异常
            return null;
        }
        return jwt.getClaims();
    }

    /**
     * 根据Token获取user_id
     *
     * @param token
     * @return user_id
     */
    public static Long getAppUID(String token) {
        Map<String, Claim> claims = verifyToken(token);
        if(null == claims || claims.isEmpty()){
            // 暂时返回空,可以进行其他的操作:例如 抛出自定义异常
            return null;
        }
        Claim user_id_claim = claims.get("userId");
        if (null == user_id_claim || StringUtils.isEmpty(user_id_claim.asString())) {
            // 暂时返回空,可以进行其他的操作:例如 抛出自定义异常
            return null;
        }
        return Long.valueOf(user_id_claim.asString());
    }


    public static void main(String[] args) throws Exception {
        String token = createToken(1100L);
        System.err.println(token);
        Map<String, Claim> stringClaimMap = verifyToken(token);
        Long appUID = getAppUID(token);
        System.err.println(appUID);
    }
}
精彩内容推送,请关注公众号!
最近更新时间: 3/24/2020, 9:44:42 PM