文章目录
- 一、Cookie-Session认证
- 校验流程
- 缺点
- 二、Token
- 校验流程
- 缺点
- 三、JWT
- 校验流程
- 四、JWT令牌的实践使用
- JWT组成
- 引入依赖
- 生成令牌
前言
在讲解这个问题之前,我们要先搞清楚基本的用户登录流流程。
- 用户在web登录页面填写登录信息
- 前端发送登录信息到后端
- 后端接收登录信息
- 后端进行校验
- 校验成功,返回成功结果
在本文中会讲解三种常见的登录校验流程
1.Cookie-Session认证
2.
一、Cookie-Session认证
校验流程
- 用户在web端输入登录信息
- 后端拿到信息后进行校验
- 后端校验成功后生成SessionId(这个SessionId关联着用户信息)返回给web
- 前端拿到SessionId后就会保存到Cookie中
- 在后续的操作中,每次用户访问就会带着着这个Cookie
- 后端在后续接收到前端的请求时,就会解析这个SessinId,查询到SessionId关联到的用户信息
- 解析成功后,就会根据用户的请求返回响应
流程图如下
缺点
- Cookie-Session 这种形式只存在于web端
我们目前更多的使用是在手机端,并不是通过web网页的形式。 - Cookie不能跨域
- 不适用于集群环境
二、Token
校验流程
- 用户在web端输入登录信息
- 后端拿到信息后进行校验
- 后端校验成功后生成Toekn(这个Toekn可能是根据用户id生成的,Token关联着用户表)返回给web
- 前端拿到后会将Token存储下来
- 在后续的操作中,每次用户访问就会带着着这个Token
- 后端在后续接收到前端的请求时,就会解析这个Token,查询到关联到的用户信息
- 解析成功后,就会根据用户的请求返回响应
流程图如下
缺点
- 数据量大,对于内存来说,压力巨大、
后端在解决集群环境的问题时,将Toekn存储到Redis中,一旦数据量起来,就会对内存造成巨大压力
三、JWT
校验流程
- 用户在web端输入登录信息
- 后端拿到信息后进行校验
- 后端校验成功后生成JWT令牌,返回给web
- 前端拿到后会将JWT令牌存储下来
- 在后续的操作中,每次用户访问就会带着着这个JWT令牌
- 后端在后续接收到前端的请求时,就会解析这个JWT令牌,查询到关联到的用户信息
- 解析成功后,就会根据用户的请求返回响应
流程图如下
四、JWT令牌的实践使用
JWT组成
JWT由三部分组成, 每部分中间使⽤点 (.) 分隔,⽐如:aaaaa.bbbbb.cccc
- Header(头部) 头部包括令牌的类型(即JWT)及使用的哈希算法(如SHA256)
- Payload(负载) 负载部分是存放有效信息的地⽅, ⾥⾯是⼀些⾃定义内容
- Signature(签名) 此部分⽤于防⽌jwt内容被篡改,确保安全性
JWT之所以安全,就是因为这个签名,JWT当中任何一个字符被篡改,整个令牌都会失效
引入依赖
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
生成令牌
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParserBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JWTUtil {
private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);
/**
* 密钥:Base64编码的密钥
*/
private static final String SECRET = "SDKltwTl3SiWX62dQiSHblEB6O03FG9/vEaivFu6c6g=";
/**
* 生成安全密钥:将一个Base64编码的密钥解码并创建一个HMAC SHA密钥。
*/
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(
Decoders.BASE64.decode(SECRET));
/**
* 过期时间(单位: 毫秒)
*/
private static final long EXPIRATION = 60*60*1000;
/**
* 生成密钥
*
* @param claim {"id": 12, "name":"张山"}
* @return
*/
public static String genJwt(Map<String, Object> claim){
//签名算法
String jwt = Jwts.builder()
.setClaims(claim) // 自定义内容(载荷)
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) // 设置过期时间
.signWith(SECRET_KEY) // 签名算法
.compact();
return jwt;
}
/**
* 验证密钥
*/
public static Claims parseJWT(String jwt){
if (!StringUtils.hasLength(jwt)){
return null;
}
// 创建解析器, 设置签名密钥
JwtParserBuilder jwtParserBuilder = Jwts.parserBuilder().setSigningKey(SECRET_KEY);
Claims claims = null;
try {
//解析token
claims = jwtParserBuilder.build().parseClaimsJws(jwt).getBody();
}catch (Exception e){
// 签名验证失败
logger.error("解析令牌错误,jwt:{}", jwt, e);
}
return claims;
}
/**
* 从token中获取用户ID
*/
public static Integer getUserIdFromToken(String jwtToken) {
Claims claims = JWTUtil.parseJWT(jwtToken);
if (claims != null) {
Map<String, Object> userInfo = new HashMap<>(claims);
return (Integer) userInfo.get("userId");
}
return null;
}
}