接着上篇博客的学习。上篇博客是在基本完成用户模块的注册接口的开发以及注册时的参数合法性校验的基础上,基本完成用户模块的登录接口的主逻辑以及提到了问题:"用户未登录,需要通过登录,获取到令牌进行登录认证,才能使用其他功能的接口"。具体往回看了解的链接如下。springboot实战学习(6)(用户模块的登录认证)(初识令牌)(JWT)-CSDN博客文章浏览阅读2次。本篇博客是在处理用户模块中登录认证时遇到需要解决的问题。因为未登录时,需要做到无法访问和使用其他功能的接口。也就提到了令牌的作用以及满足令牌的规范"JWT"。具体的学习下篇博客进行学习...https://blog.csdn.net/m0_74363339/article/details/142365524?spm=1001.2014.3001.5502
接下来就去认真了解和学习Web登录认证中常用的令牌规范——>"JWT"。
目录
一、JWT(令牌规范)
(1)基本介绍
(2)基本组成
(I)解释上方图片(JWT令牌字符串)
(II)总结组成
二、程序中使用JWT令牌
(1)回顾与思考
三、JWT令牌的生成
(1)JWT令牌示范
(1)如何生成
(2)如何使用生成"JWT令牌"工具
(I)第一步。导入工具的坐标(依赖)
(II)第二步。调用API,生成令牌。
(III)生成"JWT令牌"的代码不需要去记忆
(3)IDEA中写单元测试的方法测试生成JWT令牌
四、JWT令牌的验证
(1)DEA中写单元测试的方法测试"JWT令牌"的验证
(2)测试因篡改"JWT令牌"导致验证失败的几种情况
(I)篡改JWP令牌"头部部分"进行验证。
编辑
(II)篡改JWP令牌中间"载荷部分"进行验证。
(III)篡改JWP令牌尾部"数字签名"(篡改密钥)进行验证。
(3)处理因篡改JWT令牌的数据导致的异常
(I)如果篡改了头部和载荷部分的数据,那么验证失败。
(II)如果篡改了密钥数据,那么验证失败。
(III)如果超过设定的Token令牌过期时间,那么验证失败。
(4)关于"JWT令牌"验证的注意事项
五、总结
(1)JWT令牌的组成
(2)JWT令牌的使用
(3)尾言
一、JWT(令牌规范)
(1)基本介绍
JSON Web Tokens - jwt.ioJSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).https://jwt.io/
- JWT的全称:JSON Web Token
(也就是用于Web领域的基于JSON格式的令牌)
- 定义了一种简洁的、自包含的格式,用于通信双方以json数据格式安全的传输信息。
(JSON是一种文本数据格式,来源于JavaScript的对象语法。)
(2)基本组成
(I)解释上方图片(JWT令牌字符串)
- 上面展示的它是前后端交互时传输的字符串。而这个字符串是由JSON格式的字符串编码得来的字符串。可以看到上面字符串有两个小".",它就是把这整个字符串分为三个部分。而"分割"的每一个小串,对应着Token令牌的一部分。
- 第一部分是头。它是由JSON字符串编码得来的。这个部分的字符串会记录两个信息。第一个是"alg"(它是加密算法,防篡改),而"type"(是JWT)。
- 第二部分是"有效载荷"。它是也是一段字符串。而在这段字符串中,他会存放我们的业务数据。比如存放用户的id、用户的username等等。这段字符串编码后,也会得到上面Token令牌里面的这一段。
- 而对应的JSON格式的字符串,是如何转换为上面Token令牌中展示的这一段"特殊长的字符串"呢?在JWT中会借助于"base64"这种编码方式完成。而"base64"可以将任意数据转换成64个可打印字符('a'-'z'、'A'-'Z'、'0'-'9'等等)。这些64个可打印字符最重要的特点就是:"通用",在任意场景都可以被支持。
- 将JSON格式的字符串转换为64个可打印的字符串,原因是为了提供Token令牌的实用性。记得"base64"仅仅是一种编码方式,而不是加密。因此记得在Token令牌的第二部分"有效载荷",一定注意不要放私密数据(比如用户密码等),不然不安全。
- 最后一部分叫做"数字签名"。它是将第一部分以及第二部分,借助于密钥和加密算法,通过加密得来的。而这里的加密算法,是通过头部的"alg"来制定的。密钥可以在程序中单独配置。
- 有了这个数字签名,就可以防篡改,确保Token是安全的。因为将来即使篡改了第一和第二部分,但第三部分是不能篡改的,因为是加密后的字符串。将来JWT再去解析Token令牌时,通过解密第三部分,得到"头部"与"载荷",再拿到解密的内容与用户传递的内容进行比对,如果不一样,就不让访问。
(II)总结组成
二、程序中使用JWT令牌
(1)回顾与思考
- 回到之前的所完成用户模块的注册接口与登录接口的主逻辑。
springboot实战学习笔记(5)(用户登录接口的主逻辑)-CSDN博客
- 现在需要在用户登录之后生成一个"JWT令牌",生成了之后还需要把"JWT令牌"响应给浏览器。
- 将来浏览器再去访问服务器上的其它资料时,会携带这个令牌访问。这时服务器就能够得到这个令牌,并且还需要去验证这个令牌的合法性。如果令牌合法、没有被篡改,就正常提供服务,反之。
- 需要学习如何生成令牌?如何验证令牌?
三、JWT令牌的生成
(1)JWT令牌示范
(1)如何生成
- 可以自己手写,毕竟JWT是一个令牌的规范写法。而是规范,大家都可以实现的。
- 有人提供了生成"JWT令牌"的工具,所以可以直接使用这些工具即可。
(2)如何使用生成"JWT令牌"工具
(I)第一步。导入工具的坐标(依赖)
引入的是java-jwt
引入坐标时,报错,记得尝试刷新一个Maven
因为我们当前写的"生成令牌"或者"验证令牌"代码,它仅仅是一个测试的代码。所以需要把它写到单元测试里面。所以还要引入单元测试的坐标
springboot为了更方便的测试spring程序,提供了springboot整合单元测试的一个起步依赖。
添加完毕,一定记得刷新Maven。
(II)第二步。调用API,生成令牌。
(III)生成"JWT令牌"的代码不需要去记忆
- 因为将来在公司中使用的时候,都是使用提供好的工具类,然后直接调用即可
(3)IDEA中写单元测试的方法测试生成JWT令牌
- 在test目录的feisi目录下,新建一个类"JwtTest"。在这个类的内部去写单元测试的代码。
- 新建一个方法testGen(),并且需要在方法上添加一个注解@Test。
- 如果要生成令牌,就要调用JWT的API。然后先调用其create()方法。
- 然后利用链式编程的方式来调用方法。首先调用第一个方法"withClaim()",这个方法的作用是添加"载荷"(前面讲过)。
- 这个添加载荷的方法就是:第一值传入键的名字(name)(如这里设置为"user",那么将来这个载荷就是承载着用户相关的信息),第二个参数值放一个map集合,比如user将来肯定有"id"、"username"等等。
JWT.create() .withClaim("user",claims);
- 所以接着就要创建一个Map类型的集合。指定其键是"String"类型,而值是"Object"类型的。
Map<String, Object> claims = new HashMap<>();
- 有了这个claims集合,就可以调用方法put()。如下添加。这样就提供添加"载荷"的方式,把当前用户信息添加进去了。
claims.put("id",1); claims.put("username","张三");
- 接下来JWT.create().后面还可以添加几个方法。
- 如.withExpiresAt()。这是添加过期时间。也就是登录时获得的令牌是有登录有效期的,时间过了,就需要重新登录。将来的Token令牌是有有效期的。
- 里面是需要一个Data对象。直接new Data()。但是这个是当前的时间,所以还需要通过System提供的获取当前毫秒值的方法,再重新new一个Data对象。往后延迟指定一段时间。
- 当前毫秒值+1000*60*60*12(也就是延后12个小时)。
JWT.create() .withClaim("user",claims) .withExpiresAt(new Date(System.currentTimeMillis()+1000*60*60*12));
- 如sign()方法。"sign"是签名的意思。也就是如上面说的数字签名,加密用的。方法参数需要指定一个加密算法。这里选择HMAC256()这个算法。
- 在指定算法的时候,需要指定一个密钥。这个是由自己定就行,就是一个字符串。但是它是加密的密钥,所以不要泄露,不然不能防篡改,保证安全。
.sign(Algorithm.HMAC256("feisi"));//指定算法,配置密钥
- 这几个方法一旦调用,我们的Token令牌就能生成。所以就要去接收一下Token。
package com.feisi; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import org.junit.jupiter.api.Test; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtTest { //生成JWT令牌的方法 @Test public void testGen(){ Map<String, Object> claims = new HashMap<>(); claims.put("id",1); claims.put("username","张三"); //生成JWT代码 String token = JWT.create() .withClaim("user",claims) //添加载荷 .withExpiresAt(new Date(System.currentTimeMillis()+1000*60*60*12)) //添加过期时间 .sign(Algorithm.HMAC256("feisi"));//指定算法,配置密钥 //打印输出到控制台,查看生成的token令牌 System.out.println(token); } }
- 最后运行一下这个测试类的方法。这样"JWT令牌"的生成就完成了。
四、JWT令牌的验证
(1)DEA中写单元测试的方法测试"JWT令牌"的验证
- 首先这一部分的代码也不需要去背或者记忆。
- 接着上面,在"testGen()"方法下添加一个方法"testParse()"。
- 也是一样记得添加单元测试的注解@Test。
- 接着定义一个字符串"token",模拟存储用户传递给浏览器的token令牌。
- 而这个token的值就用上面控制台打印的token令牌字符串就行。
//验证JWT令牌的方法 @Test public void testParse(){ //定义字符串,模拟用户传递给浏览器的token令牌 //这个token就用上面生成的token令牌字符串就行 String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +".eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3MjcwMzg2MDl9" +".LgfVNY-OAkjAps8yQXDkKyCIRbWw5jNQiNZwwbMf2F4"; }
- 接着开始提供验证了。去提供调用JWT这个类里面的一个静态方法"require()"。这是申请一个JWT的验证器。
- 而这个方法里面的参数需要传递一个算法。之前加密用的算法,解密也要同样用一样的算法。直接复制过来。
- 然后再调用一个".build()"方法去生成验证器。再用一个变量"jwtVerifier"给他存起来。
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("feisi")).build();
- 有了这个变量之后,就可以调用验证器的方法,去验证这个token令牌字符串。
- 调用方法".verify()",把之前控制台的token传递给它。
- 它可以去解析这个token令牌。然后生成一个解析后的JWT对象。
DecodedJWT decodedJWT = jwtVerifier.verify(token); //验证token,生成一个解析后的JWT对象
- 如果能正常的解析成功之后。那么就意味着可以从变量"decodedJWT"里面获取到当前的"头部"或者"载荷"以及"数字签名"等等。
- 则调用它的一个方法:"GetClaims()",而它会得到所有的"载荷"。用Map对象的变量去接收一下它。
Map<String, Claim> claims = decodedJWT.getClaims();
- 然后我们指定获取键名(name)为"user"的载荷。并且把得到的结果输出到控制台里面。
package com.feisi; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import org.junit.jupiter.api.Test; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtTest { //生成JWT令牌的方法 @Test public void testGen(){ Map<String, Object> claims = new HashMap<>(); claims.put("id",1); claims.put("username","张三"); //生成JWT代码 String token = JWT.create() .withClaim("user",claims) //添加载荷 .withExpiresAt(new Date(System.currentTimeMillis()+1000*60*60*12)) //添加过期时间 .sign(Algorithm.HMAC256("feisi"));//指定算法,配置密钥 //打印输出到控制台,查看生成的token令牌 System.out.println(token); } //验证JWT令牌的方法 @Test public void testParse(){ //定义字符串,模拟用户传递给浏览器的token令牌 //这个token就用上面生成的token令牌字符串就行 String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +".eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3MjcwMzg2MDl9" +".LgfVNY-OAkjAps8yQXDkKyCIRbWw5jNQiNZwwbMf2F4"; JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("feisi")).build(); DecodedJWT decodedJWT = jwtVerifier.verify(token); //验证token,生成一个解析后的JWT对象 Map<String, Claim> claims = decodedJWT.getClaims(); System.out.println(claims.get("user")); } }
- 得到控制台的正常输出,如:"id,1"、"username,"张三""等等就解析成功了!
- 正常的解析成功如下。
(2)测试因篡改"JWT令牌"导致验证失败的几种情况
(I)篡改JWP令牌"头部部分"进行验证。
(II)篡改JWP令牌中间"载荷部分"进行验证。
所以我们的JWT令牌是具体放篡改的功能的。
(III)篡改JWP令牌尾部"数字签名"(篡改密钥)进行验证。
- 篡改了原来设定的密钥"feisi"改成"fesi"。
(3)处理因篡改JWT令牌的数据导致的异常
(I)如果篡改了头部和载荷部分的数据,那么验证失败。
(II)如果篡改了密钥数据,那么验证失败。
(III)如果超过设定的Token令牌过期时间,那么验证失败。
(Token过期)
- 例如如下情况:报错提示"这个Token令牌"已经过期。
(4)关于"JWT令牌"验证的注意事项
五、总结
(1)JWT令牌的组成
- "载荷"这一块要注意。不要存放一些私密信息,因为"base64"算法不是一个加密算法,它是公开的,每个人都能使用
- 通过密钥与加密算法去验证Token令牌的合法性
(2)JWT令牌的使用
(借助工具:"JAVA-JWT")
(3)尾言
下篇博客就要开始继续完成用户登录认证的接口开发了!也就是把令牌的生成与验证加入到登录功能中。