浅谈JWT,需要了解的认证登录方案
JWT是目前常用的用于身份验证和授权机制。能够在不同的应用程序之间安全的传输用户的身份信息。
1. 传统的认证方式(cookie-session)
- 用户输入用户名密码,登录系统
- 到达服务器后,服务器会验证用户名、密码是否正确,密码正确则生成session信息,将sessionId返回给客户端,客户端存入cookie
- 下次客户端再次请求时,自动带上cookie,服务端最终通过携带的session信息进行校验。

1.1 缺点
- cookie是无法跨域的(即使是子域也不行),比如在天猫商城网站中会有天猫超时、天猫国际这些链接,且域名是不一样的,不能让用户跳转一次登录一次吧。
- 如果部署的是分布式的,还要考虑各部署服务的session同步问题。当然了可以通过各服务之间同步或者中间加一层比如利用redis存储session.
2. JWT该出场了
JWT是一种cookie-session改造版的具体的实现。他可以让你不用再服务端存储认证信息(比如token),完全由客户端提供,服务端只需要根据JWT的解密算法就可以验证用户的合法性,且过程是安全的。
2.1 JWT数据结构
JWT的最终的形式其实就是一个字符串,它由三部分组成 头部、载荷、签名。这三部分在字符串中用“.”分隔开。
各部分是由base64生成的字符串。

2.2.1 头部
头部是以JSON格式存储,是用来指明加密算法和令牌类型,例如
{
"alg": "HS256",
"typ": "JWT"
}2.2.2 载荷
载荷是用来存储具体需要的用户信息,比如:用户名的信息,姓名、年龄、性别等
{
"name": "用户名",
"sex": "性别男"
}2.2.3 签名
签名有一个计算公式
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
Secret
)使用HMACSHA256算法计算得出,这个方法有两个参数,前一个参数是 (base64 编码的头部 + base64 编码的载荷)用点号相连,后一个参数是自定义的字符串密钥,密钥不要暴露在客户端,只应该服务器知道。
3. JWT怎么保证安全性的呢
保证安全性的关键就是 HMACSHA256 或者与它同类型的加密算法,因为加密过程是不可逆的,所以不能根据传到前端的 JWT 传反解到密钥信息。
另外,不同的头部和载荷加密之后得到的签名都是不同的,所以,如果有人改了载荷部分的信息,那最后加密出的结果肯定就和改之前的不一样的,所以,最后验证的结果就是不合法的请求。
别人拿到JWT还安全吗
假设载荷部分存储了权限级别相关的字段,强盗拿到 JWT 串后想要修改为更高权限的级别,上面刚说了,这种情况下是肯定不会得逞的,因为加密出来的签名会不一样,服务器可能很容易的判别出来。
那如果强盗拿到后不做更改,直接用呢,那就没有办法了,为了更大程度上防止被强盗盗取,应该使用 HTTPS 协议而不是 HTTP 协议,这样可以有效的防止一些中间劫持攻击行为。
有同学就要说了,这一点也不安全啊,拿到 JWT 串就可以轻松模拟请求了。确实是这样,但是前提是你怎么样能拿到,除了上面说的中间劫持外,还有什么办法吗?
除非强盗直接拿了你的电脑,那这样的话,对不起,不光 JWT 不安全了,其他任何网站,任何认证方式都不安全。
4. 问题
JWT 有个问题,导致很多开发团队放弃使用它,那就是一旦颁发一个 JWT 令牌,服务端就没办法废弃掉它,除非等到它自身过期。有很多应用默认只允许最新登录的一个客户端正常使用,不允许多端登录,JWT 就没办法做到,因为颁发了新令牌,但是老的令牌在过期前仍然可用。这种情况下,就需要服务端增加相应的逻辑。
5. 简单示例
引入maven包
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>登陆时创建令牌
public static String create(){ try { Algorithm algorithm = Algorithm.HMAC256("secret"); String token = JWT.create() .withIssuer("auth0") .withSubject("subject") .withClaim("name","zhangsan") .withClaim("introduce","zhangsan awesome") .sign(algorithm); System.out.println(token); return token; } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. throw exception; } }登录成功后,再次发起请求的时候将 token 放到 header 或者请求体中,服务端对 token 进行验证。
public static Boolean verify(String token){ try { Algorithm algorithm = Algorithm.HMAC256("secret"); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); String payload = jwt.getPayload(); String name = jwt.getClaim("name").asString(); String introduce = jwt.getClaim("introduce").asString(); System.out.println(payload); System.out.println(name); System.out.println(introduce); return true; } catch (JWTVerificationException exception){ //Invalid signature/claims return false; } }用 create 方法生成 token,并用 verify 方法验证一下。
public static void main(String[] args){ String token = create(); Boolean result = verify(token); System.out.println(result); }结果
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaW50cm9kdWNlIjoi6Iux5L-K5r2H5rSSIiwiaXNzIjoiYXV0aDAiLCJuYW1lIjoi5Y-k5pe255qE6aOO562dIn0.ooQ1K_XyljjHf34Nv5iJvg1MQgVe6jlphxv4eeFt8pA eyJzdWIiOiJzdWJqZWN0IiwiaW50cm9kdWNlIjoi6Iux5L-K5r2H5rSSIiwiaXNzIjoiYXV0aDAiLCJuYW1lIjoi5Y-k5pe255qE6aOO562dIn0 zhangsan zhangsan awesome true