SpringCloud之JWT鉴权
Posted 一只楠喃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud之JWT鉴权相关的知识,希望对你有一定的参考价值。
学习方式我们还是按三布来的方式,我觉得这样学比较明白(如果有其他想法留言给我~~)
JWT是什么?
JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;它是分布式服务权限控制的标准解决方案!
它跟RBAC的区别:两者不冲突,在项目中后台权限服务的数据库设计使用RBAC,而前端项目访问后台微服务的权限校验使用jwt
为什么要学习JWT
这里用我们写的项目来解释
官网:https://jwt.io
GitHub上jwt的java客户端:https://github.com/jwtk/jjwt
数据格式
普通的token:32位UUID
JWT的token:至少64位
JWT的token包含三部分数据:
-
Header:头部,通常头部有两部分信息:
-
声明类型type,这里是JWT(type=jwt)
-
加密算法,自定义(rs256/base64/hs256)
我们会对头部进行base64加密(可解密),得到第一部分数据
-
-
Payload:载荷,就是有效数据,一般包含下面信息:
-
用户身份信息-userid,username(注意,这里因为采用base64加密,可解密,因此不要存放敏感信息)
-
注册声明:如token的签发时间,过期时间,签发人等
这部分也会采用base64加密,得到第二部分数据
-
-
Signature:base64加密,签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret,盐)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性
结论:
1 jwt的一个有规则的token
2 它有三部分组成:Header.payload.signature,每部分都是通过base64加密而成的
3 jwt每个部分都是可以解密的
JWT详解
(1) base64编码原理
Base64编码之所以称为Base64,是因为其使用64个字符来对任意数据进行编码,同理有Base32、Base16编码。标准Base64编码使用的64个字符如下:
这64个字符是各种字符编码(比如ASCII码)所使用字符的子集,并可打印。唯一有点特殊的是最后两个字符。
Base64本质上是一种将二进制数据转成文本数据的方案。对于非二进制数据,是先将其转换成二进制形式,然后每连续6比特(2的6次方=64)计算其十进制值,根据该值在上面的索引表中找到对应的字符,最终得到一个文本字符串。假设我们对Hello!进行Base64编码,按照ASCII表,其转换过程如下图所示:
可知Hello!的Base64编码结果为SGVsbG8h,原始字符串长度为6个字符串,编码后长度为8个字符,每3个原始字符经编码成4个字符。
但要注意,Base64编码是每3个原始字符编码成4个字符,如果原始字符串长度不能被3整除,怎么办?使用0来补充原始字符串。
以Hello!!为例,其转换过程为:
Hello!!
Base64编码的结果为 SGVsbG8hIQAA
。最后2个零值只是为了Base64编码而补充的,在原始字符中并没有对应的字符,那么Base64编码结果中的最后两个字符 AA
实际不带有效信息,所以需要特殊处理,以免解码错误。
标准Base64编码通常用 =
字符来替换最后的 A
,即编码结果为 SGVsbG8hIQ==
。因为 =
字符并不在Base64编码索引表中,其意义在于结束符号,在Base64解码时遇到 =
时即可知道一个Base64编码字符串结束。
如果Base64编码字符串不会相互拼接再传输,那么最后的 =
也可以省略,解码时如果发现Base64编码字符串长度不能被4整除,则先补充 =
字符,再解码即可。
解码是对编码的逆向操作,但注意一点:对于最后的两个 =
字符,转换成两个A
字符,再转成对应的两个6比特二进制0值,接着转成原始字符之前,需要将最后的两个6比特二进制0值丢弃,因为它们实际上不携带有效信息。
总结:::
1、base64的编码/加密原理
答:原理:将键盘输入的字符用base64编码表示
过程:将键盘输入字符的ascii码值,转成的对应8位二进制,将该二进制6个一组拆分,并计算拆分之后的十进制值,找出十进制值在base64编码表中对应的字母,即完成base64加密
(2) jwt测试-JwtUtil的使用
导入jar包
<properties>
<jjwt.version>0.7.0</jjwt.version>
<joda-time.version>2.9.6</joda-time.version>
</properties>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>$jjwt.version</version>
</dependency>
<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>$joda-time.version</version>
</dependency>
</dependencies>
导入JwtUtil
public class JWTUtil
/**
* 获取token中的参数
*
* @param token
* @return
/
public static Claims parseToken(String token,String key)
if ("".equals(token))
return null;
try
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(key))
.parseClaimsJws(token).getBody();
catch (Exception ex)
return null;
/*
* 生成token
*
* @param userId
* @return
*/
public static String createToken(Integer userId,String username,String key, int expireMinutes)
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成签名密钥
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(key);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//添加构成JWT的参数
JwtBuilder builder = Jwts.builder()
// .setHeaderParam(“type”, “JWT”)
// .setSubject(userId.toString())
.claim(“userId”, userId) // 设置载荷信息
.claim(“username”,username)
.claim(“age”,23)
.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())// 设置超时时间
.signWith(signatureAlgorithm, signingKey);
//生成JWT
return builder.compact();
public static void main(String[] args)
String token = JWTUtil.createToken(1, “zhangsan”,“admin”, 30);
System.out.println(token);
Claims claims = JWTUtil.parseToken(token, “admin”);
System.out.println();
结论:
-
jwt是采用base64加密/编码的
-
jwt的每个部分都是可以单独解码的
-
在jwt中不应存放重要明感信息,因为可以解密,不安全
-
createToken源码跟踪–最重要的方法
JWT交互流程
流程图:
步骤翻译:
• 1、用户登录
• 2、服务的认证,通过后根据secret生成token
• 3、将生成的token返回给用户
• 4、用户每次请求携带token
• 5、服务端利解读jwt签名,判断签名有效后,从Payload中获取用户信息
• 6、处理请求,返回响应结果
因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,就能知道用户身份,完全符合了Rest的无状态规范。
结合Zuul的鉴权流程
我们逐步演进系统架构设计。需要注意的是:secret是签名的关键,因此一定要保密,我们放到鉴权中心保存,其它任何服务中都不能获取secret。
在微服务架构中,我们可以把服务的鉴权操作放到网关中,将未通过鉴权的请求直接拦截,如图:
流程图解:
- 第一个流程:用户点击登录—>请求授权中心颁发jwt凭证
- 第二个流程:用户的每次请求都携带jwt凭证—>zuul判断jwt是否正确
• 1、用户请求登录
• 2、Zuul将请求转发到授权中心,请求授权
• 3、授权中心校验完成,颁发JWT凭证
• 4、客户端请求其它功能,携带JWT
• 5、Zuul将jwt交给授权中心校验,通过后放行
• 6、用户请求到达微服务
• 7、微服务将jwt交给鉴权中心,鉴权同时解析用户信息
• 8、鉴权中心返回用户数据给微服务
• 9、微服务处理请求,返回响应
- jwt-parent:统一jar包版本控制
- jwt-pojo:实体类存放位置
- jwt-common:工具类、常量类等存放的位置
- jwt-auth:认证中心
- goods-search:商品搜索服务,对外暴露商品搜索相关接口
- user-service:用户服务,对外暴露用户操作相关接口,如新增用户等
结论:
- 项目的整体架构方式
- 搭建认证中心
- 授权中心
- 通过zuul进行权限过滤
以上是关于SpringCloud之JWT鉴权的主要内容,如果未能解决你的问题,请参考以下文章
springcloud项目多个微服务中,jwt鉴权的代码应该放在哪个服务中?
SpringCloud Gateway + Jwt + Oauth2 实现网关的鉴权操作
SpringCloud Gateway + Jwt + Oauth2 实现网关的鉴权操作
SpringCloud Gateway + Jwt + Oauth2 实现网关的鉴权操作