• 【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

  • 相关阅读:
    常用模块
    python里面的奇技淫巧
    day_06、面向对象(二)
    day_06、面向对象
    day_06、递归、二分查找
    day_05、内置函数、匿名函数
    day_05、迭代器、生成器
    day_04、函数
    php调用webservice接口
    php在命令行输出进度条
  • 原文地址:https://www.cnblogs.com/kiko2014551511/p/13139906.html
Copyright © 2020-2023  润新知