美文网首页
JWT原理和应用

JWT原理和应用

作者: 是小猪童鞋啦 | 来源:发表于2020-05-23 08:12 被阅读0次
    JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案 官网 :https://jwt.io/
    不使用token的时候

    在楼主之前(好久之前学习的时候),会使用session保存数据,因为session可以存储对象,所以我把数据放在session里面,然后通过JSP页面取出数据,但是这个过程,比较痛苦,为啥呢,服务端需要存,客户浏览器需要取,来来回回存储的session过多之后,单单记下名字就很痛苦了。

    以前的token

    以前的token我没有玩过,但是知道原理,在常见用户数据表的时候,会给token的栏位,也会给个过期时间的栏位,然后生成token之后,会把数据存储到数据库。然后每次验证用户的时候就会验证token,而不需再去查询数据库中的username和password。但是这个还是需要和数据库进行返回。请求过多不好,应该把服务器(数据库)的资源放给更需要的地方去。

    JWT

    用户登录,服务端给用户返回一个token(服务端)不保存,
    以后用户再来访问,需要携带token,服务端获取token之后,再做token的校验

    优势:相对于传统的token相比,它无需在服务器端保存token。

    JWT的实现过程

    第1步:用户提交用户名和密码给服务器,如果登录成功,使用jwt创建一个token返回

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    

    注意:jwt生成的 token 是由三段字符串组成,并且用 . 链接起来

    官网示例
    第一段字符串 [HEADER:ALGORITHM & TOKEN TYPE]:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    内部包含算法/token类型,也就是token转化为字符串,然后做base64url加密(base64加密;+ _ 等符合会进行替换操作)。
    {
      "alg": "HS256",
      "typ": "JWT"
    }
    

    第二段字符串 [PAYLOAD:DATA]: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    自定义的值
    token转化为字符串,然后做base64url加密(base64加密;+ _ 等符合会进行替换操作)。

    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
    

    第三段字符串 [VERIFY SIGNATURE]: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    第一步:会将第一部分和第二部分的密文拼接起来
    第二步:对前两部分密文进行HS256加密 + 盐值
    第三步:对HS256加密之后的密文再做base64url加密

    以后用户再来访问的时候,需要携带token,后端需要对token进行校验

    1.获取token
    2.第一步:对token进行切割
    3.第二步:对第二段进行base64url解密,并获取payload信息,检测超时时间,检测token是否超时
    4.第三步:把第一第二段再次进行HS256加密
    5.让密文和密文进行比对,如果相等,认证通过

    简单的测试
    package cn.icanci.test.utils;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.JwtBuilder;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.apache.commons.codec.binary.Base64;
    
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Date;
    import java.util.HashMap;
    
    /**
     * @Author: icanci
     * @ProjectName: AlgorithmTraining
     * @PackageName: cn.icanci.test.utils
     * @Date: Created in 2020/4/17 15:39
     * @ClassAction: JwtUtil
     */
    public class JwtUtil {
    
        /**
         * 创建 JWT
         *
         * @param id        id
         * @param subject   subject
         * @param ttlMillis ttlMillis 过期得时间长度
         * @return 返回 token 字符串
         * @throws Exception
         */
        public String createJWT(String id, String subject, long ttlMillis) throws Exception {
            //指定签名得的时候使用的签名算法 也就是header那部分,jjwt已经将这部分内容封装好了
            SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;
            //生成JWT的时间
            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
            //创建payload的私有声明(根据特定的业务需要添加如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
            HashMap<String, Object> claims = new HashMap<>();
            claims.put("uid", "DSSFAWDWADAS...");
            claims.put("user_name", "admin");
            claims.put("nick_name", "DASDA121");
            SecretKey secretKey = generalKey();
            //下面就是在为payload添加各种标准声明和私有声明了
            JwtBuilder builder = Jwts.builder() //这里其实就是new一个JwtBuilder,设置jwt的body
                    .setClaims(claims)          //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                    .setId(id)                  //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                    .setIssuedAt(now)           //iat: jwt的签发时间
                    .setSubject(subject)        //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
                    .signWith(hs256, secretKey);//设置签名使用的签名算法和签名使用的秘钥
            if (ttlMillis >= 0) {
                long expMillis = nowMillis + ttlMillis;
                Date exp = new Date(expMillis);
                builder.setExpiration(exp);     //设置过期时间
            }
            return builder.compact();           //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
            //打印了一哈哈确实是下面的这个样子
        }
    
    
        /**
         * 解密jwt
         *
         * @param jwt
         * @return
         * @throws Exception
         */
        public Claims parseJWT(String jwt) throws Exception {
            SecretKey key = generalKey();  //签名秘钥,和生成的签名的秘钥一模一样
            Claims claims = Jwts.parser()  //得到DefaultJwtParser
                    .setSigningKey(key)         //设置签名的秘钥
                    .parseClaimsJws(jwt).getBody();//设置需要解析的jwt
            return claims;
        }
    
        /**
         * 由字符串生成加密key
         *
         * @return
         */
        public SecretKey generalKey() {
            //本地配置文件中加密的密文
            String stringKey = "7786df7fc3a34e26a61c034d5ec8245d";
            //本地的密码解码
            byte[] encodedKey = Base64.decodeBase64(stringKey);
            System.out.println(encodedKey);
            //7786df7fc3a34e26a61c034d5ec8245d
            System.out.println(Base64.encodeBase64URLSafeString(encodedKey));
            // 根据给定的字节数组使用AES加密算法构造一个密钥,使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。(后面的文章中马上回推出讲解Java加密和解密的一些算法)
            SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
            return key;
        }
    }
    

    测试

    package cn.icanci.test.jwt;
    
    import cn.icanci.test.utils.JwtUtil;
    import io.jsonwebtoken.Claims;
    
    /**
     * @Author: icanci
     * @ProjectName: AlgorithmTraining
     * @PackageName: cn.icanci.test.jwt
     * @Date: Created in 2020/4/17 13:48
     * @ClassAction: TestJWT
     */
    public class TestJWT {
        public static void main(String[] args) throws Exception {
            JwtUtil util = new JwtUtil();
            String ab = util.createJWT("jwt", "{id:100,name:xiaohong}", 600000);
            System.out.println(ab);
            String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiJ7aWQ6MTAwLG5hbWU6eGlhb2hvbmd9IiwidXNlcl9uYW1lIjoiYWRtaW4iLCJuaWNrX25hbWUiOiJEQVNEQTEyMSIsImV4cCI6MTUxNzgzNTEwOSwiaWF0IjoxNTE3ODM1MDQ5LCJqdGkiOiJqd3QifQ.G_ovXAVTlB4WcyD693VxRRjOxa4W5Z-fklOp_iHj3Fg";
            Claims c = util.parseJWT(jwt);//注意:如果jwt已经过期了,这里会抛出jwt过期异常。
            System.out.println(c.getId());//jwt
            System.out.println(c.getIssuedAt());//Mon Feb 05 20:50:49 CST 2018
            System.out.println(c.getSubject());//{id:100,name:xiaohong}
            System.out.println(c.getIssuer());//null
            System.out.println(c.get("uid", String.class));//DSSFAWDWADAS...
        }
    }
    
    代码部分转载自:https://blog.csdn.net/qq_37636695/article/details/79265711
    JWT 的几个特点

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

    (2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

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

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

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

    (6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

    Header

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

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

    上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

    最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

    Payload

    Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

    iss (issuer):签发人
    exp (expiration time):过期时间
    sub (subject):主题
    aud (audience):受众
    nbf (Not Before):生效时间
    iat (Issued At):签发时间
    jti (JWT ID):编号
    

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

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

    注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

    这个 JSON 对象也要使用 Base64URL 算法转成字符串。

    Signature

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

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

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

    算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

    Base64URL

    前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

    JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

    相关文章

      网友评论

          本文标题:JWT原理和应用

          本文链接:https://www.haomeiwen.com/subject/ezkevhtx.html