概述
-
JWT 基于 token 的鉴权机制,基于 token 的鉴权机制类似于 http 协议也是无状态的,它不需要服务端保留用户认证信息或回话信息,这意味着基于 token 认证机制不需要考虑存在哪台服务器,为应用扩展提供了便利,它的运行流程:
- 用户使用用户密码来请求服务器
- 服务器进行验证用户信息
- 服务器通过验证发送用户一个token
- 客户端存储token,并在每次请求时在HTTP头附送上这个token值
- 服务端验证token值,并返回数据
通常把JWT Base64编码后保存在HTTP请求头里,这个 token 在每次请求时传给服务端,另外,服务端要支持CORS(跨域访问):Access-Control-Allow-Origin:*
构成
- 第一部分称它为头部(header),第二部分为载荷(payload),第三部分为签证(signature),将这三部分连接成一个完整字符串就构成了 JWT
-
header:
- jwt 头部承载两部分信息,声明类型,此处为 jwt ,声明加密算法,通常为 HMAC SHA256,完整头部 JSON 如:{'typ':'JWT','alg':'HS256'}
- 然后进行 base64 加密(可以对称解密),构成第一部分:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
-
payload:
- 载荷是存放有效信息的地方,包括三部分:标签中注册的声明、公共的声明、私有的声明
- 标准中的注册的声明如:iss(jwt签发者)、sub(jwt所面向用户)、aud(接收jwt的一方)、exp(jwt过期时间)、nbf(定义jwt生效时间)、iat(jwt签发时间)、jti(jwt唯一身份标识,作为一次性token,从而回避重放攻击)
- 公共的声明:公共的声明可以添加任何信息,一般为用户相关信息或业务信息,但不要加敏感信息,该部分在客户端可解密
- 私有的声明:私有声明是提供者和消费者所共同定义的声明,不建议存放敏感信息,因为是base64对称加密,该部分信息可以归类为铭文信息
- 定义一个payload:{"sub":"1234567890","name":"John Doe","admin":true}
- base64加密得到 JWT 第二部分:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
-
signature:
- 该部分是一个签证信息,由三部分组成:header(base64后)、payload(base64后)、secret(自定义密钥)
- 由加密后的 header 和 payload 连接组成的字符串,然后通过 header 中声明的加密方式进行 secret 组合加密,构成 JWT 第三部分
- 注意:secret 是保存在服务端的,JWT 签发生成也是在服务端,secret 就是用来进行 JWT 签发和 JWT 验证的,所以,它是服务端的私钥,任何场景都不能泄漏出去,一旦客户端知道这个 secret 则意味客户端可以自我签发 JWT
-
header:
总结
优点:
■ 因 JSON 通用性,所以 JWT 可跨语言支持
■ payload 可存储业务逻辑信息(非敏感性)
■ 便于传输,字节占用很小
■ 不需要存储在服务端,易于扩展
安全相关:
■ 不应该在 JWT 的 payload 存储敏感信息,该部分是可以解密的
■ 保护好 secret 私钥,该私钥非常重要
■ 如果可以采用 HTTPS 协议
与 Session 区别
- Session 通过 cookie 传输,存储在服务器,服务器通过 cookie 中的 sessionID 获取当前会话用户,对于单台服务器没问题,但多服务器就涉及到共享 Session ,而且认证用户多时 Session 会占用大量服务器内存
- JWT 存储在客户端,服务器不需要存储 JWT ,JWT 里有用户 ID,服务器拿到 JWT 后验证可以获得用户信息,也就实现了 Session 功能,但是是无状态的,只要签名秘钥足够安全就能保证 JWT 可靠性
JWT 为什么能保证信息可靠性?
- 比如现在有 token :
eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0OTg0ODIxNTQsInN1YiI6InF1ZGluZyIsInVzZXJJZCI6IjEwMzc5NDAxIiwicm9sZSI6ImFkbWluIn0.-YFTYJ6FLlIQqD4G3hYcWvYlYE8H9eAA2369WEcJFVY
{
"alg": "HS256"
}
{
"exp": 1498482154,
"sub": "linyuan",
"userId": "123456",
"role": "admin"
}
Sign ��6蒥!ୡaůbV��^땄pU
- 假设 payload 被劫持了,其他人把 userId 修改为自己的,如 123456 ,但没有签名秘钥,所以没法生成签名,服务端收到该 Token 后,先用 Base64 解码出信息,然后重新生成 Signtrue,使用该 Signtrue 与客户端传来的 Signtrue 对比,一样则证明没被修改,否则拒绝请求
JJWT使用
- 通过Maven引入JJWT库:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
- 签发 JWT
public static String createJWT() {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id) // JWT_ID
.setAudience("") // 接受者
.setClaims(null) // 自定义属性
.setSubject("") // 主题
.setIssuer("") // 签发者
.setIssuedAt(new Date()) // 签发时间
.setNotBefore(new Date()) // 失效时间
.setExpiration(long) // 过期时间
.signWith(signatureAlgorithm, secretKey);// 签名算法以及密匙
return builder.compact();
}
- 验证 JWT
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
- 当 JWT验证失败时会抛出异常,常见异常有:
- SignatureException:签名错误异常
- MalformedJwtException:JWT格式错误异常
- ExpiredJwtException:JWT过期异常
- UnsupportedJwtException:不支持的JWT异常