token验证-jwt方法
一、登录token验证方式:
session方式:
用户进行用户登录,服务端会验证用户名和密码,成功后会生成一个session,它存储在服务端,并且要有与之对应的Cookie中的sessionId,
不适合集群搭建。
JWT方式:
day02-09-网关鉴权流程分析(视频)
-
自媒体和管理端使用用户名和密码登录,服务端收到请求,去验证用户名与密码是否一致
-
如果一致,通过jwt生成token(令牌),token里面包含有用户的信息
-
登陆完成后服务端把token再返回给前端,前端拿到后缓存到浏览器里面,以后前端访问其他请求时都会携带token
-
在网关中统一校验token是否存在且合法,如果合法解析出userId放到请求头中传递到微服务,
-
在微服务中 我们采用拦截器获取userId放到ThreadLocal中,这样在Controller和service层都可以获取userId
注意细节:
2、jwt 包含三部分内容,头、载荷、签名,其中头和载荷用的是base64位加密算法,签名用的是你选择的比如HS256 RS256
3、不用ThreadLocal可以吗,可以,但是这样就需要在每个Controller的方法中使用request.getHeader方法获取userId了
4、token生成的时候是否有过期时间?有,两个小时 ?
5、token怎么续签(续期),前端每隔一段时间发起重新获取token的请求
6、退出登录,token怎么处理?放到了Redis中,标记为黑名单,就是标记成这个token不能用了
二、拦截器应用
JWT工具类:
import io.jsonwebtoken.*;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
public class AppJwtUtil {
// TOKEN的有效期一天(MS)
private static final int TOKEN_TIME_OUT = 3600 * 1000 * 24;
// 加密KEY
private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
// 最小刷新间隔(S)
private static final int REFRESH_TIME = 300;
/**
* 生成token
*
* @param claimMaps 自定义信息
* @return
*/
public static String getToken(Map<String, Object> claimMaps) {
long currentTime = System.currentTimeMillis();
return Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date(currentTime)) //签发时间
.setSubject("system") //说明
.setIssuer("heima") //签发者信息
.setAudience("app") //接收用户
.compressWith(CompressionCodecs.GZIP) //数据压缩方式
.signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
.setExpiration(new Date(currentTime + TOKEN_TIME_OUT)) //过期时间戳
.addClaims(claimMaps) //cla信息
.compact();
}
/**
* 获取token中的claims信息
*
* @param token
* @return
*/
private static Jws<Claims> getJws(String token) {
return Jwts.parser()
.setSigningKey(generalKey())
.parseClaimsJws(token);
}
/**
* 获取payload body信息
*
* @param token
* @return
*/
public static Claims getClaimsBody(String token) {
try {
return getJws(token).getBody();
} catch (ExpiredJwtException e) {
return null;
}
}
/**
* 获取hearder body信息
*
* @param token
* @return
*/
public static JwsHeader getHeaderBody(String token) {
return getJws(token).getHeader();
}
/**
* 是否过期
*
* @param claims
* @return -1:有效,0:有效,1:过期,2:过期
*/
public static int verifyToken(Claims claims) {
if (claims == null) {
return 1;
}
try {
claims.getExpiration()
.before(new Date());
if ((claims.getExpiration().getTime() - System.currentTimeMillis()) > REFRESH_TIME * 1000) {
return -1;
} else {
return 0;
}
} catch (ExpiredJwtException ex) {
return 1;
} catch (Exception e) {
return 2;
}
}
/**
* 由字符串生成加密key
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static void main(String[] args) {
Map map = new HashMap();
map.put("userId", "10001");
map.put("userName", "tom");
String token = AppJwtUtil.getToken(map);
System.out.println(token);
Jws<Claims> jws = AppJwtUtil.getJws(token);
Claims claims = jws.getBody();
System.out.println(claims.get("userId"));
System.out.println(claims.get("userName"));
}
}
拦截器配置:
package com.gateway.filter;
import com.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
ServerHttpRequest request = exchange.getRequest();
// 判断是否为登录操作 如果是登录操作直接放行
String path = request.getURI().getPath(); // api/v1/channel/list /login/in
if(path.contains("/login")){
return chain.filter(exchange); //直接放行
}
String token = request.getHeaders().getFirst("token");
// 判断请求头中是否携带token 如果没有直接返回401
if(StringUtils.isBlank(token)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 如果携带token判断token是否有效
Claims claims = AppJwtUtil.getClaimsBody(token);
if(claims!=null){
// 如果有效从token中解析出userId和name放到请求头中继续进入到后面的微服务中
int type = AppJwtUtil.verifyToken(claims);
// -1:有效,0:有效
if(type==-1||type==0){
Integer userId = claims.get("userId", Integer.class);
String name = claims.get("name", String.class);
request.mutate().header("userId",userId.toString());
request.mutate().header("name",name);
return chain.filter(exchange); //放行
}
}
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
@Override
public int getOrder() {
return 0;
}
}
测试效果:
启动admin服务,继续访问其他微服务,会提示需要认证才能访问,这个时候需要在heads中设置设置token才能正常访问。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界