Token
概述
Token 是服务端生成的一串字符串,以作为客户端进行请求的一个令牌,当第一次登录后,服务器生成一个 Token 便将此 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次带上用户名和密码。
优势
-
无状态、可扩展
- 在客户端存储的
Tokens
是无状态的,并且能够被扩展。基于这种无状态和不存储Session
信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。tokens
自己hold
住了用户的验证信息。
- 在客户端存储的
-
Tokens
能够创建与其它程序共享权限的程序。 -
跨域
-
安全
- 请求中发送
token
而不再是发送cookie
能够防止CSRF
(跨站请求伪造)。即使在客户端使用cookie
存储token
,cookie
也仅仅是一个存储机制而不是用于认证。不将信息存储在Session
中,让我们少了对session
操作。
- 请求中发送
-
Token 设置过期时间
JWT
概述
JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作为 JSON 对象传输。 由于此信息是经过数字签名的,因此可以被验证和信任。 可以使用秘钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公用/专用密钥对对 JWT 进行签名。
在前后端或者服务器进行交互的过程中,通过 JSON 形式作为 Web 应用中的令牌,以完成数据传输、加密、签名等相关处理操作。
JWT 认证流程
- 首先,前端通过 Web 表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个 HTTP POST 请求。建议的方式是通过 SSL 加密的传输(https协议),从而避免敏感信息被嗅探。
- 后端核对用户名和密码成功后,将用户的 id 等其他信息作为 JWT Payload (载荷),将其与头部分别进行 Base64 编码拼接后再生成一个签名,形成一个 JWT(Token)。形成的 JWT 就是一个形同 111.ZzZz.xxx 的字符串。
- 后端将 JWT 字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在 localStorage 或 sessionStorage 上,退出登录时前端删除保存的 JWT 即可。
- 前端在每次请求时将 JWT 放入 HTTP Header 中的 Authorization 位。(解决XSS和XSRF问题)
- 后端检查是否存在,如存在验证 JWT 的有效性。例如,检查签名是否正确;检查 Token 是否过期;检查 Token 的接收方是否是自己(可选)。
- 验证通过后后端使用 JWT 中包含的用户信息进行其他逻辑操作,返回相应结果。
JWT 的优势
- 简洁(Compact):可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
- 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
- 因为Token是以 JSON 加密的形式保存在客户端的,所以 JWT 是跨语言的,原则上任何web形式都支持。
- 不需要在服务端保存会话信息,特别适用于分布式微服务。
JWT 的组成【重点】
JWT 有3个组成部分,每个部分之间用 . 进行分隔
- Header
- Payload
- Signature
格式为
xxxxx.yyyyy.zzzzz
1、头部(header) 声明类型以及加密算法,例如 HMAC、SHA256或 RSA。
{"alg":"HS256","typ":"JWT"}
2、载荷(payload) 携带一些用户身份信息,用户id,颁发机构,颁发时间,过期时间等。用Base64进行了处理。这一段其实是明文,所以一定不要放敏感信息。例如
{"id": 200, "username": "易烊千玺"}
3、签证(signature) 签名信息,使用了自定义的盐然后加密后的结果,目的就是为了保证签名的信息没有被别人改过,这个一般是让服务器验证的。
JWT 入门
1、pom.xml文件导入对应的依赖
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.15.0</version>
</dependency>
2、测试代码
public class JwtTest {
// 通过JWT获取token
@Test
public void createToken() {
HashMap<String, Object> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
Calendar expireTime = Calendar.getInstance();
expireTime.add(Calendar.MINUTE, 30);
// 创建一个Token
String token = JWT.create().
// 头部
withHeader(header).
// 载荷
withClaim("username", "易烊千玺").
withClaim("id", 1).
withExpiresAt(expireTime.getTime()).
// 签名(盐)
sign(Algorithm.HMAC256("ABC8D!EFG"));
System.out.println(token);
}
// 验证JWT的token
@Test
public void verifyToken() {
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("ABC8D!EFG")).build();
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjE4MTM4NzY0LCJ1c2VybmFtZSI6IuaYk-eDiuWNg-eOuiJ9.IhMvu1CPUVd94rPTu6HNQTGP_fBTOaJaAofD7WTDFwo");
System.out.println("token头:" + verify.getHeader());
System.out.println("token载荷:" + verify.getPayload());
System.out.println("token签名:" + verify.getSignature());
System.out.println("id:" + verify.getClaim("id").asInt());
System.out.println("username:" + verify.getClaim("username").asString());
System.out.println("过期时间:" + verify.getExpiresAt());
}
}
JWT 工具类
/**
* JWT工具类
*/
public class JwtUtils {
// token头算法
private static final String ALG = "HS256";
// token头类型
private static final String TYP = "JWT";
// token签名算法
private static final Algorithm ALGORITHM;
// token头
private static final HashMap<String, Object> HEADER = new HashMap<>();
// token签名
private static final String SIGN = "QwErTyUi";
// 静态代码块加载token头以及token签名算法
static {
HEADER.put("alg", ALG);
HEADER.put("typ", TYP);
ALGORITHM = Algorithm.HMAC256(SIGN);
}
/**
* 获取Token
*
* @param claim 载荷
* @param expireTime 过期时间,单位:分钟
* @return token
*/
public static String getToken(Map<String, Object> claim, Integer expireTime) {
// 返回创建的token
return JWT.create().
// 设置头