JWT
是一种通用的无状态的用于后端鉴权的字符串,在 spring cloud
中一般使用 jjwt
包,结合 spring cloud security
进行颁发与鉴权,但由于 spring cloud security
本身过重,如果仅仅只需将 token
用于鉴定请求是否合法,则并不需要使用 spring cloud security
以达到这一目的,只需使用 spring cloud gateway
, 在 spring cloud gateway
中实现 GlobalFilter
接口,在这里面进行鉴定即可。
接下来看具体实现过程:
1. 首先,编写颁发 token
与解析 token
类
public class JWTUtil {
// token 过期时间
private static long expireTime = 12L * 60L * 60L * 1000;
// 生成以及解析 token 的密钥
private static String key = "123456789abcedfghijklmn";
private static ObjectMapper objectMapper;
public JWTUtil(ObjectMapper objectMapper){
this.objectMapper = objectMapper;
}
// 创建 token 的静态方法
public static String createToken(String subject){
Map<String, Object> claims = new HashMap<>();
// 创建私有声明
claims.put("uid", "springGql");
claims.put("user_name", "initialing");
String token = Jwts.builder()
// 设置私有声明
.setClaims(claims)
// 设置 token 私有标识,可以是任意字符串
.setId("id")
// 设置 token 载荷,可以存入用户名等信息
.setSubject(subject)
// 设置颁发 token 时间, 这里设当前时间
.setIssuedAt(new Date())
// 设置过期时间
.setExpiration(new Date(System.currentTimeMillis() + expireTime))
// 设置加密算法和密钥
.signWith(SignatureAlgorithm.HS512, key)
.compact();
return token;
}
// 解析 token 成 Claim 方便后续操作
public static Claims parseJwt(String token) throws Exception {
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
return claims;
}
// 获取 token body 内容
public static JWTModel getModel(String token){
try {
Claims claims = parseJwt(token);
String subject = claims.getSubject();
JWTModel jwtModel = objectMapper.readValue(subject, JWTModel.class);
return jwtModel;
} catch (Exception e){
return null;
}
}
}
上面代码中的 JWTModel
类是信息载荷类,可为以下样式:
public class JwtModel {
public JwtModel(Integer id, String userName){
this.userName = userName;
this.id = id;
}
private Integer id;
private String userName;
public Integer getId(){
return this.id;
}
public void setId(Integer id){
this.id = id;
}
public String getUserName(){
return this.userName;
}
public void setUserName(String userName){
this.userName = userName;
}
}
2. 在 login
调用方法内查询用户,颁发 token
,伪代码如下所示:
@RestController
@RefreshScope
public class TestController{
private ObjectMapper objectMapper;
public TestController(ObjectMapper objectMapper){
this.objectMapper = objectMapper;
}
@GetMapping("/login")
public CommonResult login(@RequestParam(value = "userName") String userName,
@RequestParam(value = "password") String password,
HttpServletResponse response){
// 获取并对比用户信息的代码
// 生成 JWTModel
JWTModel jwtModel = new JWTModel(id, username);
// 颁发 token
String token = JWTUtil.createToken(objectMapper.writeValueAsString(jwtModel));
// 返回头添加 Access-Token 属性,存入 token 值
response.addHeader("Access-Token", token);
return new CommonResult(200, "success");
}
}
上述代码中 CommonResult
为 RESTFull
通用返回格式,可定义为
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message, T data){
this.code = code;
this.message = message;
this.data = data;
}
public CommonResult(Integer code, String message){
this(code,message,null);
}
}
*注:前端可对每次调用的返回头信息进行查询,如果有 Access-Token
信息,变将其存至本地,这里使用 axios
举例:
/**
* 创建axios实例
*/
const instance = axios.create({
timeout: 60000,
responseType: "json",
})
/**
* 统一返回拦截处理
*/
instance.interceptors.response.use(function(response){
// 如果返回存在access-token,存入localStorage中
if(response.headers["access-token"]){
localStorage.setItem("token",response.headers["access-token"]);
}
return response;
},function(error){
return Promise.resolve(error);
}
)
/**
* 统一请求拦截处理
*/
instance.interceptors.request.use(function(config){
// 如果在本地存在 token 便传给后端
let token = storage.getItem("token");
if(token){
config.headers["Authorization"] = token;
}
return config;
},function(error){
return Promise.reject(error);
}
)
3. 最后一步就是使用 spring cloud gateway
来做后台统一判断请求是否合法,具体代码如下:
@Component
@Slf4j
public class TokenFilter implements GlobalFilter, Ordered {
// 跳过鉴定的url
private String[] skipAuthUrl = {
"/login"
};
private ObjectMapper objectMapper;
public TokenFilter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求地址
String url = exchange.getRequest().getURI().getPath();
// 如果在跳过列表里,则不进行鉴定
if(skipAuthUrl != null && (Arrays.asList(skipAuthUrl).contains(url))){
return chain.filter(exchange);
}
// 获取token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
ServerHttpResponse response = exchange.getResponse();
try {
JwtUtil.parseJwt(token);
return chain.filter(exchange);
} catch (ExpiredJwtException e){
if(e.getMessage().contains("Allowed clock skew")){
Date et = e.getClaims().getExpiration();
long exMillis = et.getTime();
long nowMillis = System.currentTimeMillis();
// 过期时间在2天内自动刷新token
if(nowMillis - exMillis < 1000L * 60L * 60L * 24L * 2L){
String subject = e.getClaims().getSubject();
try {
// 设置刷新时间为8小时
String newJwt = JwtUtil.createJwt(subject);
// 返回头设置新 token
response.getHeaders().add("Access-Token",newJwt);
ServerHttpRequest req = exchange.getRequest().mutate().header("Authorization", newJwt).build();
exchange = exchange.mutate().request(req).build();
} catch (Exception ne){
log.error("******** 刷新token失败"+ne.getMessage(),ne);
}
return chain.filter(exchange);
}
log.error("******** 过期"+e.getMessage(),e);
return reqReject(response,"认证过期");
}else{
return reqReject(response,"认证失败");
}
} catch (Exception e) {
log.error(e.getMessage(),e);
return reqReject(response,"认证失败");
}
}
private Mono<Void> reqReject(ServerHttpResponse response, String message){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
CommonResult result = new CommonResult(403,message);
String returnStr = "";
try{
returnStr = objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e){
log.error(e.getMessage(),e);
}
DataBuffer buf = response.bufferFactory().wrap(returnStr.getBytes(StandardCharsets.UTF_8));
Mono<Void> vm = response.writeWith(Flux.just(buf));
return vm;
}
@Override
public int getOrder(){
return -1;
}
}
以上就是利用 jjwt
包进行 token
颁发与解析的具体过程,该过程可轻量化的产出 token
返回给前端,并通过实现 GlobalFilter
方法,在 spring cloud gateway
中进行 token
守卫,拦截 token
过期或无 token
的请求,并且在过期一定时间内刷新 token
。
作者:勾勒两只企鹅
链接:基于 spring cloud 的简易 JWT 颁发与刷新构建方法 - 掘金