1. 简介
JWT(JSON Web Token)是一种基于 JSON 格式的轻量级、自包含的身份验证和授权标准(RFC 7519)。它允许将用户信息和其他必要数据在各方之间以一种安全的方式进行传输。
-
结构:JWT 通常由三部分组成,用点(.)分隔:
-
Header(头部):包含令牌的类型(即 JWT)和所使用的签名算法(如 HMAC SHA256 或 RSA)。
-
Payload(负载):包含声明(Claims)。声明是关于实体(通常是用户)和其他数据的声明。它们可以被分为三类:注册的声明、公共的声明和私有的声明。
-
Signature(签名):用于验证消息在传输过程中未被篡改,并且,对于使用私钥签名的令牌,还可以验证发送者的身份。签名是使用头部中指定的算法和密钥生成的。
-
-
编码:JWT 的各部分在编码前通常是 JSON 格式,然后使用 Base64Url 编码。这种编码方式是 Base64 编码的变体,它使用 URL 安全的字符集。
-
无状态和可扩展:由于 JWT 包含所有必要的信息,因此它是无状态的,不需要在服务器端存储会话信息。这使得 JWT 非常适合分布式系统和微服务架构。
-
安全性:JWT 的安全性主要依赖于签名过程。使用强密钥和安全的签名算法可以确保 JWT 的完整性和真实性。然而,JWT 本身并不提供加密功能,如果需要保护令牌内容不被读取,可以使用额外的加密层。
-
应用场景:
-
身份验证:在用户登录后,服务器会创建一个 JWT 并发送给客户端。客户端在随后的请求中将 JWT 作为认证信息发送回服务器。
-
信息交换:JWT 可以安全地在各方之间传输信息,因为只有拥有正确签名的令牌才能被验证。
-
授权:JWT 可以用于控制用户对资源的访问权限,通过在负载中添加特定的权限声明来实现。
-
-
刷新机制:JWT 通常有一个过期时间。当令牌过期时,可以使用刷新令牌(refresh token)来获取新的访问令牌,而无需用户重新登录。
-
跨域认证:JWT 可以用于单点登录(SSO)系统,允许用户在多个域之间无缝切换,而无需重复登录。
2. 原理
2.1 基本结构
JWT 是一个由三部分组成的字符串,每部分通过 .
分隔:
2.1.1Header(头部) 用于描述令牌的元信息,通常包含两部分:
-
alg
:签名算法,例如HS256
(HMAC-SHA256)。 -
typ
:令牌类型,固定为JWT
。
{
"alg": "HS256",
"typ": "JWT"
}
经过 Base64Url 编码后:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2.1.2 Payload(负载) 包含实际传递的数据(声明),主要分为以下几类:
-
注册声明(Registered Claims):标准字段,如
iss
(签发者)、exp
(过期时间)、sub
(主题)、aud
(接收方)。 -
公共声明(Public Claims):自定义字段,需避免冲突。
-
私有声明(Private Claims):仅供双方使用的字段。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1717795799
}
经过 Base64Url 编码后:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTcxNzc5NTc5OX0
2.1.3 Signature(签名) 用于确保 JWT 的完整性和可信度。签名的生成公式如下:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
完整jwt token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTcxNzc5NTc5OX0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
2.2 工作原理
-
令牌生成:
-
用户通过登录接口发送用户名和密码。
-
服务器验证用户凭据,生成包含用户信息和声明的 JWT。
-
服务器将 JWT 返回给客户端,客户端通常将其存储在
localStorage
、sessionStorage
或Cookie
中。
-
-
携带令牌请求:
-
客户端在后续请求中,将 JWT 放在 HTTP 请求头中(通常是
Authorization: Bearer <token>
)。
-
-
令牌验证:
-
服务器接收到请求后,从请求头中提取 JWT。
-
使用签名密钥(
secret
)验证签名,确保令牌未被篡改。 -
如果签名有效且令牌未过期,服务器根据 Payload 中的信息执行相应的业务逻辑。
-
2.3 核心特性
-
无状态性:
服务器不需要存储用户的会话信息,所有信息都包含在 JWT 中。适用于分布式系统和微服务架构。
-
自包含性:
JWT 包含所有必要的信息,用户身份信息直接从 Payload 中提取,无需额外查询数据库。
-
签名验证:
签名确保令牌未被篡改,服务器通过密钥或公钥验证签名,确认 JWT 的完整性和真实性。
2.4 生命周期
- 令牌的有效期:
使用 exp
(Expiration Time)声明设置过期时间,确保令牌只能在特定时间内使用。
-
刷新令牌(Refresh Token):
为了避免频繁登录,可以使用短生命周期的 Access Token 和长生命周期的 Refresh Token:
- Access Token:用于访问资源,生命周期短。
- Refresh Token:用于获取新 Access Token,生命周期较长。
2.5 优缺点
优点
-
轻量化:相比传统的 Session 机制,JWT 无需服务器端存储会话信息。
-
跨语言支持:由于是基于 JSON 的标准,JWT 可以在不同语言和平台间互操作。
-
安全性:通过签名验证保证数据完整性,支持对敏感信息加密。
缺点
-
过期管理难:JWT 无法主动撤销,无法动态更新其内容。
-
体积较大:包含所有信息后,JWT 的体积较大,可能对性能产生一定影响。
-
明文存储问题:Payload 是明文的,敏感数据需要加密处理。
2.6 JWT 的应用场景
-
用户身份认证:
-
用于验证用户身份,尤其是无状态的单点登录(SSO)场景。
-
-
API 调用认证:
-
用于 RESTful API 的访问控制,客户端通过 JWT 证明自己的身份。
-
-
分布式系统:
-
在微服务架构中,通过 JWT 传递用户身份信息,减少跨服务的数据存储和验证成本。
-
2.7 最佳实践
-
使用 HTTPS 传输 JWT,防止中间人攻击。
-
配置短有效期,避免长期令牌泄露的风险。
-
使用 Refresh Token 机制支持令牌续期。
-
加密敏感信息,避免 Payload 数据泄露。
-
实现令牌黑名单机制,支持主动撤销特定令牌。
3. 示例
一个简单的小例子说明jwt在Java中怎么加密(encode)、解密(decode)。
3.1.1 添加依赖
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.12.1</version>
</dependency>
3.1.2 Demo
package com.xiaokai;import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.AllArgsConstructor;
import lombok.Data;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** Author:yang* Date:2024-12-06 16:26*/
public class Main {public static void main(String[] args) {Algorithm algorithm = Algorithm.HMAC256("yang"); // 设置秘钥---由服务器管理JWTCreator.Builder builder = JWT.create();Map<String, String> map = new ConcurrentHashMap<>();CloudUser cloudUser = new CloudUser("yang", "123456", "admin");String jsonString = JSONObject.toJSONString(cloudUser);map.put("bless", jsonString); // 设置mqp类型,只接收jwt接收类型,自定义类型不行// 设置payload,生成tokenString token = builder.withClaim("bless", map) // 声明.withSubject("test") // 主题.withIssuer("yang") // 执行人.sign(algorithm); // 算法签名// 解析tokenDecodedJWT jwt = JWT.require(algorithm).build().verify(token);Claim claim = jwt.getClaim("bless"); // 获取payloadMap<String, Object> claimMap = claim.asMap(); // 转化为对应的类型System.out.println(claimMap.get("bless")); // 输出value}}@Data
@AllArgsConstructor
class CloudUser {public String username;public String password;public String role;
}
测试结果:
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. // headereyJzdWIiOiJ0ZXN0IiwiaXNzIjoieWFuZyIsImJsZXNzIjp7ImJsZXNzIjoie1wicGFzc3dvcmRcIjpcIjEyMzQ1NlwiLFwicm9sZVwiOlwiYWRtaW5cIixcInVzZXJuYW1lXCI6XCJ5YW5nXCJ9In19. //payloadzKLC9WG87d1PgJdXoG_do1kwTYY4DQiTDT581jltvFQ // signature
object: {"password":"123456","role":"admin","username":"yang"}
4. 搭配权限验证框架
4.1 Spring Security + JWT
Spring Security 是 Java 中最常用的权限验证框架,具有强大的认证和授权功能。结合 JWT 使用时,Spring Security 可以处理用户身份验证和基于角色的权限控制。
4.1.1 引入依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
4.1.2 生成 JWT Token:
使用 io.jsonwebtoken
库生成 JWT:
public String generateToken(String username) {return Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1小时有效期.signWith(SignatureAlgorithm.HS256, "secret_key").compact();
}
4.1.3 创建过滤器验证 Token:
在每次请求时从 HTTP Header 中解析并验证 JWT:
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {String header = request.getHeader("Authorization");if (header != null && header.startsWith("Bearer ")) {String token = header.substring(7);try {String username = Jwts.parser().setSigningKey("secret_key").parseClaimsJws(token).getBody().getSubject();if (username != null) {UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (Exception e) {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);}}chain.doFilter(request, response);}
}
4.1.4 配置 Spring Security:
注册自定义过滤器,禁用默认的 Session 管理。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/auth/**").permitAll() // 允许未认证的请求访问.anyRequest().authenticated().and().addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}
}
4.2 Shiro + JWT
Apache Shiro 是另一款轻量级的权限验证框架,适合不需要复杂 Spring 框架的场景。
4.2.1 引入依赖:
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.8.0</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
4.2.2 创建 JWT 工具类:
用于生成和解析 Token。
public class JwtUtil {private static final String SECRET_KEY = "secret_key";public static String createToken(String username) {return Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1小时.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();}public static String getUsernameFromToken(String token) {return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();}
}
4.2.3 自定义 Realm 验证 JWT:
public class JwtRealm extends AuthorizingRealm {@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String jwt = (String) token.getCredentials();String username = JwtUtil.getUsernameFromToken(jwt);if (username == null) {throw new AuthenticationException("Token invalid");}return new SimpleAuthenticationInfo(jwt, jwt, getName());}
}
4.2.4 配置 Shiro:
@Configuration
public class ShiroConfig {@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(new JwtRealm());return securityManager;}
}