一.介绍
在当今的Web开发中,安全认证和授权变得尤为重要。JWT(全称JSON Web Token)是一个开放的行业标准(RFC 7519),用于客户端和服务器之间传递安全可靠的信息,本质上是一个token
(token表示令牌,本质上为字符串
),是一种紧凑的URL安全方法
二.JWT的组成
JWT由三部分组成,每个部分中间使用(.)分割,例如aa.bb.cc
Header(头部)
:头部包括令牌的类型(即JWT)以及使用的哈希算法(如HMAC SHA256或RSA)Payload(负载)
:负载部分是存放有效信息的地方,里面是一些自定义内容,比如用户ID,用户名等,也可以存放JWT提供的现场字段,比如过期时间戳EXP等Signature(签名)
:签名部分用于防止JWT内容被篡改,当签名部分中任何一个字符被篡改,整个令牌都会校验失败,很大程度上确保了JWT令牌的安全性
三.JWT的常见应用
在企业开发中,JWT令牌多被用于解决会话跟踪
(例如登录认证),其流程可以简单描述为如下步骤:
- 服务器具备生成令牌和验证令牌的能力
- 用户登录请求,经过负载均衡,把请求转给了第一台服务器,第一台服务器进行账号密码验证,验证成功后,生成一个令牌,并返回给客户端
- 客户端收到令牌之后,把令牌存储起来,可以存储在Cookie中,或者也可以存储在其他存储空间中(如localStorage)
- 用户登录成功之后,携带令牌进一步操作,此时请求转发到了第二台服务器,第二台服务器会先进行权限验证操作,验证令牌是否有效,如果有效就说明用户已经执行了登录操作,反之如果令牌无效说明用户未执行登录操作
四.JWT的优缺点
优点:
- 解决了
集群
环境下的认证问题 - 减轻服务器的
存储压力
(无需在服务器端存储)
缺点:
- 需要自己实现令牌的生成,传递和校验
五.JWT的使用
1.引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
2.生成密钥
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.security.Key;
@SpringBootTest
public class JWTTest {
@Test
public void test(){
// 使用HS256签名算法创建一个密钥对象
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 将密钥编码为Base64字符串
String secretString = Encoders.BASE64.encode(key.getEncoded());
System.out.println(secretString);
}
}
3.JWT工具包实现
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class JwtUtil {
/**
* expiration 过期时间 (ms)
* secretString Base64编码的密钥(上述测试方法随机生成的密钥)
* key 解码secretString生成的HMAC SHA密钥
*/
private static long expiration = 60 * 60 * 1000;
private static String secretString = "WPSZRvzliySfPi1vb/LOdqOPJD0EdQRPnstMiVako6o=";
private static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));
/**
* 生成JWT令牌
* @param claim
* @return
*/
public static String generateJwt(Map<String, Object> claim) {
String jwt = Jwts.builder()
.setClaims(claim) // 自定义内容(载荷)
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + expiration)) // 设置过期时间
.signWith(key) // 签名算法
.compact();
return jwt;
}
/**
* 验证JWT令牌
* @param jwt
* @return
*/
public static Claims verifyJwt(String jwt) {
if (!StringUtils.hasText(jwt)) {
return null;
}
JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
Claims body = null;
try {
// 解析token
body = build.parseClaimsJws(jwt).getBody();
} catch (ExpiredJwtException e) { //令牌过期
log.error("令牌过期");
} catch (SignatureException e) { //令牌错误(伪造)
log.error("令牌错误(伪造)");
} catch (MalformedJwtException e) { //不存在令牌
log.error("未查找到令牌");
}
return body;
}
/**
* 从jwt中获取对应字段(如id)
* @param jwt
* @return
*/
public static Integer getIdFromToken(String jwt) {
Claims claims = verifyJwt(jwt);
if (claims == null) {
log.error("Jwt令牌错误");
return null;
}
Map<String,Object> map = new HashMap<>(claims);
return (Integer) map.get("id");
}
}