引入maven依赖
org.projectlombok
lombok
io.jsonwebtoken
jjwt
0.9.0
compile
JwtTokenFilter.java
实现拦截器、判断url地址信息以及权限信息
/**
* @Author Lxq
* @Date 2020/4/28 16:58
* @Version 1.0
* 描述: JwtToken 过滤器
*/
@Component
//读取 yml 文件下的 com.gpdi.jwt
@ConfigurationProperties("com.gpdi.jwt")
@Setter
@Getter
@Slf4j
public class JwtTokenFilter implements GlobalFilter, Ordered {
private String[] skipAuthUrls;
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
//跳过不需要验证的路径
if (null != skipAuthUrls && Arrays.asList(skipAuthUrls).contains(url)) {
return chain.filter(exchange);
}
//获取token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
ServerHttpResponse resp = exchange.getResponse();
if (StringUtils.isBlank(token)) {
// 没有token
return authErro(resp, "请登陆!");
} else {
//有token
try {
JwtUtil.checkToken(token);
return chain.filter(exchange);
} catch (ExpiredJwtException e) {
log.error(e.getMessage(), e);
if (e.getMessage().contains("Allowed clock skew")) {
return authErro(resp, "认证过期");
} else {
return authErro(resp, "认证失败");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
return authErro(resp, "认证失败");
}
}
}
@Override
public int getOrder() {
return -100;
}
/**
* 认证错误返回的消息
*
* @param resp
* @param mess
* @return
*/
private Mono authErro(ServerHttpResponse resp, String mess) {
resp.setStatusCode(HttpStatus.UNAUTHORIZED);
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
ReturnData returnData = new ReturnData<>(cn.hutool.http.HttpStatus.HTTP_UNAUTHORIZED, mess, mess);
String returnStr = "";
if (returnData != null){
returnStr = JSONObject.toJSONString(returnData);
}
DataBuffer buffer = resp.bufferFactory().wrap(returnStr.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Flux.just(buffer));
}
}
JwtUtil.java
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @description:
* @author: Lxq
* @date: 2020/5/20 8:34
* 描述: jwt 工具类
*/
@Slf4j
public class JwtUtil {
/**
* 密钥
*/
private static final String KEY = "022bdc63c3c5a45879ee6581508b9d03adfec4a4658c0ab3d722e50c91a351c";
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decodeBase64(KEY);
SecretKeySpec key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* @param id 是JWT的唯一标识
* @param object jwt签发对象
* @param subject 代表这个JWT的主体
* @param ttlMillis 设置过期时间
* @return
* @throws Exception
*/
public static String createJWT(Integer id, Object object, String subject, long ttlMillis) {
// 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
// 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
Map claims = new HashMap<>();
JSONObject jsonObject = (JSONObject) object;
claims.put("user", jsonObject);
// 生成签名的时候使用的秘钥secret,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。
// 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
SecretKey key = generalKey();
// 下面就是在为payload添加各种标准声明和私有声明了
// 这里其实就是new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
.setId(String.valueOf(id))
// iat: jwt的签发时间
.setIssuedAt(now)
// userName:jwt签发人
.setIssuer(jsonObject.getString("username"))
// sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志
.setSubject(subject)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, key);
// 设置过期时间
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
return builder.compact();
}
/**
* 解密jwt
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) {
//签名秘钥,和生成的签名的秘钥一模一样
SecretKey key = generalKey();
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(key)
//设置需要解析的jwt
.parseClaimsJws(jwt).getBody();
return claims;
}
/**
* 检查token
*
* @return
*/
public static boolean checkToken(String jwtToken) {
Claims claims = JwtUtil.parseJWT(jwtToken);
// 角色id|权限id|权限列表
String subject = claims.getSubject();
Object pSubject = JSONObject.parse(subject);
Object obj = claims.get("user");
/*
获取token的过期时间,和当前时间作比较,如果小于当前时间,则token过期
*/
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date expiration = claims.getExpiration();
log.info("======== token的过期时间:" + df.format(expiration));
return true;
}
}
UserDTO.java
import lombok.*;
/**
* @Author Lxq
* @Date 2020/4/28 16:54
* @Version 1.0
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO {
private String userName;
private String password;
}
ReturnData.java
/**
* @Author Lxq
* @Date 2020/4/28 16:53
* @Version 1.0
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ReturnData {
private int status;
private String mass;
private T data;
public ReturnData(int status, String mass) {
this.status = status;
this.mass = mass;
}
}
application.yml
com:
gpdi:
jwt:
#跳过认证的路由
skip-auth-urls:
- /baidu
- /test
# 单位小时
effective-time: 2
JsonSentinelGatewayBlockExceptionHandler.java 自定义限流熔断的处理器
import cn.hutool.http.HttpStatus;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.function.Supplier;
import com.alibaba.fastjson.JSONObject;
import com.gpdi.wireless.dto.ReturnData;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @Author Lxq
* @Date 2020/4/29 12:55
* @Version 1.0
* 自定义限流hanlder
*/
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
private List viewResolvers;
private List> messageWriters;
private final Supplier contextSupplier = () -> {
return new ServerResponse.Context() {
@Override
public List> messageWriters() {
return JsonSentinelGatewayBlockExceptionHandler.this.messageWriters;
}
@Override
public List viewResolvers() {
return JsonSentinelGatewayBlockExceptionHandler.this.viewResolvers;
}
};
};
public JsonSentinelGatewayBlockExceptionHandler(List viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers;
this.messageWriters = serverCodecConfigurer.getWriters();
}
@Override
public Mono handle(ServerWebExchange exchange, Throwable ex) {
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
} else {
return !BlockException.isBlockException(ex) ? Mono.error(ex) : this.handleBlockedRequest(exchange, ex).flatMap((response) -> {
return this.writeResponse(response, exchange);
});
}
}
private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
/**
* 重写返回数据
*
* @param response
* @param exchange
* @return
*/
private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
ReturnData stringReturnData = new ReturnData<>(HttpStatus.HTTP_FORBIDDEN, "当前访问人数过多,请稍后再试", "当前访问人数过多,请稍后再试");
byte[] datas = JSONObject.toJSONString(stringReturnData).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
}
GatewayConfiguration.java 将自定义的限流熔断处理器加入到配置中
package com.gpdi.wireless.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @Author Lxq
* @Date 2020/4/29 12:40
* @Version 1.0
*/
@Configuration
public class GatewayConfiguration {
private final List viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 配置SentinelGatewayBlockExceptionHandler,限流后异常处理
*
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// 默认的限流的handler
// return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
// 自定义的限流handler
return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 配置SentinelGatewayFilter
*
* @return
*/
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
// 提前加载限流规则
// @PostConstruct
// public void doInit() {
// initGatewayRules();
// }
/**
* 配置限流规则
*/
private void initGatewayRules() {
Set rules = new HashSet<>();
rules.add(new GatewayFlowRule("payment_route")
.setCount(1) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
// 添加分组 限流
// rules.add(new GatewayFlowRule("customized_api"));
GatewayRuleManager.loadRules(rules);
}
/**
* 自定义分组限流
*/
private void initCustomizedApis() {
Set definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("customized_api")
.setPredicateItems(new HashSet() {{
// test完全匹配
add(new ApiPathPredicateItem().setPattern("/test"));
// payment/开头的
add(new ApiPathPredicateItem().setPattern("/payment/**")
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api1);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}