JWT概念及使用详解
核心概念:JWT是什么?
JWT,全称是 JSON Web Token,是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。
你可以把它想象成一个数字身份证或安全通行证。当用户登录成功后,服务器会生成一个JWT并返回给客户端(通常是浏览器)。客户端在后续的请求中都需要带着这个“通行证”,服务器通过验证这个通行证来确认用户的身份和权限。
为什么在Java项目中使用JWT?(主要为了解决什么问题?)
在传统的Web应用中,我们使用 Session 机制来保持用户的登录状态。但Session有一些局限性,尤其是在现代分布式、微服务架构中:
服务器内存开销:每个用户的Session信息都存储在服务器内存中,用户量巨大时对服务器压力很大。
扩展性问题:当你有多台服务器(集群)时,需要做Session共享(Session Replication)或使用外部存储(如Redis),增加了架构的复杂性。
不适合跨域:在前后端分离、跨域API调用(如手机APP访问后端API)的场景下,基于Cookie的Session机制用起来很麻烦。
JWT的出现完美地解决了这些问题:
无状态(Stateless):服务器不需要存储任何用户状态。用户的全部认证信息都存储在JWT本身中,并由客户端携带。这使得服务器应用非常容易扩展。
跨域友好:可以轻松通过HTTP Header(通常是
Authorization: Bearer <token>
)进行传输,完美支持跨域请求和移动端。自包含(Self-contained):Payload部分可以包含用户ID、角色等所有需要的信息,减少多次查询数据库的需要。
JWT的组成结构
一个JWT通常看起来像这样(由三部分组成,用点.
分隔):
xxxxx.yyyyy.zzzzz
例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header(头部)
通常由两部分组成:令牌的类型(即"JWT")和所使用的签名算法(如HMAC SHA256或RSA)。
这是一个JSON对象经过 Base64Url 编码后形成的字符串。
示例解码后:
{"alg": "HS256", "typ": "JWT"}
Payload(负载)
包含了你要传递的“声明”(Claims)。声明是关于实体(通常是用户)和其他数据的陈述。
有三种类型的声明:
注册标准声明:预定义但不强制使用的标准字段,如
iss
(签发者),exp
(过期时间),sub
(主题),aud
(受众)等。公共声明:可以添加任何信息的自定义字段,但为了避免冲突,应在IANA JSON Web Token Registry中定义或使用抗冲突命名空间的名称。
私有声明:自定义的字段,用于在同意使用它们的各方之间共享信息。
同样是一个JSON对象经过 Base64Url 编码后形成的字符串。
示例解码后:
{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
Signature(签名)
这是最核心的部分,用于防止令牌被篡改。
签名是通过将编码后的Header、编码后的Payload、一个密钥(Secret)以及Header中指定的算法生成。
例如,使用HMAC SHA256算法的签名是这样创建的:
java
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
在Java项目中的典型工作流程
认证(Login):用户使用用户名密码登录。
生成JWT:服务器验证凭据有效后,根据用户信息(如userId, role)生成一个JWT,并使用一个只有服务器知道的密钥(Secret) 进行签名。然后将JWT返回给客户端(通常在响应体中)。
客户端存储:客户端(如浏览器)收到JWT后,通常会将其存储在本地(Local Storage, Session Storage或Cookie中)。
携带JWT访问:客户端在后续请求的HTTP Header中添加:
Authorization: Bearer <JWT>
。验证JWT:服务器接收到请求后,提取Header中的JWT:
检查签名是否有效(使用相同的密钥和算法进行验证),确保令牌未被篡改。
检查令牌是否过期(检查
exp
声明)。如果一切有效,就从Payload中解析出用户信息(如userId),并进行后续业务处理。
响应请求:服务器验证通过后,返回客户端请求的资源或数据。
常用的Java JWT库
在Java中,你不需要自己实现JWT的编码、解码和签名逻辑,可以使用成熟的开源库:
jjwt:一个非常流行、易于使用的库。
java-jwt:Auth0公司提供的库,功能强大。
Nimbus JOSE + JWT:一个更全面、功能更丰富的库。
示例(使用jjwt库生成一个JWT):
java
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import java.security.Key;// 生成一个安全的密钥(生产环境中应从一个安全的配置文件中读取,而不是硬编码) Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);// 构建JWT String jws = Jwts.builder().setSubject("1234567890") // 设置主题(通常是用户ID).claim("name", "John Doe") // 添加自定义声明.claim("admin", true) // 添加自定义声明.setIssuedAt(new Date()) // 设置签发时间.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 设置过期时间(1小时后).signWith(key) // 使用密钥签名.compact(); // 压缩生成最终的字符串System.out.println(jws);
注意事项
安全存储密钥:签名密钥是安全的核心,必须妥善保管,绝不能泄露。
不要在不安全的信道传输敏感信息:JWT的Payload只是Base64编码,并不是加密的(除非你使用了JWE)。绝对不要在JWT中存放密码等敏感信息。
管理Token过期:一定要为JWT设置合理的过期时间(
exp
),以减少令牌被盗用的风险。选择合适的存储方式:在浏览器端,存储在LocalStorage有XSS风险,存储在Cookie有CSRF风险,需要根据实际情况权衡并做好相应的安全防护。
总结来说,在Java项目中,JWT是一种用于实现无状态、可扩展的分布式用户认证和授权的令牌机制,它是构建现代API和微服务架构的基石之一。