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); } }