什么是JWT?JWT,全称JsonWebToken,是一种基于json的开发标准。下面的文章,将为大家详细地介绍一下JWT的特点、原理和数据结构以及在Java中是怎么去使用的。
JWT特点
JWT默认是不加密,但也是可以加密的。生成原始Token以后,可以用密钥再加密一次。
JWT不加密的情况下,不能将秘密数据写入JWT。
JWT不仅可以用于认证,也可以用于交换信息。有效使用JWT,可以降低服务器查询数据库的次数。
JWT的最大缺点是,由于服务器不保存session状态,因此无法在使用过程中废止某个token,或者更改token的权限。也就是说,一旦JWT签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
1.JWT的原理
Jwt官网:https://jwt.io/
JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,就像下面这样。
{"name":"JohnDoe","角色":"管理员","到期时间":"2018年7月1日0点0分"}
以后,用户与服务端通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何session数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
2.JWT的数据结构
JWT大概就像下面这样。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT内部是没有换行的。
JWT的三个组成部分依次如下。
·Header(头部)·Payload(负载)·Signature(签名)
写成一行,就是下面的样子
Header.Payload.Signature
2.1Header
Header部分是一个JSON对象,描述JWT的元数据,通常是下面的样子。
{"alg":"HS256","typ":"JWT"}
上面代码中,alg属性表示签名的算法(algorithm),默认是HMACSHA256(写成HS256);typ属性表示这个令牌(token)的类型(type),JWT令牌统一写为JWT。
最后,将上面的JSON对象使用Base64URL算法转成字符串。
2.2Payload
Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,供选用。
iss(issuer):签发人exp(expirationtime):过期时间sub(subject):主题aud(audience):受众nbf(NotBefore):生效时间iat(IssuedAt):签发时间jti(JWTID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子
{"sub":"1234567890","name":"JohnDoe","admin":true}
JWT默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个JSON对象也要使用Base64URL算法转成字符串。
2.3Signature
Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面指定的签名算法(默认是HMACSHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)`
算出签名以后,把Header、Payload、Signature三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。
3.在Java中使用
3.1引入依赖
<!-- Jwt https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
3.2代码例子
package com.suibibk.jwt;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import io.jsonwebtoken.impl.Base64Codec;import java.util.Date;import java.util.HashMap;import java.util.Map;class DemoApplicationTests {// 加盐秘钥private static String secret = "jwtSecretValueqweruqwyeiquweyi";public static void main(String[] args) throws InterruptedException {// 创建tokenString token = createToken(5);System.out.println("token: " + token);// 解析token, jwt是经过Base64编码的String[] ts = token.split("\\.");System.out.println("Header(头部): " + Base64Codec.BASE64.decodeToString(ts[0]));System.out.println("Payload(负载): " + Base64Codec.BASE64.decodeToString(ts[1]));System.out.println("Signature(签名): " + Base64Codec.BASE64.decodeToString(ts[2]));//这里休息六秒后会导致抛出异常ExpiredJwtException,表明token已经失效了//Thread.sleep(6 * 1000);// 解析tokenparseToken(token);}// 创建tokenpublic static String createToken(Integer time) {// 自定义信息Map<String, Object> map = new HashMap<>();map.put("name", "admin");String token = Jwts.builder().setClaims(map)// 自定义内容接受一个map.setId("9527") // 唯一id.setSubject("jwtSubject") // JWT的主体.setExpiration(new Date(System.currentTimeMillis() + time * 1000))// 设置过期时间 time秒.setIssuedAt(new Date())//设置签发时间.signWith(SignatureAlgorithm.HS256, secret)// 设置签名算法和加盐秘钥.compact();return token;}// 解析tokenpublic static void parseToken(String token) {System.out.println("====================开始解析JWT====================");try {Claims body = Jwts.parser().setSigningKey(secret)// 签名秘钥.parseClaimsJws(token)// 要解析的jwt.getBody();System.out.println("id: " + body.getId());System.out.println("sub: " + body.getSubject());System.out.println("自定义内容 name: " + body.get("name"));System.out.println("令牌签发时间: " + body.getIssuedAt());System.out.println("令牌过期时间: " + body.getExpiration());} catch (Exception e) {e.printStackTrace();System.out.println("无效Token");}System.out.println("====================JWT解析结束====================");}}
运行输出
token: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqd3RTdWJqZWN0IiwibmFtZSI6ImFkbWluIiwiZXhwIjoxNjg5MDY2OTk3LCJpYXQiOjE2ODkwNjY5OTIsImp0aSI6Ijk1MjcifQ.-b2wY6_Zjeq0GV1xxMK7TEtYYAN_5VXwVj99MwV6CV8Header(头部): {"alg":"HS256"}Payload(负载): {"sub":"jwtSubject","name":"admin","exp":1689066997,"iat":1689066992,"jti":"9527"Signature(签名): ????[乱码,这是因为这里是签名]====================开始解析JWT====================id: 9527sub: jwtSubject自定义内容 name: admin令牌签发时间: Tue Jul 11 17:16:32 CST 2023令牌过期时间: Tue Jul 11 17:16:37 CST 2023====================JWT解析结束====================
如果放开
//Thread.sleep(6 * 1000);
我们设置的过期时间是5秒,所以休息6秒后就会抛出异常
====================开始解析JWT====================io.jsonwebtoken.ExpiredJwtException: JWT expired at 2023-07-11T17:19:57Z. Current time: 2023-07-11T17:19:59Z, a difference of 2164 milliseconds. Allowed clock skew: 0 milliseconds.at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:385)at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)at com.suibibk.jwt.DemoApplicationTests.parseToken(DemoApplicationTests.java:55)at com.suibibk.jwt.DemoApplicationTests.main(DemoApplicationTests.java:30)无效Token====================JWT解析结束====================
我们捕获到这个异常就表明已过期了,如果有人篡改就会报签名异常
====================开始解析JWT====================io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:354)at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)at com.suibibk.jwt.DemoApplicationTests.parseToken(DemoApplicationTests.java:55)at com.suibibk.jwt.DemoApplicationTests.main(DemoApplicationTests.java:30)无效Token====================JWT解析结束====================
