一、使用注意事项
1、全局过滤器作用于所有的路由,不需要单独配置。
2、通过@Order来指定执行的顺序,数字越小,优先级越高。
二、默认全局拦截器的整体架构
三、实战场景,例如,校验token、记录请求参数(可参考这边
https://www.cnblogs.com/hyf-huangyongfei/p/12849406.html
)、替换负载均衡以后的路由等等。
1、校验token
@Slf4j
public class AuthenFilter implements GlobalFilter, Ordered {
@Resource
private IFeignClient feignClient;
private static final String GATEWAY_ROUTE_BEAN = "org.springframework.cloud.gateway.support" +
".ServerWebExchangeUtils.gatewayRoute";
private static final String BEAR_HEAD = "bear";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestUrl = request.getPath().pathWithinApplication().value();
//判断过滤器是否执行
if (!RequestUtils.isFilter(requestUrl)) {
//该请求转发,因为访问/leap,需要展示登录页
if (requestUrl.equals("/leap/") || requestUrl.equals("/leap")) {
ServerHttpRequest authErrorReq = request.mutate()
.path("/index.html")
.build();
ServerWebExchange indexExchange = exchange.mutate().request(authErrorReq).build();
return chain.filter(indexExchange);
}
ResEntity res;
ServerHttpResponse response = exchange.getResponse();
Map<String, String> cookiesInfo = getCookiesInfo(request);
String account = cookiesInfo.get("account");
String token = cookiesInfo.get("token");
//校验token
res = feignClient.verifyToken(token);
log.info("校验token:{}", res.getMsg());
//如果token失效清除cookies ,让用户解锁或者重新登录
if (200 == res.getHttpStatus()) {
response.addCookie(ResponseCookie.from("token", token).path("/").build());
response.addCookie(ResponseCookie.from("userAccount", account).path("/").build());
} else {
log.error("网关过滤器AuthenFilter:{}", res.getMsg());
//token失效,通过cookies失效告知前端,重新解锁
response.addCookie(ResponseCookie.from("token", token).path("/").maxAge(Duration.ofSeconds(0L)).build());
response.addCookie(ResponseCookie.from("userAccount", account).path("/").maxAge(Duration.ofSeconds(0L)).build());
ServerHttpRequest authErrorReq = request.mutate()
.path("/index.html")
.build();
ServerWebExchange indexExchange = exchange.mutate().request(authErrorReq).build();
return chain.filter(indexExchange);
}
final ResEntity resEntity = feignClient.findUserByAccount(account);
//判断用户是否存在
if (200 != resEntity.getHttpStatus() || null == resEntity.getData()) {
throw new BusinessException(ExceptionEnum.AUTH_USER_NOT_FOUND, account);
}
//设置请求头信息
exchange = setHeader(exchange, resEntity);
}
return chain.filter(exchange);
}
/**
* 获取cookies中的数据
*
* @param request 请求对象
*/
private Map<String, String> getCookiesInfo(ServerHttpRequest request) {
Map<String, String> map = new HashMap<>();
Set<Map.Entry<String, List<HttpCookie>>> cookies = request.getCookies().entrySet();
for (Map.Entry<String, List<HttpCookie>> entry : cookies) {
if ("userAccount".equals(entry.getKey())) {
map.put("account", entry.getValue().get(0).getValue());
}
if ("token".equals(entry.getKey())) {
map.put("token", entry.getValue().get(0).getValue());
}
}
return map;
}
/**
* 设置头信息
* am exchange
*
* @param resEntity
* @return
* @throws UnsupportedEncodingException
*/
private ServerWebExchange setHeader(ServerWebExchange exchange, ResEntity resEntity) {
final HashMap<String, String> claims = Maps.newHashMap();
claims.put("jwt", UUID.randomUUID().toString().replaceAll("-", ""));
ServerHttpRequest userInfo = null;
try {
String user = URLEncoder.encode(JSON.toJSONString(resEntity.getData()), "UTF-8");
userInfo = exchange.getRequest().mutate()
.header(BEAR_HEAD, JwtHelper.genToken(claims))
.header("userInfo", user)
.build();
exchange = exchange.mutate().request(userInfo).build();
//feign拦截器的线程局部变量
FeignRequestInterceptor.setContext(user);
} catch (UnsupportedEncodingException e) {
throw new BusinessException(ExceptionEnum.COMMON_ENCODE_EXCEPTION, e, "网关拦截器");
}
return exchange;
}
/**
* 过滤器的优先级
*
* @return
*/
@Override
public int getOrder() {
return 4;
}
}
RequestInterceptor该类为Fegin请求的拦截器,你可以在通过Feign调用其他服务时,在请求头放数据,例如系统内部各系统通过fegin调用时,需要校验请求是否合法,各系统写个拦截器获取请求头约定的key然后通过内部公共的加解密方式校验key是否合法来提高系统的安全性。
@Slf4j
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {
private static final String BEAR_HEAD = "bear";
private static final String USER_INFO_HEAD = "hd-user";
private static final ThreadLocal<String> USER_INFO = new ThreadLocal<>();
public static void setContext(String userInfo) {
USER_INFO.set(userInfo);
}
public static void clean() {
USER_INFO.remove();
}
@Override
public void apply(RequestTemplate requestTemplate) {
final HashMap<String, String> claims = Maps.newHashMap();
claims.put("jwt", UUID.randomUUID().toString().replaceAll("-", ""));
requestTemplate.header(BEAR_HEAD, JwtHelper.genToken(claims));
if (null != USER_INFO.get()) {
requestTemplate.header(USER_INFO_HEAD, USER_INFO.get());
}
}
}
2、更改负载均衡后的url(该场景,需要因业务逻辑不同而特殊处理,我们项目中是用于灰度发布),该过滤器的优先级一定要在请求转发之前,负载均衡之后,可参考上图。
@Slf4j
public class VersionControlFilter implements GlobalFilter, Ordered {
private static final int VERSION_CONTROL_FILTER_ORDER = 101001;
private static final String HTTP_PREFIX = "http://";
private static final String SLASH = "/";
private static final String STAR = "*";
private static final String COLON = ":";
private final RedisUtil redisUtil;
private final ValueAnnotationUtils valueAnnotationUtils;
public VersionControlFilter(RedisUtil redisUtil, ValueAnnotationUtils valueAnnotationUtils) {
this.redisUtil = redisUtil;
this.valueAnnotationUtils = valueAnnotationUtils;
}
@Override
public int getOrder() {
return VERSION_CONTROL_FILTER_ORDER;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
//获取远程ip地址
InetSocketAddress inetSocketAddress = request.getRemoteAddress();
if (null == inetSocketAddress) {
return chain.filter(exchange);
}
String clientIp = inetSocketAddress.getAddress().getHostAddress();
//获取path
URI uri = request.getURI();
String path = uri.getPath();
//只有非白名单路径才版本控住
String requestPath = RequestUtils.getCurrentRequest(request);
if (!RequestUtils.isFilter(requestPath)) {
//判断redis中是否存在key
boolean hasKey =
redisUtil.exists(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv());
if (!hasKey) {
redisUtil.set(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv(),
JSON.toJSONString(new HashMap<>()));
}
//先取出原本的key
Map<String, String> preMap =
JSON.parseObject(redisUtil.get(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv()),
HashMap.class);
//正常url 例如 /platform/user/me
String clientAddress = clientIp + path;
String serviceIp = preMap.get(clientAddress);
//非正常,匹配正则表达式 例如 /platform/user/* 或者 /platform/user/**
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
if (StringUtils.isBlank(serviceIp)) {
serviceIp = getRegx(clientIp, path, preMap);
}
if (StringUtils.isBlank(serviceIp)) {
return chain.filter(exchange);
}
//负载均衡以后的路由地址 例如:http://160.5.34.210:9772/platform/user/me
int port = requestUrl.getPort();
//替换到灰度的版本中
StringBuilder forwardAddress = new StringBuilder(HTTP_PREFIX);
forwardAddress.append(serviceIp)
.append(COLON)
.append(port)
.append(path);
//追加参数
if ("GET".equalsIgnoreCase(request.getMethodValue())) {
forwardAddress.append("?").append(uri.getQuery());
}
log.debug("VersionControlFilter 灰度转发的地址:{}", forwardAddress.toString());
try {
requestUrl = new URI(forwardAddress.toString());
} catch (URISyntaxException e) {
log.error("VersionControlFilter URI不合法:{}", requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
}
return chain.filter(exchange);
}
/**
* 匹配正则规则
*
* @param clientIp 客户端ip
* @param path 路径
* @param map redis中的数据
* @return 服务器地址
*/
private String getRegx(String clientIp, String path, Map<String, String> map) {
String[] paths = path.split(SLASH);
if (1 > paths.length) {
log.error(" VersionControlFilter 请求路径:{}", path);
throw new BusinessException(" VersionControlFilter 请求路径不合法");
}
for (int i = 0; i < paths.length; i++) {
StringBuilder clientAddress = new StringBuilder(clientIp);
String item = paths[i];
if (StringUtils.isBlank(item)) {
continue;
}
for (int j = 0; j <= i; j++) {
if (StringUtils.isBlank(paths[j])) {
continue;
}
if (j == paths.length - 1) {
clientAddress.append(SLASH + STAR);
} else {
clientAddress.append(SLASH).append(paths[j]);
}
}
if (i != paths.length - 1) {
clientAddress.append(SLASH + STAR + STAR);
}
String serverIp = map.get(clientAddress.toString());
if (StringUtils.isNotBlank(serverIp)) {
return serverIp;
}
}
return null;
}
}
版权声明:本文为qqqqqqhhhhhh原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。