前言
最近使用到了基于Token的身份验证,跟大家分享下。很多大型网站也都在用,比如淘宝、京东、谷歌、百度等待,比起传统的身份验证方法,Token扩展性更强,也更安全,非常适合用在Web应用或者移动应用上。
身份验证:当用户尝试登录,将请求提交到服务器端,如果服务器端认证通过,会生成一个Token数据响应到客户端,此Token是有意义的数据,此客户端在后续的每一次请求中,都应该携带此Token数据,服务器端通过解析此Token来识别用户身份!
问题:Session和Token都是身份验证,这次为什么使用Token?Token的优势在哪?
Session默认是保存在服务器的内存中的数据,会占用一定的服务器内存资源,并且,不适合集群或分布式系统(虽然可以通过共享Session来解决),客户携带的Session ID只具有唯一性的特点(理论上),不具备数据含义。
Token的本质是将有意义的数据进行加密处理后的结果,各服务器都只需要具有解析这个加密数据的功能即可获取到其中的信息含义,理论上不占用内存资源,更适合用于集群和分布式系统,但是,存在一定的被解密的风险(概率极低)。
正文
一、JWT基本原理
JWT是JSON Web Token,是使用JSON格式表示多项数据的Token。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证JWTToken的正确性,只要正确即通过验证。
JWT包含三个部分:Header头部,Playload负载和Signature签名。由三部分生成JwtToken,三部分之间用“.”号做分割。校验也是JWT内部自己实现的,并且可以将你存储时候的信息从JwtToken中取出来无须查库。
1.Head头部:ALGORITHM&TOKEN TYPE(算法与Token类型)
{
"alg": "HS256",
"typ": "JWT"
}
2.PAYLOAD(载荷):DATA
此部分的数据是自定义,可按需存入任何所需的数据。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
3.VERIFY SIGNATURE(验证签名)
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
二、使用jjwt生成和解析JWT数据的实例
1.在使用JWT之前,需要在项目中添加相关的依赖,用于生成JWT和解析JWT,例如添加:
<!-- JJWT(Java JWT) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
提示:更多依赖项可参考 https://jwt.io/libraries?language=Java
2.jjwt生成测试代码:
@Test
public void testGenerate() {
// 准备Claims值
Map<String, Object> claims = new HashMap<>();
claims.put("id", 9527);
claims.put("name", "LiuLaoShi");
claims.put("nickname", "JavaCangLaoShi");
// JWT的过期时间
Date expiration = new Date(System.currentTimeMillis() + 5 * 60 * 1000);
System.out.println("过期时间:" + expiration);
// JWT的组成:Header(头:算法和Token类型)、Payload(载荷)、Signature(签名)
String jwt = Jwts.builder()
// Header
.setHeaderParam("alg", "HS256")
.setHeaderParam("typ", "JWT")
// Payload
.setClaims(claims)
.setExpiration(expiration)
// Signature
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
System.out.println("JWT=" + jwt);
}
3.解析JWT数据测试代码:
@Test
public void testParse() {
// 注意:必须使用相同secretKey生成的JWT,否则会解析失败
// 注意:不可以使用过期的JWT,否则会解析失败
// 注意:复制粘贴此JWT时,不要带“尾巴”,否则会解析失败
// 注意:不可以恶意修改JWT中的任何字符,否则会解析失败
String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiTGl1TGFvU2hpIiwibmlja25hbWUiOiJKYXZhQ2FuZ0xhb1NoaSIsImlkIjo5NTI3LCJleHAiOjE2NjI0NTY3ODN9.32MwkSbDz1ce4EvEKHFMCIjcQFUDZz6hn5MtAYr0njQ";
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
Integer id = claims.get("id", Integer.class);
String name = claims.get("name", String.class);
String nickname = claims.get("nickname", String.class);
System.out.println("id = " + id);
System.out.println("name = " + name);
System.out.println("nickname = " + nickname);
}
三、总结
- 客户端使用用户名和密码请求登录
- 服务器收到请求,去验证用户名和密码
- 验证成功,服务器会签发一个JwtToken,无须存储到服务器,直接再把这个JwtToken发送给客户端
- 客户端收到JwtToken以后可以把它存储起来,比如放到Cookie里或者Local Storage里
- 客户端每次向服务器请求资源的时候需要带着服务端签发的JwtToken
- 服务端收到请求,验证客户端请求里面带着的JwtToken,如果验证成功,就行客户端返回请求的数据
JwtToken发送给客户端
4. 客户端收到JwtToken以后可以把它存储起来,比如放到Cookie里或者Local Storage里
5. 客户端每次向服务器请求资源的时候需要带着服务端签发的JwtToken
6. 服务端收到请求,验证客户端请求里面带着的JwtToken,如果验证成功,就行客户端返回请求的数据