JWT 简介
JWT 简称 JSON Web Token,也就是通过 JSON 形式作为 Web 应用中的令牌,用于各方之间安全地将信息作为 JSON 对象传输,在数据传输的过程中还可以完成数据加密、签名等相关处理。
注意:JWT 的三个部分的 Header 和 Payload都是明文存储!只不过内容通过 Base64 转码了!所以不要将重要信息存储在 JWT 中!
认证流程
- 首先,前端通过 Web 表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个 HTTP POST 请求。建议的方式是通过 SSL 加密的传输(HTTPS),从而避免敏感信息被嗅探。
- 后端核对用户名和密码成功后,将用户的 ID 等其他信息作为 JWT Payload(负载),将其与头部分别进行 Base64 编码拼接后签名,形成一个 Token。形成的 Token 就是一个形同
head.payload.singurater
的字符串。 - 后端将 Token 字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在 localStorage 或 sessionStorage 上,退出登录时前端删除保存的 Token 即可。
- 前端在每次请求时将 Token 放入 HTTP Header 中的 Authorization 位。
- 后端检查是否存在,如存在验证 Token 的有效性。例如,检查签名是否正确;检查 Token 是否过期;检查 Token 的接收方是否是自己(可选)。
- 证通过后后端使用 Token 中包含的用户信息进行其他逻辑操作,返回相应结果。
JWT 结构
JWT 的组成有三部分:标头(Header)、有效载荷(Payload)、签名(Signature)。
Header(Base64Url).Payload(Base64Url).Secret(Header(Base64Url)+ Payload(Base64Url))
标头 Header
标头通常由两部分组成:令牌的类型和所使用的签名算法,例如 HMAC SHA256(默认)或 RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。
注意:Base64 是一种编码,也就是说,它是可以被翻译回原来的样子的,它并不是一种加密过程。
有效载荷 Payload
将能用到的用户信息放在 Payload 中。官方建议不要放特别敏感的信息,例如密码。
签名 Signature
签证由三部分组成,header 和 payload 分别经 Base64Url(一种在 Base64 上做了一点改变的编码)编码后由 .
连接,服务器生成秘钥(secret),连接之后的字符串在经 Header 中声明的加密方式和秘钥加密,再用 .
和加密前的字符串连接。服务器验证 Token 时只会验证第三部分。
使用JWT
导入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
创建工具类
package world.xuewei;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
public class JwtUtils {
/**
* 密钥
*/
private static final String SECRET = "xuewei";
/**
* 获取Token,默认过期时间为1天
*/
public static String getToken(Map<String, String> map) {
return getToken(map, Calendar.DATE, 1);
}
/**
* 获取Token,指定过期时间
*/
public static String getToken(Map<String, String> map, Integer calenderType, Integer count) {
// 创建令牌的过期时间
Calendar instance = Calendar.getInstance();
instance.add(calenderType, count);
// 创建令牌建造者,封装请求参数
JWTCreator.Builder builder = JWT.create();
map.forEach(builder::withClaim);
// 设置过期时间
builder.withExpiresAt(instance.getTime());
// 设置加密算法和密钥
return builder.sign(Algorithm.HMAC256(SECRET));
}
/**
* 验证Token的合法性
*
* @throws TokenExpiredException Token过期
* @throws SignatureVerificationException 密钥不匹配
* @throws AlgorithmMismatchException 算法不匹配
* @throws JWTDecodeException Token异常
*/
public static DecodedJWT validate(String token) {
return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
}
/**
* 获取Token中包含的信息
*
* @throws TokenExpiredException Token过期
* @throws SignatureVerificationException 密钥不匹配
* @throws AlgorithmMismatchException 算法不匹配
* @throws JWTDecodeException Token异常
*/
public static Map<String, String> getPayloadMap(String token) {
DecodedJWT verify = validate(token);
Map<String, Claim> claims = verify.getClaims();
// 转换为标准的Map
Map<String, String> result = new HashMap<>();
claims.forEach((k, v) -> {
result.put(k, v.asString());
});
return result;
}
}
测试代码
package world.xuewei;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import org.junit.Test;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
public class JwtTest {
/**
* 创建 Token
*/
@Test
public void testCreateToken() {
// Payload Map
Map<String, String> payload = new HashMap<>();
payload.put("userId", "1001");
payload.put("roleCode", "admin");
// 创建默认时效 Token 1天
String defaultToken = JwtUtils.getToken(payload);
System.out.println("defaultToken = " + defaultToken);
// 创建自定义时效 Token 1 分钟
String customToken = JwtUtils.getToken(payload, Calendar.MINUTE, 1);
System.out.println("customToken = " + customToken);
}
/**
* 验证 Token
*/
@Test
public void testValidateToken() {
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlQ29kZSI6ImFkbWluIiwiZXhwIjoxNjkyMTczMDk4LCJ1c2VySWQiOiIxMDAxIn0.P7Qc0agYIkuB3XuLs76tuxb9Z10a75srffwATzM45Nw";
try {
JwtUtils.validate(token);
} catch (TokenExpiredException e) {
throw new RuntimeException("Token 已过期");
} catch (SignatureVerificationException e) {
throw new RuntimeException("Token 密钥不匹配");
} catch (AlgorithmMismatchException e) {
throw new RuntimeException("Token 算法不匹配");
} catch (JWTDecodeException e) {
throw new RuntimeException("Token 异常");
}
System.out.println("Token 验证通过");
}
/**
* 解析 Token
*/
@Test
public void testParseToken() {
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlQ29kZSI6ImFkbWluIiwiZXhwIjoxNjkyMTczMDk4LCJ1c2VySWQiOiIxMDAxIn0.P7Qc0agYIkuB3XuLs76tuxb9Z10a75srffwATzM45Nw";
// 此方法中存在 Token 有效性验证的逻辑,故也需要异常捕获处理
Map<String, String> payloadMap;
try {
payloadMap = JwtUtils.getPayloadMap(token);
} catch (TokenExpiredException e) {
throw new RuntimeException("Token 已过期");
} catch (SignatureVerificationException e) {
throw new RuntimeException("Token 密钥不匹配");
} catch (AlgorithmMismatchException e) {
throw new RuntimeException("Token 算法不匹配");
} catch (JWTDecodeException e) {
throw new RuntimeException("Token 异常");
}
System.out.println("payloadMap = " + payloadMap);
}
}