清理多租户
This commit is contained in:
@@ -96,7 +96,6 @@ spring:
|
||||
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
tenant-id: <tenantId>
|
||||
```
|
||||
|
||||
网关校验成功后,会透传 `login-user` 给后端服务。
|
||||
|
||||
@@ -79,7 +79,7 @@ public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
|
||||
// 基于 tag 过滤实例列表
|
||||
chooseInstances = filterTagServiceInstances(chooseInstances, headers);
|
||||
|
||||
// 随机 + 权重获取实例列表 TODO 芋艿:目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法
|
||||
// 随机 + 权重获取实例列表 TODO 目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法
|
||||
return new DefaultResponse(NacosBalancer.getHostByRandomWeight3(chooseInstances));
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER;
|
||||
*
|
||||
* 从功能上,它类似 rdms-spring-boot-starter-web 的 ApiAccessLogFilter 过滤器
|
||||
*
|
||||
* TODO 芋艿:如果网关执行异常,不会记录访问日志,后续研究下 https://github.com/Silvmike/webflux-demo/blob/master/tests/src/test/java/ru/hardcoders/demo/webflux/web_handler/filters/logging
|
||||
* TODO 如果网关执行异常,不会记录访问日志,后续研究下 https://github.com/Silvmike/webflux-demo/blob/master/tests/src/test/java/ru/hardcoders/demo/webflux/web_handler/filters/logging
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@@ -71,34 +71,34 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
|
||||
// log.info("[writeAccessLog][日志内容:{}]", JsonUtils.toJsonString(gatewayLog));
|
||||
|
||||
// 方式二:调用远程服务,记录到数据库中
|
||||
// TODO 芋艿:暂未实现
|
||||
// TODO 暂未实现
|
||||
|
||||
// 方式三:打印到控制台,方便排查错误
|
||||
try {
|
||||
Map<String, Object> values = MapUtil.newHashMap(15, true); // 手工拼接,保证排序;15 保证不用扩容
|
||||
values.put("userId", gatewayLog.getUserId());
|
||||
values.put("userType", gatewayLog.getUserType());
|
||||
values.put("routeId", gatewayLog.getRoute() != null ? gatewayLog.getRoute().getId() : null);
|
||||
values.put("schema", gatewayLog.getSchema());
|
||||
values.put("requestUrl", gatewayLog.getRequestUrl());
|
||||
values.put("queryParams", gatewayLog.getQueryParams().toSingleValueMap());
|
||||
values.put("requestBody", JsonUtils.isJson(gatewayLog.getRequestBody()) ? // 保证 body 的展示好看
|
||||
JSONUtil.parse(gatewayLog.getRequestBody()) : gatewayLog.getRequestBody());
|
||||
values.put("requestHeaders", JsonUtils.toJsonString(gatewayLog.getRequestHeaders().toSingleValueMap()));
|
||||
values.put("userIp", gatewayLog.getUserIp());
|
||||
values.put("responseBody", JsonUtils.isJson(gatewayLog.getResponseBody()) ? // 保证 body 的展示好看
|
||||
JSONUtil.parse(gatewayLog.getResponseBody()) : gatewayLog.getResponseBody());
|
||||
values.put("responseHeaders", gatewayLog.getResponseHeaders() != null ?
|
||||
JsonUtils.toJsonString(gatewayLog.getResponseHeaders().toSingleValueMap()) : null);
|
||||
values.put("httpStatus", gatewayLog.getHttpStatus());
|
||||
values.put("startTime", LocalDateTimeUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMATTER));
|
||||
values.put("endTime", LocalDateTimeUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMATTER));
|
||||
values.put("duration", gatewayLog.getDuration() != null ? gatewayLog.getDuration() + " ms" : null);
|
||||
log.info("[writeAccessLog][网关日志:{}]", JsonUtils.toJsonPrettyString(values));
|
||||
} catch (Exception e) {
|
||||
// 兜底处理,参见 https://gitee.com/zhijiantianya/rdms-cloud/issues/IC9A70
|
||||
log.error("[writeAccessLog][打印网关日志时,发生异常]", e);
|
||||
}
|
||||
// try {
|
||||
// Map<String, Object> values = MapUtil.newHashMap(15, true); // 手工拼接,保证排序;15 保证不用扩容
|
||||
// values.put("userId", gatewayLog.getUserId());
|
||||
// values.put("userType", gatewayLog.getUserType());
|
||||
// values.put("routeId", gatewayLog.getRoute() != null ? gatewayLog.getRoute().getId() : null);
|
||||
// values.put("schema", gatewayLog.getSchema());
|
||||
// values.put("requestUrl", gatewayLog.getRequestUrl());
|
||||
// values.put("queryParams", gatewayLog.getQueryParams().toSingleValueMap());
|
||||
// values.put("requestBody", JsonUtils.isJson(gatewayLog.getRequestBody()) ? // 保证 body 的展示好看
|
||||
// JSONUtil.parse(gatewayLog.getRequestBody()) : gatewayLog.getRequestBody());
|
||||
// values.put("requestHeaders", JsonUtils.toJsonString(gatewayLog.getRequestHeaders().toSingleValueMap()));
|
||||
// values.put("userIp", gatewayLog.getUserIp());
|
||||
// values.put("responseBody", JsonUtils.isJson(gatewayLog.getResponseBody()) ? // 保证 body 的展示好看
|
||||
// JSONUtil.parse(gatewayLog.getResponseBody()) : gatewayLog.getResponseBody());
|
||||
// values.put("responseHeaders", gatewayLog.getResponseHeaders() != null ?
|
||||
// JsonUtils.toJsonString(gatewayLog.getResponseHeaders().toSingleValueMap()) : null);
|
||||
// values.put("httpStatus", gatewayLog.getHttpStatus());
|
||||
// values.put("startTime", LocalDateTimeUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMATTER));
|
||||
// values.put("endTime", LocalDateTimeUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMATTER));
|
||||
// values.put("duration", gatewayLog.getDuration() != null ? gatewayLog.getDuration() + " ms" : null);
|
||||
// log.info("[writeAccessLog][网关日志:{}]", JsonUtils.toJsonPrettyString(values));
|
||||
// } catch (Exception e) {
|
||||
// // 兜底处理,参见 https://gitee.com/zhijiantianya/rdms-cloud/issues/IC9A70
|
||||
// log.error("[writeAccessLog][打印网关日志时,发生异常]", e);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,39 +6,13 @@ import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
*
|
||||
* copy from rdms-spring-boot-starter-security 的 LoginUser 类
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Data
|
||||
public class LoginUser {
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 额外的用户信息
|
||||
*/
|
||||
private Map<String, String> info;
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private Long tenantId;
|
||||
/**
|
||||
* 授权范围
|
||||
*/
|
||||
private List<String> scopes;
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private LocalDateTime expiresTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -6,12 +6,10 @@ import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.njcn.rdms.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import com.njcn.rdms.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
|
||||
import com.njcn.rdms.framework.common.core.KeyValue;
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.framework.common.util.date.LocalDateTimeUtils;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.gateway.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.gateway.util.WebFrameworkUtils;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
@@ -28,141 +26,102 @@ import java.util.function.Function;
|
||||
|
||||
import static com.njcn.rdms.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
|
||||
|
||||
/**
|
||||
* Token 过滤器,验证 token 的有效性
|
||||
* 1. 验证通过时,将 userId、userType、tenantId 通过 Header 转发给服务
|
||||
* 2. 验证不通过,还是会转发给服务。因为,接口是否需要登录的校验,还是交给服务自身处理
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Component
|
||||
public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
|
||||
|
||||
/**
|
||||
* CommonResult<OAuth2AccessTokenCheckRespDTO> 对应的 TypeReference 结果,用于解析 checkToken 的结果
|
||||
*/
|
||||
private static final TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>> CHECK_RESULT_TYPE_REFERENCE
|
||||
= new TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>>() {};
|
||||
private static final TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>> CHECK_RESULT_TYPE_REFERENCE =
|
||||
new TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>>() {};
|
||||
|
||||
/**
|
||||
* 空的 LoginUser 的结果
|
||||
*
|
||||
* 用于解决如下问题:
|
||||
* 1. {@link #getLoginUser(ServerWebExchange, String)} 返回 Mono.empty() 时,会导致后续的 flatMap 无法进行处理的问题。
|
||||
* 2. {@link #buildUser(String)} 时,如果 Token 已经过期,返回 LOGIN_USER_EMPTY 对象,避免缓存无法刷新
|
||||
*/
|
||||
private static final LoginUser LOGIN_USER_EMPTY = new LoginUser();
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
/**
|
||||
* 登录用户的本地缓存
|
||||
*
|
||||
* key1:多租户的编号
|
||||
* key2:访问令牌
|
||||
*/
|
||||
private final LoadingCache<KeyValue<Long, String>, LoginUser> loginUserCache = buildAsyncReloadingCache(Duration.ofMinutes(1),
|
||||
new CacheLoader<KeyValue<Long, String>, LoginUser>() {
|
||||
private final LoadingCache<String, LoginUser> loginUserCache = buildAsyncReloadingCache(Duration.ofMinutes(1),
|
||||
new CacheLoader<String, LoginUser>() {
|
||||
|
||||
@Override
|
||||
public LoginUser load(KeyValue<Long, String> token) {
|
||||
String body = checkAccessToken(token.getKey(), token.getValue()).block();
|
||||
public LoginUser load(String token) {
|
||||
String body = checkAccessToken(token).block();
|
||||
return buildUser(body);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) {
|
||||
// Q:为什么不使用 OAuth2TokenApi 进行调用?
|
||||
// A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support
|
||||
// A2:校验 Token 的 API 需要使用到 header[tenant-id] 传递租户编号,暂时不想编写 RequestInterceptor 实现
|
||||
// 因此,这里采用 WebClient,通过 lbFunction 实现负载均衡
|
||||
this.webClient = WebClient.builder().filter(lbFunction).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
// 移除 login-user 的请求头,避免伪造模拟
|
||||
exchange = SecurityFrameworkUtils.removeLoginUser(exchange);
|
||||
|
||||
// 情况一,如果没有 Token 令牌,则直接继续 filter
|
||||
String token = SecurityFrameworkUtils.obtainAuthorization(exchange);
|
||||
if (StrUtil.isEmpty(token)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 情况二,如果有 Token 令牌,则解析对应 userId、userType、tenantId 等字段,并通过 通过 Header 转发给服务
|
||||
// 重要说明:defaultIfEmpty 作用,保证 Mono.empty() 情况,可以继续执行 `flatMap 的 chain.filter(exchange)` 逻辑,避免返回给前端空的 Response!!
|
||||
ServerWebExchange finalExchange = exchange;
|
||||
return getLoginUser(exchange, token).defaultIfEmpty(LOGIN_USER_EMPTY).flatMap(user -> {
|
||||
// 1. 无用户,直接 filter 继续请求
|
||||
if (user == LOGIN_USER_EMPTY || // 下面 expiresTime 的判断,为了解决 token 实际已经过期的情况
|
||||
user.getExpiresTime() == null || LocalDateTimeUtils.beforeNow(user.getExpiresTime())) {
|
||||
return getLoginUser(token).defaultIfEmpty(LOGIN_USER_EMPTY).flatMap(user -> {
|
||||
if (user == LOGIN_USER_EMPTY
|
||||
|| user.getExpiresTime() == null
|
||||
|| LocalDateTimeUtils.beforeNow(user.getExpiresTime())) {
|
||||
return chain.filter(finalExchange);
|
||||
}
|
||||
|
||||
// 2.1 有用户,则设置登录用户
|
||||
SecurityFrameworkUtils.setLoginUser(finalExchange, user);
|
||||
// 2.2 将 user 并设置到 login-user 的请求头,使用 json 存储值
|
||||
ServerWebExchange newExchange = finalExchange.mutate()
|
||||
.request(builder -> SecurityFrameworkUtils.setLoginUserHeader(builder, user)).build();
|
||||
.request(builder -> SecurityFrameworkUtils.setLoginUserHeader(builder, user))
|
||||
.build();
|
||||
return chain.filter(newExchange);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<LoginUser> getLoginUser(ServerWebExchange exchange, String token) {
|
||||
// 从缓存中,获取 LoginUser
|
||||
Long tenantId = WebFrameworkUtils.getTenantId(exchange);
|
||||
KeyValue<Long, String> cacheKey = new KeyValue<Long, String>().setKey(tenantId).setValue(token);
|
||||
LoginUser localUser = loginUserCache.getIfPresent(cacheKey);
|
||||
private Mono<LoginUser> getLoginUser(String token) {
|
||||
LoginUser localUser = loginUserCache.getIfPresent(token);
|
||||
if (localUser != null) {
|
||||
return Mono.just(localUser);
|
||||
}
|
||||
|
||||
// 缓存不存在,则请求远程服务
|
||||
return checkAccessToken(tenantId, token).flatMap((Function<String, Mono<LoginUser>>) body -> {
|
||||
return checkAccessToken(token).flatMap((Function<String, Mono<LoginUser>>) body -> {
|
||||
LoginUser remoteUser = buildUser(body);
|
||||
if (remoteUser != null) {
|
||||
// 非空,则进行缓存
|
||||
loginUserCache.put(cacheKey, remoteUser);
|
||||
loginUserCache.put(token, remoteUser);
|
||||
return Mono.just(remoteUser);
|
||||
}
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<String> checkAccessToken(Long tenantId, String token) {
|
||||
private Mono<String> checkAccessToken(String token) {
|
||||
return webClient.get()
|
||||
.uri(OAuth2TokenCommonApi.URL_CHECK, uriBuilder -> uriBuilder.queryParam("accessToken", token).build())
|
||||
.headers(httpHeaders -> WebFrameworkUtils.setTenantIdHeader(tenantId, httpHeaders)) // 设置租户的 Header
|
||||
.retrieve().bodyToMono(String.class);
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
}
|
||||
|
||||
private LoginUser buildUser(String body) {
|
||||
// 处理结果,结果不正确
|
||||
CommonResult<OAuth2AccessTokenCheckRespDTO> result = JsonUtils.parseObject(body, CHECK_RESULT_TYPE_REFERENCE);
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
if (result.isError()) {
|
||||
// 特殊情况:令牌已经过期(code = 401),需要返回 LOGIN_USER_EMPTY,避免 Token 一直因为缓存,被误判为有效
|
||||
if (Objects.equals(result.getCode(), HttpStatus.UNAUTHORIZED.value())) {
|
||||
return LOGIN_USER_EMPTY;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建登录用户
|
||||
OAuth2AccessTokenCheckRespDTO tokenInfo = result.getData();
|
||||
return new LoginUser().setId(tokenInfo.getUserId()).setUserType(tokenInfo.getUserType())
|
||||
.setInfo(tokenInfo.getUserInfo()) // 额外的用户信息
|
||||
.setTenantId(tokenInfo.getTenantId()).setScopes(tokenInfo.getScopes())
|
||||
return new LoginUser().setId(tokenInfo.getUserId())
|
||||
.setUserType(tokenInfo.getUserType())
|
||||
.setInfo(tokenInfo.getUserInfo())
|
||||
.setScopes(tokenInfo.getScopes())
|
||||
.setExpiresTime(tokenInfo.getExpiresTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -100; // 和 Spring Security Filter 的顺序对齐
|
||||
return -100;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
|
||||
*/
|
||||
private CommonResult<?> responseStatusExceptionHandler(ServerWebExchange exchange,
|
||||
ResponseStatusException ex) {
|
||||
// TODO 芋艿:这里要精细化翻译,默认返回用户是看不懂的
|
||||
// TODO 这里要精细化翻译,默认返回用户是看不懂的
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
log.error("[responseStatusExceptionHandler][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex);
|
||||
return CommonResult.error(ex.getStatusCode().value(), ex.getReason());
|
||||
@@ -66,7 +66,7 @@ public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
|
||||
Throwable ex) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
log.error("[defaultExceptionHandler][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex);
|
||||
// TODO 芋艿:是否要插入异常日志呢?
|
||||
// TODO 是否要插入异常日志呢?
|
||||
// 返回 ERROR CommonResult
|
||||
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
|
||||
}
|
||||
|
||||
@@ -2,91 +2,45 @@ package com.njcn.rdms.gateway.util;
|
||||
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.route.Route;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Web 工具类
|
||||
*
|
||||
* copy from rdms-spring-boot-starter-web 的 WebFrameworkUtils 类
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Slf4j
|
||||
public class WebFrameworkUtils {
|
||||
|
||||
private static final String HEADER_TENANT_ID = "tenant-id";
|
||||
|
||||
private WebFrameworkUtils() {}
|
||||
|
||||
/**
|
||||
* 将 Gateway 请求中的 header,设置到 HttpHeaders 中
|
||||
*
|
||||
* @param tenantId 租户编号
|
||||
* @param httpHeaders WebClient 的请求
|
||||
*/
|
||||
public static void setTenantIdHeader(Long tenantId, HttpHeaders httpHeaders) {
|
||||
if (tenantId == null) {
|
||||
return;
|
||||
}
|
||||
httpHeaders.set(HEADER_TENANT_ID, String.valueOf(tenantId));
|
||||
}
|
||||
|
||||
public static Long getTenantId(ServerWebExchange exchange) {
|
||||
String tenantId = exchange.getRequest().getHeaders().getFirst(HEADER_TENANT_ID);
|
||||
return NumberUtil.isNumber(tenantId) ? Long.valueOf(tenantId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 JSON 字符串
|
||||
*
|
||||
* @param exchange 响应
|
||||
* @param object 对象,会序列化成 JSON 字符串
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Mono<Void> writeJSON(ServerWebExchange exchange, Object object) {
|
||||
// 设置 header
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
|
||||
// 设置 body
|
||||
return response.writeWith(Mono.fromSupplier(() -> {
|
||||
DataBufferFactory bufferFactory = response.bufferFactory();
|
||||
try {
|
||||
return bufferFactory.wrap(JsonUtils.toJsonByte(object));
|
||||
} catch (Exception ex) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
log.error("[writeJSON][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex);
|
||||
log.error("[writeJSON][uri({}/{}) error]", request.getURI(), request.getMethod(), ex);
|
||||
return bufferFactory.wrap(new byte[0]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得客户端 IP
|
||||
*
|
||||
* 参考 {@link ServletUtil} 的 getClientIP 方法
|
||||
*
|
||||
* @param exchange 请求
|
||||
* @param otherHeaderNames 其它 header 名字的数组
|
||||
* @return 客户端 IP
|
||||
*/
|
||||
public static String getClientIP(ServerWebExchange exchange, String... otherHeaderNames) {
|
||||
String[] headers = { "X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR" };
|
||||
String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP",
|
||||
"HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
|
||||
if (ArrayUtil.isNotEmpty(otherHeaderNames)) {
|
||||
headers = ArrayUtil.addAll(headers, otherHeaderNames);
|
||||
}
|
||||
// 方式一,通过 header 获取
|
||||
String ip;
|
||||
for (String header : headers) {
|
||||
ip = exchange.getRequest().getHeaders().getFirst(header);
|
||||
@@ -94,8 +48,6 @@ public class WebFrameworkUtils {
|
||||
return NetUtil.getMultistageReverseProxyIp(ip);
|
||||
}
|
||||
}
|
||||
|
||||
// 方式二,通过 remoteAddress 获取
|
||||
if (exchange.getRequest().getRemoteAddress() == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -103,12 +55,6 @@ public class WebFrameworkUtils {
|
||||
return NetUtil.getMultistageReverseProxyIp(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求匹配的 Route 路由
|
||||
*
|
||||
* @param exchange 请求
|
||||
* @return 路由
|
||||
*/
|
||||
public static Route getGatewayRoute(ServerWebExchange exchange) {
|
||||
return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user