原文链接:

JWT详解:https://blog.csdn.net/weixin_45070175/article/details/118559272

1、什么是JWT

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探;
  2. 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Heade分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串;
  3. 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可;
  4. 前端在每次请求时将JWT Token放入HTTP请求头中Authorization属性中(解决XSS和XSRF问题);
  5. 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等;
  6. 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果;

2、 JWT认证的优势

对比传统的session认证方式,JWT的优势是:

  1. 简洁:JWT Token数据量小,传输速度也很快;
  2. 因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持;
  3. 不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务;
  4. 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题;
  5. 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端;
  6. 因为这些优势,目前无论单体应用还是分布式应用,都更加推荐用JWT token的方式进行用户认证;

3、JWT结构

JWT由3部分组成:标头(Header)有效载荷(Payload)签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串;

JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

3.1 Header

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存;

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

3.2 Payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择

iss: 发行人
exp: 到期时间
sub: 主题
aud: 用户
nbf: 在此之前不可用
iat: 发布时间
jti: JWT ID用于标识该JWT

这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:

{
"sub": "1234567890",
"name": "Helen",
"admin": true
}

请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

3.3 3.Signature

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名;

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

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象:

注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token之后:

  • header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据;
  • signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值;

4、Java中使用JWT

官网推荐了6个Java使用JWT的开源库,其中比较推荐使用的是java-jwtjjwt-root

4.1.java-jwt

4.1.1 对称签名

4.1.1.1 依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
4.1.1.2 生成JWT的token
/**
* @author : huayu
* @date : 25/11/2022
* @param : []
* @return : void
* @description : 生成JWT的token
*/
@Test
public void testGenerateToken(){
// 指定token过期时间为10秒
Calendar calendar = Calendar.getInstance();
// calendar.add(Calendar.SECOND, 10);
//为了测试不过期,指定token过期时间为100秒
calendar.add(Calendar.SECOND, 100); String token = JWT.create()
.withHeader(new HashMap<>()) // Header
.withClaim("userId", 001) // Payload
.withClaim("userName", "huayu")
.withExpiresAt(calendar.getTime()) // 过期时间
.sign(Algorithm.HMAC256("!34ADAS")); // 签名用的secret System.out.println(token);
}

测试结果:

4.1.1.3 解析JWT字符串
/**
* @author : huayu
* @date : 25/11/2022
* @param : []
* @return : void
* @description : 解析JWT字符串
*/
@Test
public void testResolveToken(){
// 创建解析对象,使用的算法和secret要与创建token时保持一致
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!34ADAS")).build();
// 解析指定的token
DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6Imh1YXl1IiwiZXhwIjoxNjY5MzQ1NTE2LCJ1c2VySWQiOjF9.mN9DIfqy6ZKl6gwQ4WM5gmrQL2y0Q0bvleTy7AfTuFo");
// 获取解析后的token中的payload信息
Claim userId = decodedJWT.getClaim("userId");
Claim userName = decodedJWT.getClaim("userName");
log.info("userId:{}",userId.asInt());
log.info("userName:{}",userName.asString());
// 输出超时时间
log.info("超出时间:{}",decodedJWT.getExpiresAt());
}

测试:

我们设置过期时间位100秒,再次测试:

4.1.1.4 封装成工具类
public class JWTUtils {
// 签名密钥
private static final String SECRET = "!DAR$"; /**
* 生成token
* @param payload token携带的信息
* @return token字符串
*/
public static String getToken(Map<String,String> payload){
// 指定token过期时间为7天
Calendar calendar = Calendar.getInstance();
// calendar.add(Calendar.DATE, 7);
// 指定token过期时间为 12分钟
// calendar.add(Calendar.MINUTE, 12);
// 指定token过期时间为 100秒
calendar.add(Calendar.SECOND, 100); JWTCreator.Builder builder = JWT.create().withHeader(new HashMap<>());
// 构建payload
payload.forEach((k,v) -> builder.withClaim(k,v)); // 指定过期时间和签名算法
String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET)); return token;
} /**
* 解析token
* @param token token字符串
* @return 解析后的token
*/
public static DecodedJWT decode(String token){
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
return decodedJWT;
} }
4.1.1.5 JWTUtils 工具类测试
/**
* @author : huayu
* @date : 25/11/2022
* @param : []
* @return : void
* @description : 测试 JWTUtils 工具类 生成token 和 token 解析
*/
@Test
public void testJWTUtils(){ //创建payload map 存放用户信息
Map<String, String> payload = new HashMap();
payload.put("userId","1");
payload.put("userName","hauyu"); //生成 token
String token = JWTUtils.getToken(payload); //解析token
DecodedJWT decodedJWT = JWTUtils.decode(token);
Claim userId = decodedJWT.getClaim("userId");
Claim userName = decodedJWT.getClaim("userName");
log.info("userId:{}",userId.asString());
log.info("userName:{}",userName.asString());
// 输出超时时间
log.info("超出时间:{}",decodedJWT.getExpiresAt()); log.info("token:{}",token);
}

测试结果:

4.1.2 非对称签名

生成jwt串的时候需要指定私钥,解析jwt串的时候需要指定公钥

还没有测试成功,我的 RSA rsa = new RSA(null, RSA_PUBLIC_KEY); 只有一个参数,无法实例化RSA

4.2 jwt-root

4.2.1 对称签名

4.2.1.1 依赖
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
4.2.1.2 工具类
public class JWTUtils2 {
// token时效:24小时
public static final long EXPIRE = 1000 * 60 * 60 * 24;
// 签名哈希的密钥,对于不同的加密算法来说含义不同
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; /**
* 根据用户id和昵称生成token
* @param id 用户id
* @param nickname 用户昵称
* @return JWT规则生成的token
*/
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("baobao-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id)
.claim("nickname", nickname)
// HS256算法实际上就是MD5加盐值,此时APP_SECRET就代表盐值
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact(); return JwtToken;
} /**
* 判断token是否存在与有效
* @param jwtToken token字符串
* @return 如果token有效返回true,否则返回false
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
} /**
* 判断token是否存在与有效
* @param request Http请求对象
* @return 如果token有效返回true,否则返回false
*/
public static boolean checkToken(HttpServletRequest request) {
try {
// 从http请求头中获取token字符串
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
} /**
* 根据token获取会员id
* @param request Http请求对象
* @return 解析token后获得的用户id
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
4.2.1.3 请求方法
4.2.1.3.1 JWT规则生成的token 和 判断token是否存在与有效
/**
* @author : huayu
* @date : 25/11/2022
* @param : [id, nickname]
* @return : java.lang.String
* @description : JWT规则生成的token 和 判断token是否存在与有效
*/
@ApiOperation(value = "JWT规则生成的token 和 判断token是否存在与有效")
@PostMapping("testGetJwtToken")
@ApiImplicitParams({
@ApiImplicitParam(value = "用户id",name = "id"),
@ApiImplicitParam(value = "昵称",name = "nickname")
})
public String testGetJwtToken(@RequestParam("id") String id,
@RequestParam("nickname") String nickname){ //JWT规则生成的token
String jwtToken = JWTUtils2.getJwtToken(id, nickname); log.info("JWT规则生成的token jwtToken:{}",jwtToken); //判断token是否存在与有效
boolean checkoutToken = JWTUtils2.checkToken(jwtToken);
log.info("判断token是否存在与有效 checkoutToken:{}",checkoutToken); return jwtToken; }

测试结果:

4.2.1.3.2 根据token获取会员id
/**
* @author : huayu
* @date : 25/11/2022
* @param : [request]
* @return : java.lang.String
* @description : 根据token获取会员id
*/
@ApiOperation(value = "根据token获取会员id ")
@PostMapping("testGetMemberIdByJwtToken")
public String testGetMemberIdByJwtToken(HttpServletRequest request){
//根据token获取会员id
String memberIdByJwtToken = JWTUtils2.getMemberIdByJwtToken(request); log.info("根据token获取会员id memberIdByJwtToken:{}",memberIdByJwtToken); return memberIdByJwtToken;
}

测试结果:

4.2.2 非对称签名

还没有测试成功,我的 RSA rsa = new RSA(null, RSA_PUBLIC_KEY); 只有一个参数,无法实例化RSA

5、实际开发中的应用

在实际的SpringBoot项目中,一般我们可以用如下流程做登录:

  1. 在登录验证通过后,给用户生成一个对应的随机token(注意这个token不是指jwt,可以用uuid等算法生成),然后将这个token作为key的一部分,用户信息作为value存入Redis,并设置过期时间,这个过期时间就是登录失效的时间;
  2. 将第1步中生成的随机token作为JWT的payload生成JWT字符串返回给前端;
  3. 前端之后每次请求都在请求头中的Authorization字段中携带JWT字符串;
  4. 后端定义一个拦截器,每次收到前端请求时,都先从请求头中的Authorization字段中取出JWT字符串并进行验证,验证通过后解析出payload中的随机token,然后再用这个随机token得到key,从Redis中获取用户信息,如果能获取到就说明用户已经登录;
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String JWT = request.getHeader("Authorization");
try {
// 1.校验JWT字符串
DecodedJWT decodedJWT = JWTUtils.decode(JWT);
// 2.取出JWT字符串载荷中的随机token,从Redis中获取用户信息
...
return true;
}catch (SignatureVerificationException e){
System.out.println("无效签名");
e.printStackTrace();
}catch (TokenExpiredException e){
System.out.println("token已经过期");
e.printStackTrace();
}catch (AlgorithmMismatchException e){
System.out.println("算法不一致");
e.printStackTrace();
}catch (Exception e){
System.out.println("token无效");
e.printStackTrace();
}
return false;
}
}

SpringCloud Alibaba(七) - JWT(JSON Web Token)的更多相关文章

  1. Java JWT: JSON Web Token

    Java JWT: JSON Web Token for Java and Android JJWT aims to be the easiest to use and understand libr ...

  2. 如何在SpringBoot中集成JWT(JSON Web Token)鉴权

    这篇博客主要是简单介绍了一下什么是JWT,以及如何在Spring Boot项目中使用JWT(JSON Web Token). 1.关于JWT 1.1 什么是JWT 老生常谈的开头,我们要用这样一种工具 ...

  3. JWT(JSON Web Token) 【转载】

    JWT(JSON Web Token) 什么叫JWTJSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. 一般来说,互联网用户认证是这样子的. 1.用户向服务器发送用户名和密码. ...

  4. [更新]一份包含: 采用RSA JWT(Json Web Token, RSA加密)的OAUTH2.0,HTTP BASIC,本地数据库验证,Windows域验证,单点登录的Spring Security配置文件

    没有任何注释,表怪我(¬_¬) 更新: 2016.05.29: 将AuthorizationServer和ResourceServer分开配置 2016.05.29: Token获取采用Http Ba ...

  5. ( 转 ) 什么是 JWT -- JSON WEB TOKEN

    什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点 ...

  6. 关于JWT(Json Web Token)的思考及使用心得

    什么是JWT? JWT(Json Web Token)是一个开放的数据交换验证标准rfc7519(php 后端实现JWT认证方法一般用来做轻量级的API鉴权.由于许多API接口设计是遵循无状态的(比如 ...

  7. 什么是JWT(Json Web Token)

    什么是 JWT (Json Web Token) 用户认证是计算机安全领域一个永恒的热点话题. JWT 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519). 该to ...

  8. API安全验证之JWT(JSON WEB TOKEN) OLCMS

    假如www.olcms.com/getUserInfo获取用户信息,你怎么知道当前用户是谁?有人说登陆时候我把他UID写入session了,如果是API接口,没有session怎么办,那么就需要把UI ...

  9. 5分钟搞懂:JWT(Json Web Token)

    https://www.qikegu.com/easy-understanding/892 JWT 基于token的用户认证原理:让用户输入账号和密码,认证通过后获得一个token(令牌),在toke ...

  10. JWT(Json Web Token)认证

    目录 JWT(Json Web Token) JWT的数据结构 JWT的用法 JWT验证流程

随机推荐

  1. (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通

    一.注意要点 1:输入字符串的的编码双方保持统一,如:UTF8: 2:HASH计算输出结果 byte[] 数组转String 时,编码要统一,如:转16进制小写字符串.当然也可以转Base64. 3: ...

  2. aardio + PHP 可视化快速开发独立 EXE 桌面程序

    aardio 支持与很多编程语言混合开发.网络上大家分享的 aardio + Python 混合开发的文章很多,aardio + PHP 的文章却很少. 其实 aardio 与 PHP 混合开发是真的 ...

  3. 定制开发 ERP 的优势有哪些?

    定制开发ERP对企业而言是把双刃剑,成败难以把握.定制开发ERP理论上来讲是最贴合企业业务需求的,因为它是按企业需求定制,看上去似乎没什么毛病,但ERP是专业性极强的业务逻辑极其复杂的软件系统,有两个 ...

  4. Jupyter Notebook单元格加宽的方法3种

    Jupyter Notebook的代码单元格比较窄,在我的屏幕上只占了一半都不到,网络搜索下,共找到3种加宽的方法,总结一下. (一)只改变当前Jupyter笔记本的单元格宽度 在Jupyter No ...

  5. SECS半导体设备通讯-3 SECS-II通信标准

    一 SECS-II 概述 SECS-II 标准定义了使用如SECS-I.HSMS等传输协议在设备和主机之间交换的消息的形式和含义. 定义了以消息的形式在设备和主机之间传递信息,消息按其行为分类,称为S ...

  6. 2022.9.30 Java第四次课后总结

    1.public class BoxAndUnbox { /** * @param args */ public static void main(String[] args) { int value ...

  7. P1886 滑动窗口 /【模板】单调队列 方法记录

    原题链接 滑动窗口 /[模板]单调队列 题目描述 有一个长为 \(n\) 的序列 \(a\),以及一个大小为 \(k\) 的窗口.现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最 ...

  8. 《Vue3.x+TypeScript实践指南》已出版

    转眼回长沙快2年了,图书本在去年就已经完稿,因为疫情,一直耽搁了,直到这个月才出版!疫情之下,众生皆苦!感觉每天都是吃饭.睡觉.上班.做核酸! 图书介绍 为了紧跟技术潮流,该书聚焦于当下火的Vue3和 ...

  9. 深入浅出TCP与IP协议笔记

    TCP/IP 4层结构:应用层 传输层 网络层 链路层   探索过程问题:一个主机的数据要经过哪些过程才到达对方的主机上 一组电信号就是一个数据包,一个数据包称为一帧,制定这个规则的就是以太网协议   ...

  10. 3.pytest断言assert

    pytest使用的python自带的断言assert关键字,和unittest封装的assert断言不一样 原理:用来测试某个断言条件,如果断言条件为True,则程序将继续正常执行:但如果断言条件为假 ...