【JWT】JSON Web Token原理与实现

目录

  一、简介

  二、应用场景

  三、组成结构

  四、特点

  五、代码实现【java】

 一、简介

  JSON Web Token(JWT)是为了在网络应用环境间传递声明的一种基于JSON的开放标准(RCF 7519),它定义了一种紧凑(Compact)且自包含(Self-contained)的方式,用于在各方之间以JSON安全地传输信息。由于这些信息是经过数字前面的,因此可以被验证和信任。可以使用密钥(使用HMAC算法)或使用RSA的公钥/私钥对对JWT进行签名。JWT的声明一般被用来传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加额外的其他业务处理所必须的声明信息,该token可直接被用于认证,也可被加密。是目前最流行的跨域认证解决方案。

  • compact(紧凑):由于它们内容较小,JWT可以通过URL,POST参数或HTTP的头信息内发送,另外,内容越小意味着传输速度越快。
  • Self-contained(自包含):有效载荷(Playload)包含有关用户的所有必需信息,避免了多次查询数据库。

 

二、应用场景

 Authentication(授权) :JWT最常见的作用就是授权。一旦用户登录,每个后续请求将包含JWT,从而允许用户访问该token允许的路由,服务和资源。

Information Exchange(信息交换):JWT是在各方之间安全地传输信息的方案。因为可以对JWT进行签名(例如,使用公私密钥对),所以您可以确认发件人是否是真正的发件人,另外,由于会对头部(header)和载荷(payload)进行数字签名,因此您还可以验证内容是否未被篡改。

三、组成结构

JWT由以下三个部分组成,中间用( . )分隔

  • Header(头部)
  • Payload(载荷)
  • Signature(签名)

写成一行,就是下面的样子

 Header.Payload.Signature

1、Header

 Header部分是一个JSON对象,描述JWT的元数据,通常是下面这个样子的

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

上面代码中 alg属性表示签名的算法,默认是HMACSHA256(写成HS256);

typ属性表示这个令牌(token)的type,JWT令牌统一写JWT

最后,将上面的JSON对象使用Base64URL算法转成字符串

2、Payload

Payload部分也是一个JSON对象,用来存放实际需要传递的数据。也需要使用Base64URL编码

JWT规定了7个官方字段,供选用

  • iss(issuer):jwt签发者
  • sub(subject):jwt所面向的用户
  • aud(audience):接收jwt的一方,受众
  • exp(expiration time):jwt的过期时间,这个过期时间必须大于签发时间
  • nbf(Not Before):生效时间,定义在什么时间之前
  • iat(Issued At):jwt的签发时间
  • jti(JWT ID):jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

除了官方字段,你还可以在这个部分自定义字段,下面就是一个例子

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

注意:JWT中的payload是不加密的,只是Base64URL编码一下,任何人拿到都可以进行解码,所以不要把敏感信息放在这个部分

3、Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

var header = Base64URL({ "alg": "HS256", "typ": "JWT"});
var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022});
var secret = "私钥";
#签名 var signature
= HMACSHA256(header + "." + payload, secret); var jwt = header + "." + payload + "." + signature;

四、特点

1、因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。

2、它不需要在服务端保存会话信息,所以它易于应用的扩展

3、JWT默认是不加密,但也是可以加密的。生成原始Token以后,可以用密钥再加密一次

4、JWT不加密的情况下,不能将秘密写入JWT

5、JWT不仅可以用于认证,也可以用于交换信息。有效使用JWT,可以降低服务器查询数据库的次数

6、JWT的最大缺点是,由于服务器不保存session状态,因此无法在使用过程中废止某个token,或者更改token的权限。也就是说,一旦JWT签发了,在到期之前就会始终有效。除非服务器部署额外的逻辑

7、JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌(token)的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

8、为了减少盗用,JWT不应该使用HTTP协议明码传输,要使用HTTPS协议传输

 

五、代码实现【java】

引入maven依赖

<dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.1</version>
</dependency>

生成token ,解析token

package com.harara.jjwt.emp;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;

import javax.xml.bind.DatatypeConverter;
import java.util.Date;
import java.util.Random;

/**
 * @author : harara
 * @version : 2.0
 * @date : 2020/6/16 17:15
 */
public class TokenUtil {

    //默认密钥
    public static final String DEFAULT_APPKEY = "harara";

    //默认token有效期30分钟
    public static final long DEFAULT_TOKEN_PERIOD = 30 * 60 *1000;
    //默认token刷新间隔
    public static final long DEFAULT_TOKEN_INTERVAL= 5 * 60 *1000;

    //token 不合法
    public static final String MSG_TOKEN_ERROR = "token 不合法";
    //token 已过期
    public static final String MSG_TOKEN_EXPIRED = "token 已过期";
    //token 不合法
    public static final String MSG_UNKNOWN_ERROR = "未知错误";

    /**
     * 登录和token快过期时调用
     * 生成token
     * @param id 令牌ID
     * @param subject 用户ID
     * @param user 登录用户信息
     * @param period 有效时长,单位毫秒
     * @param appKey 加密key
     * @param algorithm 加密算法
     * @return
     */
    public static String genToken(String id, String subject, User user,
            Long period, String appKey, SignatureAlgorithm algorithm){
        //读取需要的密钥
        if(StringUtils.isEmpty(appKey)){
            appKey = DEFAULT_APPKEY;
        }

        //将密钥转换成字节形式
        byte[] secreKeyBytes = DatatypeConverter.parseBase64Binary(appKey);

        //计算过期时间
        Date nowDate = new Date();
        Date endDate =new Date(nowDate.getTime()+period);

        JwtBuilder jwtBuilder = Jwts.builder();
        jwtBuilder.setId(id)
                .setSubject(subject)
                .setIssuedAt(nowDate)
                .setExpiration(endDate);

        String userStr = JSONArray.toJSONString(user);
        //其他自定义信息
        jwtBuilder.claim("user",userStr)
                .claim("period",period+"");

        //加签
        jwtBuilder.signWith(algorithm,secreKeyBytes);

        return jwtBuilder.compact();

    }



    public static Token parseToken(String token,String appkey){

        //密钥
        if(StringUtils.isEmpty(appkey)){
            appkey = DEFAULT_APPKEY;
        }

        byte[] secretKeyBytes = DatatypeConverter.parseBase64Binary(appkey);

        Claims claims;

        try{
            //检查jwt token是否合法
            claims = Jwts.parser().setSigningKey(secretKeyBytes).parseClaimsJws(token).getBody();
        }catch (ExpiredJwtException e){
            throw new RuntimeException(MSG_TOKEN_EXPIRED);
        }catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e){
            throw new RuntimeException(MSG_TOKEN_ERROR);
        }catch (Exception e){
            throw new RuntimeException(MSG_UNKNOWN_ERROR);
        }

        //Token对象,保存token字符串中的一些信息,便于其他业务处理时从该对象中获取登录用户等信息
        Token  jwtToken = new Token();
        //设置登录user信息到token对象中
        User user= JSONObject.parseObject(claims.get("user", String.class), User.class);
        jwtToken.setUser(user);

        //检查是否需要刷新token
        //获取签发时间
        long issueAttime = claims.getIssuedAt().getTime();
        //获取当前时间
        long nowTime = System.currentTimeMillis();
        //获取有效时长
        String periodTime = claims.get("period",String.class);
        long period;
        if(StringUtils.isEmpty(periodTime)){
            period = DEFAULT_TOKEN_PERIOD;
        }else{
            period = Long.valueOf(periodTime);
        }

        //如果已过期,获取已过去一半有效时长,则重新生成token
        if(nowTime > issueAttime + period || nowTime > issueAttime + DEFAULT_TOKEN_INTERVAL ){
            String token_id = getRandomString(20);
            String tokenNew = genToken(token_id,user.getUserId()+"",user,period,appkey,SignatureAlgorithm.HS256);
            //设置新的token字符串
            jwtToken.setToken(tokenNew);
        }else{
            //设置原来token字符串
            jwtToken.setToken(token);
        }
        //设置用户名
        jwtToken.setUserId(claims.getSubject());

        return jwtToken;
    }


    /**
     * 获取指定位数的随机数
     * @param length
     * @return
     */
    public static String getRandomString(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }


    public static void main(String[] args) {

        //模拟登录的时候从数据库中拿到的登录用户信息,省略
        User sysuser = new User();
        sysuser.setUserId(1111L);

        String tokenId = getRandomString(20);
        String appkey = "1223232131";
        //生成JWT token
        String jwttoken = genToken(tokenId,sysuser.getUserId()+"",sysuser,DEFAULT_TOKEN_PERIOD,appkey,SignatureAlgorithm.HS256);
        System.out.println(jwttoken);

        //解析token
        Token token = parseToken(jwttoken,appkey);
        System.out.println(token);
    }



}

自定义Token类,保存token字符串中的一些信息,便于其他业务处理时从该对象中获取登录用户等信息

package com.chenly.jjwt.emp;

import lombok.Data;

@Data
public class Token {

    private String token; //请求时携带的token
    private String userId; //用户ID
    private User user;//登录信息

}

用户类

package com.harara.jjwt.emp;

import lombok.Data;

/**
 * <p>
 * 操作员表
 * </p>
 *
 * @author harara
 * @since 2020-06-16
 */
@Data
public class User {

//    private static final long serialVersionUID = 1L;

    private Long userId;
    private String userName;
    private Integer sex;
   ....
}

 

参考地址

jwt官方介绍 : https://jwt.io/introduction/

JSON Web Token 入门教程http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

Spring Boot集成JSON Web Token(JWT)https://www.cnblogs.com/cndarren/p/11518443.html

原文地址:https://www.cnblogs.com/kiko2014551511/p/13139906.html