JWT令牌

142 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情

每日英语:

Reading is to the mind what exercise is to the body.

读书之于思想,犹如运动之于身体。 -约瑟夫·艾迪生

JWT令牌

用户每次访问后台的时候,如果是一些需要认证的链接,都需要识别用户身份,比如用户抢单、用户中心等,在传统项目中用的是Session,但在微服务中不建议使用Session,使用JWT令牌。

初识JWT令牌

JWT简称JSON Web Token ,也就是通过json形式作为web应用的令牌,用在各方之间安全的将信息作为json对象传输,在数据传输过程中还可以完成数据加密,签名相关处理 。

JWT令牌作用:

身份授权:这是使用jwt的最常见方案,一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该领牌允许的路由,服务和资源,单点登录是的当今广泛使用JWT的一项功能,因为他的开销很小,并且可以在不同的域中使用。信息交换:JWT令牌是在各方面之间安全地传输信息的好方法,因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人,此外由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。

JWT令牌认证流程:

1612146984727.png

JWT令牌鉴权流程如上图:

1:用户携带账号密码登录
2:登录通过后,后端服务会封装账号信息,并且采用指定算法进行加密,并将加密后的密文(令牌)返回客户端
3:客户端拿到令牌后,将令牌保存到本地,可以是Cookie,也可以是localStoreage
4:客户端每次发起请求的时候,会将本地令牌写到到请求头中,一起传到后台
5:后台在微服务网关中校验令牌是否正确,如果正确再执行权限校验
6:令牌校验通过、权限校验通过,则执行用户要操作的业务流程
7:令牌校验失败或者权限校验失败,则提示错误信息

JWT令牌校验优势:

1:简洁(Compact):可以通过URL,POST参数或者在HTTP header中发送,因为数据量小,所以传输的速度也快2:自包含(Self-contained): 负载中包含了所有用户所需的信息,避免了多次查询数据库3:因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何Web形式都支持4:不要再服务端保存信息,特别适用于分布式微服务

JWT令牌结构

JWT令牌组成有3部分,分别为HeaderPayloadSignature,将三部分组合就是标准的JWT令牌了,三部分组合通常以"."链接,如下:

HJLDISNDSSDYIREWREWRDFDSDSFBH.DSFTIHGNDSFSDREWREWRDFDSFDSFRDNSJDJ.DSVDSFEWEREREWREWRDFDS

Header:

1:通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC,SHA265或RSA,它会使用Base64编码组成JWT结构的一部分
​
2:注意: Base64是一种编码,也就是说,他是可以被翻译回原来的样子的,他并不是一种加密的过程
​
3:{"alg":"HS365", "typ":"JWT"}

Payload:

1:令牌的第二部分是有效负载,其中包含声明,声明有关实体(通常是指用户)和其他数据的声明。同样的他会使用Base64编码组成JWT结构的第二部分
​
2:{"sub":"12334798", "name":"John Doe", "admin":true}

Signature:

1:前面两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息,Signature需要使用编码后的Header和Payload以及我们提供的一个密钥,然后使用Header中指定的签名算法(HS256)进行签名,没有被篡改过:
​
2:如:HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payLoad).secret)

为什么要签名?

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改,如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合成新的JWT的话,那么服务器会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的,如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

JWT令牌存在数据安全隐患?

JWT令牌采用了Base64加密,Base64加密数据可以直接解密,因此我们在JWT令牌中尽量不要传输敏感信息,如果非要传输敏感信息,可以把敏感信息进行加密操作,比如AES加密。

JWT令牌实现

JWT令牌使用参考地址:github.com/auth0/java-…

在创建JWT令牌的时候,会有很多属性需要填写,关于JWT令牌中一些属性,我们说明一下:

iss: jwt签发者
sub: 主题
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

mall-common中引入依赖

<!--JWT令牌-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.12.1</version>
</dependency>

创建com.xz.mall.util.JwtToken代码如下:

public class JwtToken {
​
    //默认秘钥
    private static final String DEFAULT_SECRET="springcloudalibaba";
​
    /***
     * 生成令牌
     * @param dataMap
     * @return
     */
    public static String createToken(Map<String,Object> dataMap){
        return createToken(dataMap,null);
    }
​
    /***
     * 生成令牌
     * @return
     */
    public static String createToken(Map<String,Object> dataMap,String secret){
        //秘钥为空就采用默认秘钥
        if(StringUtils.isEmpty(secret)){
            secret = DEFAULT_SECRET;
        }
​
        //创建令牌操作算法
        Algorithm algorithm = Algorithm.HMAC256(secret);
        //创建令牌
        return JWT.create()
                .withClaim("body",dataMap)
                .withIssuer("GP")            //JWT签发者
                .withSubject("JWT令牌")       //主题
                .withAudience("member")      //接收JWT的一方
                .withExpiresAt(new Date(System.currentTimeMillis()+3600000))    //过期时间
                .withNotBefore(new Date(System.currentTimeMillis()))      //指定时间之前JWT令牌是不可用的
                .withIssuedAt(new Date())    //JWT签发时间
                .withJWTId(UUID.randomUUID().toString().replace("-","")) // jwt唯一标识
                .sign(algorithm);
    }
​
    /***
     * 解析令牌
     * @param token
     * @return
     */
    public static Map<String,Object> parseToken(String token){
        return parseToken(token,null);
    }
​
    /***
     * 令牌校验并解析
     * @param token
     * @return
     */
    public static Map<String,Object> parseToken(String token,String secret){
        //秘钥为空就采用默认秘钥
        if(StringUtils.isEmpty(secret)){
            secret = DEFAULT_SECRET;
        }
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm).build(); //Reusable verifier instance
        DecodedJWT jwt = verifier.verify(token);
        return jwt.getClaim("body").as(Map.class);
    }
​
    /***
     * 令牌校验解析
     * @param args
     */
    public static void main(String[] args) {
        Map<String,Object> dataMap = new HashMap<String,Object>();
        dataMap.put("name","zhangsan");
        dataMap.put("age","26");
        dataMap.put("address","深圳市");
​
        Map<String,Object> headerMap = new HashMap<String,Object>();
        headerMap.put("version","v1.0");
        headerMap.put("mysql","5.7");
​
        //创建令牌
        String token = createToken(dataMap);
        System.out.println(token);
​
        //解析令牌
        Map<String,Object> resultMap =parseToken(token);
        System.out.println(resultMap);
    }
}

测试结果如下:

eyJteXNxbCI6IjUuNyIsInR5cCI6IkpXVCIsInZlcnNpb24iOiJ2MS4wIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJKV1Tku6TniYwiLCJhdWQiOiJtZW1iZXIiLCJuYmYiOjE2MTIxNTg5MjIsImlzcyI6IkdQIiwiYm9keSI6eyJhZGRyZXNzIjoi5rex5Zyz5biCIiwibmFtZSI6InpoYW5nc2FuIiwiYWdlIjoiMjYifSwiZXhwIjoxNjEyMTYyNTEyLCJpYXQiOjE2MTIxNTg5MTIsImp0aSI6IjE0NTJmNzExYzY1MzQ5ZjI5Y2FhNDUwOTg3MmIzYTc5In0.wMZ9fbuUFtStblckO71FJAbnmNbfJRQEPylsXDr9ewM
eyJteXNxbCI6IjUuNyIsInR5cCI6IkpXVCIsInZlcnNpb24iOiJ2MS4wIiwiYWxnIjoiSFMyNTYifQ
{address=深圳市, name=zhangsan, age=26}

总结

本篇主要介绍了一下JWT令牌概念、结构、还有实现的demo,有兴趣的朋友,可以自己按照demo玩一玩。