开启掘金成长之旅!这是我参与「掘金日新计划 · 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令牌认证流程:
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部分,分别为Header、Payload、Signature,将三部分组合就是标准的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玩一玩。