目录
1. 什么是JWT
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
JWT.IO allows you to decode, verify and generate JWT.
翻译过来就是:
JSON Web Tokens 是一种开放的、行业标准的 RFC 7519方法,用于在两方之间安全地表示声明。
JWT.IO 允许您解码、验证和生成 JWT。
2.JWT能做什么
1.授权
这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户该访问该令牌允许的路由,服务和资源。单点登录是当令广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
2.信息交换
JSON Web Token 是在各方之间安全地传输信息的好方法。因为可以对jWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。
3.为什么是JWT
1.基于传统的Session认证
1.认证方式
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再进行一次亿用户认证才行,因为按照http协议,我们并不能知道是哪个用户发送的请求,所以为了让我们的应用能识别是哪个用户发出发送的请求,我们只能在服务器存储一分用户登录的信息,这份登录信息会在相应时传递给浏览器,告诉其保存为cookie,以便下次求时给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
2.认证流程
3.暴露问题
- 每个用户经过我们的应用认证后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都保存在内存中,而随着认证用户 的增多,服务端的开销会明显增大
- 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这邰服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
- 因为基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易收到跨站。
- 在前后端分离系统中就更加痛苦: 就是说前后端分离在应用解耦后增加了部署的复杂性,通常用户一次情趣就要转发多次了,如果用session每次携带sessionid到服务,服务器还要查询用户信息,同时如果用户很多,这些信息存储在服务器中,给服务器增加负担。还有就是csrf跨域(跨站伪造请求攻击),session 是基于cookie进行用户识别的,cookie如果被截获,用户就会很容易受到请求伪造的攻击,还有就是session就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署,那么久需要实现session共享机制。
2.基于JWT认证
1. 认证流程
- 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
- 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT playload(负载),将其与头部分别进行Base64 编码拼接后签名,形成一个JWT,形成的JWT就是一个形同lll,zzz,www 的字符串。
- 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回结果保存在localStorage上,退出登录时前端删除保存的JWT即可。
- 前端每次请求时将JWT放入HTTP Header中的Authorization位(解决XSS、XSF问题)
- 后端检查是否存在,如存在验证JWT的有效性。例如:检查签名是否正确;检查token是否过期;检查token的接收方是否是自己(可选)
- 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应的结果。
2.JWT 优势
- 简洁(compact):可以通过url,post参数或者在HTTP header发送,因为数据量小,传输速度也快
- 自包含(self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
- 因为token是以JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上任何web形式都支持
- 不需要在服务端保存会话信息,特别适用于分布式微服务。
4.JWT的结构是什么?
1.令牌的组成
- 标头(Header)
- 有效载荷(Payload)
- 签名(Signature)
- 因此,JWT通常如下所示:xxxx.yyyy.zzzzz Header.payloader.Signature
2.header
- 标头通常由两部分组成:令牌的类型(即JWT)和使用的签名算法,例如HMAC、SHA256、RSA。它会使用Base64 编码组成 JWT 结构的第一部分
- 注意:Base64 是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
{
"alg": "HS256",
"typ": "JWT"
}
3.Payload
- 令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base编码组成JWt结构的第二部分。
{
"sub":"123456",
"name": "Hone Doe",
"admin": true
}
4.Signature
- 前面两部分都是使用 Base64 进行编码。即前端可以解开知道里面的信息。Signature需要使用编码后的header和payload以及我们提供的一个秘钥,然后使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过
5.签名的目的
- 最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合成新的JWT的话,那么服务器端就会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的秘钥的话,得出来的签名也是不一样的。
6.信息安全问题
- 在这里大家一定会问一个问题,Base64是一种编码,是可逆的,那么我的信息不就暴露了么?
- 是的,所以,在JWT中,不应该在负载里面加入任何敏感的数据。像密码这样的内容就不能被放在JWT中了,如果将用户的密码放在JWT中,那么怀有恶意的第三方通过base64解码就能很快地知道你的密码了。因此JWT适合用于向web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现web应用的单点登录。
放在一起
- 输出是三个由点分隔的Base64-URL 字符串,可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑。
- 简洁(Compact) 可以通过URL,POST 参数或者 HTTP header 发送,因为数据量小,传输速度快
- 自包含(Self-contained)负载中包含了所有用户所需要的信息,避免了多次查询数据库。
5.使用JWT
1.引入依赖
<!-- 引入依赖 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version
<dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.3</version>
</dependency>
2.生成Token
static void contextLoader () {
HashMap<String, Object> map = new HashMap<>();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 20);
String sign = JWT.create()
.withHeader(map)
.withClaim("id", 2)
.withClaim("username", "zhangsan")
// 指定临牌的过期时间
.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC256("@lipw"));
System.out.println(sign);
// 创建验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("@lipw")).build();
DecodedJWT verify = jwtVerifier.verify(sign);
System.out.println("===" + verify);
System.out.println("===" + verify.getClaim("id").asInt());
System.out.println("===" + verify.getClaim("username").asString());
System.out.println("===" + verify.getExpiresAt());
}
先验证加密方法,后验证过期时间。
6.封装工具类
public class JwtUtils {
/**
* 加解密秘钥.
*/
private static final String SIGN = "@vue_shop";
/**
* 过期时间.
*/
private static final int SECONDS = 1800;
/**
* 生成Token.
* @param header 生成 token 的 头部信息
* @param map token中需要加入的信息
* @return token
*/
public static String getToken(Map<String, Object> header,
Map<String, Object> map) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, SECONDS);
// 创建 jwt builder
JWTCreator.Builder builder = JWT.create();
map.forEach((k, v) -> builder.withClaim(k, Collections.singletonList(v)));
return builder
// 指定头
.withHeader(header)
// 指定令牌过期时间
.withExpiresAt(calendar.getTime())
// 指定签名
.sign(Algorithm.HMAC256(SIGN));
}
/**
* 验证 token 是否合法.
* @param token 需要验证的token
* @return DecodedJWT token 信息
*/
public static DecodedJWT verify(String token) {
return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
}