官网文档: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
Spring Cloud Gateway
根据过滤器Filter的作用范围划分为GatewayFilter
和 GlobalFilter
,二者区别如下:
GatewayFilter : GatewayFilter
称为内置过滤器,需要通过 spring.cloud.routes.filters配置在具体路由下,只作用在当前路由上或者特定路由上,可以通过配置 spring.cloud.default-filters,表明作用在所有路由上,GatewayFilter允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway提供了许多内置的GatewayFilter工厂。
GlobalFilter: GlobalFilter
称为全局过滤器,不需要在配置文件中配置,作用在所有的路由上,全局有效。
GlobalFilter
全局过滤器不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter
包装成GatewayFilterChain
识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
Spring Cloud Gateway框架提供的内置的GlobalFilter如下:
GlobalFilter
接口和 GatewayFilter
有一样的接口定义,只不过, GlobalFilter
会作用于所有路由,我们可以用它来实现很多统一化处理的业务需求,比如权限认证,IP访问限制等等;而GatewayFilter
作用于某一个特定的路由, GlobalFilter
全局过滤器是一系列特殊的过滤器,会根据条件应用到所有路由中。GatewayFilter
网关过滤器是更细粒度的过滤器,作用于指定的路由中。
gateway自带的GlobalFilter实现类有很多,如下图:
有转发,路由,负载,WebSocket等相关的GlobalFilter,有兴趣的小伙伴可以下载对应的源码研究下。
关于如何定义一个GlobalFilter实现自己的业务逻辑,Gateway官方给出了一个例子:
例子5.1. ExampleConfiguration.java
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}
/**
* 自定义全局过滤器
*/
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
/**
* 过滤器业务逻辑
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("custom global filter");
return chain.filter(exchange);
}
/**
* 过滤器执行顺序,数值越小,优先级越高
*
* @return
*/
@Override
public int getOrder() {
return -1;
}
}
定义一个AuthFilter
过滤器实现 GlobalFilter, Ordered
全局过滤器,实现统一鉴权,例如摘自ruoyi-cloud
中的实现方案如下:
参考自: https://gitee.com/zhangmrit/ruoyi-cloud/
package com.ruoyi.gateway.fiflt;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import javax.annotation.Resource;
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.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.ValueOperations;
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 com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 网关鉴权
*/
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered
{
// 排除过滤的 uri 地址
// swagger排除自行添加
private static final String[] whiteList = {"/auth/login", "/user/register", "/system/v2/api-docs",
"/auth/captcha/check", "/auth/captcha/get","/auth/login/slide"};
@Resource(name = "stringRedisTemplate")
private ValueOperations<String, String> ops;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
String url = exchange.getRequest().getURI().getPath();
log.info("url:{}", url);
// 跳过不需要验证的路径
if (Arrays.asList(whiteList).contains(url))
{
return chain.filter(exchange);
}
String token = exchange.getRequest().getHeaders().getFirst(Constants.TOKEN);
// token为空
if (StringUtils.isBlank(token))
{
logger.info( "token is empty..." );
return setUnauthorizedResponse(exchange, "token can't null or empty string");
}
String userStr = ops.get(Constants.ACCESS_TOKEN + token);
if (StringUtils.isBlank(userStr))
{
return setUnauthorizedResponse(exchange, "token verify error");
}
JSONObject jo = JSONObject.parseObject(userStr);
String userId = jo.getString("userId");
// 查询token信息
if (StringUtils.isBlank(userId))
{
return setUnauthorizedResponse(exchange, "token verify error");
}
// 设置userId到request里,后续根据userId,获取用户信息
ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(Constants.CURRENT_ID, userId)
.header(Constants.CURRENT_USERNAME, jo.getString("loginName")).build();
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
return chain.filter(mutableExchange);
}
private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg)
{
ServerHttpResponse originalResponse = exchange.getResponse();
originalResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] response = null;
try
{
response = JSON.toJSONString(R.error(401, msg)).getBytes(Constants.UTF8);
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
return originalResponse.writeWith(Flux.just(buffer));
}
@Override
public int getOrder()
{
return -200;
}
}
在上面的AuthFilter需要实现GlobalFilter和Ordered接口,这和实现GatewayFilter很类似。然后根据ServerWebExchange获取ServerHttpRequest,再根据ServerHttpRequest中是否含有参数token,如果没有则完成请求,终止转发,否则执行正常的逻辑。