一.全局Token过滤器
在Spring Cloud Gateway中,实现全局过滤器的目的是对所有进入系统的请求或响应进行统一处理,比如添加日志、鉴权等。下面是如何创建一个全局过滤器的基本步骤:
步骤1: 创建过滤器类
首先,你需要创建一个实现了GlobalFilter接口,创建一个全局token过滤器。
@Slf4j
@Component
//@Order(2)
public class TokenFilter implements GlobalFilter, Ordered {
@Value("${cn.smart.tokenx.key}")
private String tokenKey;
private static List<String> whiteList = CollUtil.newArrayList("/api/login", "/api/register");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
String path = uri.getPath().toLowerCase().trim();
//排除白名单的验证
if(whiteList.contains(path)){
return chain.filter(exchange);
}
ServerHttpResponse response = exchange.getResponse();
List<String> tokens = request.getHeaders().get("token");
//验证的是有没有传token
if(ObjectUtil.isEmpty(tokens)){
log.error("请传token");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
String token = tokens.get(0);
//验证的是传的token值是否为空
if(ObjectUtil.isEmpty(token)){
log.error("token 不能为空");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
boolean b = false;
// 验证算法,JWTValidator包含过期的验证,验证比较全面
try {
JWTValidator.of(token).validateAlgorithm(JWTSignerUtil.hs256(tokenKey.getBytes())).validateDate();
b = true;
}catch (Exception ex){
ex.printStackTrace();
log.error("token不正确");
}
if(!b){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//接着执行下面的过滤器,token还会带到下游,我们的服务还是要用我们之前的token-starter,token-starter功能只保留解析token写入Threadlocal,验证的功能不再需要
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 2;
}
}
步骤2: 注册全局过滤器
为了让Spring Cloud Gateway应用识别并使用这个过滤器,你通常需要在配置类中注册它。但是,由于我们使用了@Component
注解,Spring会自动扫描并注册该Bean。
使用@Component
或下面代码,二选一。
@Configuration
public class FilterConfig {
@Bean
public TokenFilter tokenFilter() {
return new TokenFilter();
}
}
二.局部过滤器接口耗时
1.找规律
局部过滤器命名规则 XXXGatewayFilterFactory, 必须以GatewayFilterFactory结尾。
/* 注意名称约定
* AddRequestHeaderGatewayFilterFactory 配置的时候写的是 AddRequestHeader
* AddRequestParameterGatewayFilterFactory 配置的时候写的是 AddRequestParameter
* LogTimeGatewayFilterFactory 配置的时候写什么?
* */
spring.cloud.gateway.routes[0].filters[0] = LogTime=gte,500
2.接口耗时过滤器
@Slf4j
@Component
public class LogTimeGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
private static long timeSpan = 0;
@Override
public GatewayFilter apply(NameValueConfig config) {
String timeSpanStr = config.getValue();
timeSpan = Long.valueOf(timeSpanStr);
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long startTime = System.currentTimeMillis();
return chain.filter(exchange).then(Mono.fromRunnable(()->{
long endTime = System.currentTimeMillis();
long time = endTime-startTime;
if(time >= timeSpan){
log.debug("{} 耗时:{}",exchange.getRequest().getURI(), time);
}
}));
}
};
}
}
3.如何使用
3.全链路跟踪TraceId日志
1.创建全局过滤器,在请求头上带入traceId参数,穿透到下游服务.
@Slf4j
@Component
public class TraceIdFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
List<String> traceIds = request.getHeaders().get("traceId");
if(ObjectUtil.isNotEmpty(traceIds)){
return chain.filter(exchange);
}
String traceId = IdUtil.simpleUUID();
ServerHttpRequest request2 = request.mutate().header("traceId", traceId).build();
ServerWebExchange exchange2 = exchange.mutate().request(request2).build();
return chain.filter(exchange2);
}
}
2.MDC原理
当请求来时生成一个traceId放在ThreadLocal里,然后打印时去取就行了。但在不改动原有输出语句的前提下自然需要日志框架的支持了。
MDC 介绍 MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
简而言之,MDC就是日志框架提供的一个InheritableThreadLocal,项目代码中可以将键值对放入其中,然后使用指定方式取出打印即可。
3.下游服务如何使用全链路跟踪Id
3.1配置TraceId 过滤器
@Order(1)
@WebFilter(urlPatterns = "/*",filterName = "traceIdFilter")
public class TraceIdFilter implements Filter {
public final static String MDC_TRACE_ID = "traceId";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String traceId = httpRequest.getHeader(MDC_TRACE_ID);
if (StringUtils.isBlank(traceId)) {
traceId = IdUtil.fastSimpleUUID();;
}
MDC.put(MDC_TRACE_ID, traceId);
chain.doFilter(request, response);
}
}
3.2 启动类开启ServletComponentScan扫描。
@SpringBootApplication
@ServletComponentScan
public class OpenApp {
public static void main(String[] args) {
SpringApplication.run(OpenApp.class, args);
}
}
3.3 配置文件配置日志输出格式
##日志输出格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%-5level) %clr([%X{traceId}]) %clr(${PID:-}) --- %clr(%logger{50}) - %m%n
4.Openfeign扩展
@Component
public class OpenFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String traceId = MDC.get(TraceIdFilter.MDC_TRACE_ID);
requestTemplate.header(TraceIdFilter.MDC_TRACE_ID, traceId);
}
}