Java - JWT的简单介绍和使用
Java - JWT的简单介绍和使用
前言
目前自己在做一个云直播个人项目,后端架构是微服务,目前准备用JWT
来做Token
的校验。借此机会来复习和学习一遍JWT
的相关知识。
一. JWT 基础知识
JWT
的全称是JSON Web Tokens
,主要是服务器认证相关信息之后,生成一种JSON
数据,并通过一种算法(例如HMAC
)对其进行安全的签名加密。主要用于两个用途:
- 数据交换:因为
JWT
使用JSON
来存储相关数据,而JSON
这一种数据格式可以在各方之间传递。 - 安全验证:登录之后,后续的请求可以携带
JWT
,只有携带了JWT
(认证Token
)的请求才能够正常地访问到相关的数据及资源。
一般我们在开发过程中,凡是涉及到用户登录的,我们就需要去考虑用什么去存储用户登录的一个状态,本文只说两种:
session
(基于cookie
的实现):只不过session
相关的数据存储于服务器。JWT
:一旦生成,一般就抛给客户端去保存。客户端只需要每次携带这个Token
就可以正常地访问接口。
1.1 session 案例测试
我们来写一个简单的案例:pom
依赖:
<parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.3.2.RELEASEversion>parent><dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency>dependencies>
Controller
代码:
@RestControllerpublic class UserController { @GetMapping("/login") public String login(HttpServletRequest request) { request.getSession().setAttribute("user", "Ljj"); return "Login Success"; }@GetMapping("/getUser") public String getUser(HttpServletRequest request) { String user = (String) request.getSession().getAttribute("user"); return user; }}
那么我们项目启动之后,访问以下路径:http://localhost:8080/login
,就能发现当前客户端会多出一个名为JSESSIONID
的Cookie
:
之后我们的每一次请求,都会自动携带上这个cookie
去访问服务端:
如果我们采取session
来存储用户信息,那么大致流程就是如下:
- 用户登录成功,那么我们就
request.getSession().setAttribute("user"+userId, "用户信息")
。 - 此时服务端就会写入一个session来保存相关的数据(基于cookie实现)。因此客户端能看到一个名为
JSESSIONID
的Cookie
。 - 后续的相关请求,都会自动携带这个
Cookie
,后端就可以取到这个用户信息了。
但是,采用session
这种方式,是以本地缓存来存储相关的数据的,当有100个用户进行登录的时候,就需要在本地缓存存储100个信息。因此会给服务器带来一定的内存压力。
相关的数据存储于StandardSession.attributes
字段中:
public class StandardSession implements HttpSession, Session, Serializable {protected ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>();}
因此现在主流的都是使用JWT
来代替传统的session
存储方案。
1.2 JWT 结构
JWT
如上文所说,他是一个JSON
串,但是它有着自己的特定结构:
Header.Payload.Signature
Header
和Payload
部分都是一个base64
编码串,三个结构之间通过 ” . “
进行连接。
1.2.1 Header
Header
头部,通常有两个部分组成:
alg
:表示签名的算法类型,比如HMAC SHA256
或者RSA
。typ
:代表这个token
令牌的类型,比如JWT
。
案例如下:
{ "alg": "HS256", "typ": "JWT"}
最后这个JSON
串会通过Base64Url
进行编码,然后作为JWT
的第一部分。
1.2.2 Payload
JWT
的第二部分就是这个Payload
了,它的中文含义叫做:有效载荷。相当于我们HTTP
请求的一个请求体了。一般用来存储我们实际要传递的数据。例如:
{ "sub": "1234567890", "name": "John Doe", "admin": true}
Payload
本身还规定了几个属性,同时它可以分为三种类型:
标准注册声明:
iss (issuer)
:签发人。exp (expiration time)
:过期时间。sub (subject)
:主题。aud (audience)
:接收方。nbf (not before)
:生效时间。iat (issued at)
:签发时间。jti (jwt id)
:编号。
公共声明: 一般用于我们自己定义一些业务属性。
私有声明: 服务器和客户端共同定义的声明
这一部分的内容可见比较丰富,因为我们可以将自定义的属性塞进去,但是有一点我们需要格外地注意:Payload部分不建议添加敏感信息,例如密码、身份证等信息。因为Payload
这一部分最终也是经过Base64Url
进行编码,然后作为JWT
的第二部分暴露给客户端。因此这个是可以被解码的。
1.2.3 Signature ☆
上面的两个JWT
组成部分:Header
和Payload
,我们知道它是由Base64Url
进行编码的,那么自然而然的它就可以被解码。那么JWT
的安全性谈何而来?这就得看最后一个部分Signature
签名了。他的作用一句话概括就是:对前两部分的内容进行算法签名,防止数据篡改。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
这个代码是啥意思呢?
- 拿到经过
Base64
编码的Header
和Payload
部分。以及我们自己提供的一个秘钥secret
。 - 使用
Header
中指定的签名算法:HMAC SHA256
进行签名。
签名的目的:
- 如果有人对头部以负载内容进行解码,然后篡改相关信息,在进行
base64
编码重新组合一个新的JWT
。 - 那么这个
JWT
传输给服务器后,服务器能够判断,新的头部和新的负载内容形成的签名和原有的不一致。 - 那么此时就能够判断这个
JWT
是不合法的。因为外部使用者无法得知你进行签名时的秘钥是什么。 因此通过修改JWT
算出来签名是不一样的。
那么最后再来说下JWT
的特点:
JWT
的两个组成部分虽然是经过base64
编码的,因此他可以被解码。但是JWT
本身是可以被加密的。这样就更加安全。JWT
生成之后,不必存储在服务器端,就没有使用session
存储那样,有着内存压力。JWT
无法使服务器保存会话状态,当令牌生成后在有效期内无法取消也不能更改。- 安全:因为
Token
不是Cookie
。我们只需要每次请求的时候携带Token
即可。由于没有Cookie
被发送,还有助于防止CSRF
攻击。 - 不要再
JWT
中存储一些敏感信息,一般都是存储userId
。
二. JWT 简单使用
首先准备pom
依赖:
<dependency> <groupId>junitgroupId> <artifactId>junitartifactId> <scope>testscope>dependency><dependency> <groupId>com.auth0groupId> <artifactId>java-jwtartifactId> <version>3.19.1version>dependency>
2.1 生成JWT
public class JwtTest { // 秘钥,你可以随便取,可以取的难一点 public static final String SECRET = "ASD!@#F^%A"; @Test public void testTokenCreate() { HashMap<String, Object> headers = new HashMap<>(); // 过期时间,60s Calendar expires = Calendar.getInstance(); expires.add(Calendar.SECOND, 600); String jwtToken = JWT.create() // 第一部分Header .withHeader(headers) // 第二部分Payload .withClaim("userId", 20) .withClaim("userName", "LJJ") .withExpiresAt(expires.getTime()) // 第三部分Signature .sign(Algorithm.HMAC256(SECRET)); System.out.println(jwtToken); }}
结果如下:看红圈的两个"."
符号,可见JWT
的格式和第一章节描述的相吻合。
那么拿到令牌了,我们接下来就是根据令牌去解析数据。
2.2 解析JWT
注意:这里你要把2.1节生成的token
自己复制一下,贴到代码里面。
@Testpublic void testReadJWT() { // 创建一个验证的对象 JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6IkxKSiIsImV4cCI6MTY2NzcyMTMzMiwidXNlcklkIjoyMH0.AJA88B0F-zKEJUCIGp9kSx2TlbpoyH88GzEn-9xN5XI"); System.out.println(verify.getClaim("userId").asInt()); System.out.println(verify.getClaim("userName").asString()); System.out.println("过期时间:" + verify.getExpiresAt());}
结果如下:
2.3 常见的异常
这里贴出4个常见的异常,如果JWT
校验不通过,就会抛出异常:
SignatureVerificationException
:签名不一致。TokenExpiredException
:令牌过期。AlgorithmMismatchException
:算法不匹配异常。InvalidClaimException
:失效的Payload
异常。一般存在于这种情况:获取token
的服务器比使用token
的服务器时钟快,请求分发到时间慢的服务器上导致token
还没生效。
来源地址:https://blog.csdn.net/Zong_0915/article/details/127714702
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341