|
@@ -1,6 +1,5 @@
|
|
|
package com.jzg.filter;
|
|
package com.jzg.filter;
|
|
|
|
|
|
|
|
-import com.alibaba.fastjson.JSONArray;
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.redisson.api.RedissonClient;
|
|
import org.redisson.api.RedissonClient;
|
|
@@ -13,7 +12,6 @@ import org.springframework.core.io.buffer.DataBuffer;
|
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.HttpHeaders;
|
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.http.HttpStatus;
|
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.http.MediaType;
|
|
|
-import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Component;
|
|
|
import org.springframework.web.server.ServerWebExchange;
|
|
import org.springframework.web.server.ServerWebExchange;
|
|
|
import reactor.core.publisher.Mono;
|
|
import reactor.core.publisher.Mono;
|
|
@@ -25,9 +23,9 @@ import java.util.List;
|
|
|
/**
|
|
/**
|
|
|
* 全局鉴权过滤器 —— 网关层统一认证入口
|
|
* 全局鉴权过滤器 —— 网关层统一认证入口
|
|
|
* <p>
|
|
* <p>
|
|
|
- * 所有外部请求经过网关时,先在此过滤器校验 Token 的有效性。
|
|
|
|
|
- * 白名单内的路径直接放行,其他路径必须携带有效的 Authorization 头。
|
|
|
|
|
- * Token 校验通过后,将用户信息通过 X-* 请求头透传给下游微服务,
|
|
|
|
|
|
|
+ * 所有请求在网关入口处校验 Token 有效性。
|
|
|
|
|
+ * 白名单直接放行;其他路径校验 Authorization 头中的 Token 是否在 Redis 中存在。
|
|
|
|
|
+ * 校验通过后原样透传请求,不修改任何请求头。
|
|
|
* </p>
|
|
* </p>
|
|
|
*
|
|
*
|
|
|
* @author wh
|
|
* @author wh
|
|
@@ -47,14 +45,14 @@ public class AuthGlobalFilter implements GlobalFilter {
|
|
|
private static final List<String> WHITELIST = new ArrayList<>();
|
|
private static final List<String> WHITELIST = new ArrayList<>();
|
|
|
|
|
|
|
|
static {
|
|
static {
|
|
|
- // ==================== Swagger / API 文档 ====================
|
|
|
|
|
|
|
+ // Swagger / API 文档
|
|
|
WHITELIST.add("/swagger-ui/");
|
|
WHITELIST.add("/swagger-ui/");
|
|
|
WHITELIST.add("/v3/api-docs");
|
|
WHITELIST.add("/v3/api-docs");
|
|
|
WHITELIST.add("/swagger-resources/");
|
|
WHITELIST.add("/swagger-resources/");
|
|
|
WHITELIST.add("/webjars/");
|
|
WHITELIST.add("/webjars/");
|
|
|
WHITELIST.add("/favicon.ico");
|
|
WHITELIST.add("/favicon.ico");
|
|
|
|
|
|
|
|
- // ==================== 登录 & 注册(网页端) ====================
|
|
|
|
|
|
|
+ // 登录 & 注册(网页端)
|
|
|
WHITELIST.add("/auth/login");
|
|
WHITELIST.add("/auth/login");
|
|
|
WHITELIST.add("/auth/captchaById");
|
|
WHITELIST.add("/auth/captchaById");
|
|
|
WHITELIST.add("/auth/register");
|
|
WHITELIST.add("/auth/register");
|
|
@@ -62,26 +60,24 @@ public class AuthGlobalFilter implements GlobalFilter {
|
|
|
WHITELIST.add("/auth/forgotPassword");
|
|
WHITELIST.add("/auth/forgotPassword");
|
|
|
WHITELIST.add("/auth/publicKeyExchange");
|
|
WHITELIST.add("/auth/publicKeyExchange");
|
|
|
|
|
|
|
|
- // ==================== APP 登录 ====================
|
|
|
|
|
|
|
+ // APP 登录
|
|
|
WHITELIST.add("/appAuth/smsCodeLogin");
|
|
WHITELIST.add("/appAuth/smsCodeLogin");
|
|
|
WHITELIST.add("/appAuth/appLogin");
|
|
WHITELIST.add("/appAuth/appLogin");
|
|
|
WHITELIST.add("/appAuth/partnerAppLogin");
|
|
WHITELIST.add("/appAuth/partnerAppLogin");
|
|
|
|
|
|
|
|
- // ==================== 短信 ====================
|
|
|
|
|
|
|
+ // 短信
|
|
|
WHITELIST.add("/send/sendSmsCode");
|
|
WHITELIST.add("/send/sendSmsCode");
|
|
|
WHITELIST.add("/sendSmsCode");
|
|
WHITELIST.add("/sendSmsCode");
|
|
|
WHITELIST.add("/appLogin");
|
|
WHITELIST.add("/appLogin");
|
|
|
WHITELIST.add("/smsCodeLogin");
|
|
WHITELIST.add("/smsCodeLogin");
|
|
|
|
|
|
|
|
- // ==================== 其他公开接口 ====================
|
|
|
|
|
|
|
+ // 其他公开接口
|
|
|
WHITELIST.add("/system/sysListAccountLinking");
|
|
WHITELIST.add("/system/sysListAccountLinking");
|
|
|
WHITELIST.add("/supplementOrder/getOrderDetail");
|
|
WHITELIST.add("/supplementOrder/getOrderDetail");
|
|
|
WHITELIST.add("/dict/get");
|
|
WHITELIST.add("/dict/get");
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- // ==================== 本地生活调用的接口 ====================
|
|
|
|
|
|
|
+ // 本地生活
|
|
|
WHITELIST.add("/signIn/updateSignCompleteRuleIn");
|
|
WHITELIST.add("/signIn/updateSignCompleteRuleIn");
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -93,7 +89,7 @@ public class AuthGlobalFilter implements GlobalFilter {
|
|
|
return chain.filter(exchange);
|
|
return chain.filter(exchange);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 获取 Authorization 头(兼容大小写)
|
|
|
|
|
|
|
+ // 2. 获取 Authorization 头
|
|
|
HttpHeaders headers = exchange.getRequest().getHeaders();
|
|
HttpHeaders headers = exchange.getRequest().getHeaders();
|
|
|
String authorization = headers.getFirst("Authorization");
|
|
String authorization = headers.getFirst("Authorization");
|
|
|
if (authorization == null || authorization.isEmpty()) {
|
|
if (authorization == null || authorization.isEmpty()) {
|
|
@@ -102,50 +98,25 @@ public class AuthGlobalFilter implements GlobalFilter {
|
|
|
|
|
|
|
|
// 3. Token 不存在 → 401
|
|
// 3. Token 不存在 → 401
|
|
|
if (authorization == null || authorization.isEmpty()) {
|
|
if (authorization == null || authorization.isEmpty()) {
|
|
|
- log.warn("【网关鉴权】请求缺少 Authorization 头。path=[{}]", path);
|
|
|
|
|
|
|
+ log.warn("【网关鉴权】缺少 Authorization 头。path=[{}]", path);
|
|
|
return unauthorized(exchange, "未提供认证令牌");
|
|
return unauthorized(exchange, "未提供认证令牌");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 4. 校验 Token 在 Redis 中是否存在
|
|
// 4. 校验 Token 在 Redis 中是否存在
|
|
|
- String userJsonStr;
|
|
|
|
|
try {
|
|
try {
|
|
|
Object bucketValue = redissonClient.getBucket(authorization).get();
|
|
Object bucketValue = redissonClient.getBucket(authorization).get();
|
|
|
if (bucketValue == null) {
|
|
if (bucketValue == null) {
|
|
|
log.warn("【网关鉴权】Token 无效或已过期。path=[{}]", path);
|
|
log.warn("【网关鉴权】Token 无效或已过期。path=[{}]", path);
|
|
|
return unauthorized(exchange, "认证令牌无效或已过期");
|
|
return unauthorized(exchange, "认证令牌无效或已过期");
|
|
|
}
|
|
}
|
|
|
- userJsonStr = bucketValue.toString();
|
|
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- log.error("【网关鉴权】Redis 查询异常。path=[{}], error=[{}]", path, e.getMessage(), e);
|
|
|
|
|
|
|
+ log.error("【网关鉴权】Redis 查询异常。path=[{}]", path, e);
|
|
|
return unauthorized(exchange, "认证服务异常");
|
|
return unauthorized(exchange, "认证服务异常");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 5. 解析用户信息并透传到下游微服务
|
|
|
|
|
- try {
|
|
|
|
|
- JSONObject userInfo = JSONObject.parseObject(userJsonStr);
|
|
|
|
|
-
|
|
|
|
|
- ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
|
|
|
|
|
- .header("X-User-Id", safeGet(userInfo, "userId"))
|
|
|
|
|
- .header("X-Username", safeGet(userInfo, "username"))
|
|
|
|
|
- .header("X-User-Name", safeGet(userInfo, "name"))
|
|
|
|
|
- .header("X-System-Code", safeGet(userInfo, "system"))
|
|
|
|
|
- .header("X-Dept-Id", safeGet(userInfo, "deptId"))
|
|
|
|
|
- .header("X-Work-Number", safeGet(userInfo, "workNumber"))
|
|
|
|
|
- .header("X-Province", safeGet(userInfo, "province"))
|
|
|
|
|
- .header("X-Auths", safeGetJsonArray(userInfo, "auths"))
|
|
|
|
|
- .header("X-Data-Scope", safeGetJsonObject(userInfo, "dataScope"))
|
|
|
|
|
- .header("X-Head-Sculpture", safeGet(userInfo, "headSculpture"))
|
|
|
|
|
- .build();
|
|
|
|
|
-
|
|
|
|
|
- log.debug("【网关鉴权】认证通过。path=[{}], user=[{}], system=[{}]",
|
|
|
|
|
- path, safeGet(userInfo, "username"), safeGet(userInfo, "system"));
|
|
|
|
|
-
|
|
|
|
|
- return chain.filter(exchange.mutate().request(mutatedRequest).build());
|
|
|
|
|
-
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("【网关鉴权】用户信息解析异常。path=[{}], error=[{}]", path, e.getMessage(), e);
|
|
|
|
|
- return unauthorized(exchange, "用户信息解析失败");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 5. Token 有效,原样透传请求(不修改任何请求头)
|
|
|
|
|
+ log.debug("【网关鉴权】认证通过。path=[{}]", path);
|
|
|
|
|
+ return chain.filter(exchange);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -161,31 +132,7 @@ public class AuthGlobalFilter implements GlobalFilter {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 安全获取字符串字段(null → "")
|
|
|
|
|
- */
|
|
|
|
|
- private String safeGet(JSONObject json, String key) {
|
|
|
|
|
- String value = json.getString(key);
|
|
|
|
|
- return value != null ? value : "";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 安全获取 JSONArray 字段(转为 JSON 字符串,null → "[]")
|
|
|
|
|
- */
|
|
|
|
|
- private String safeGetJsonArray(JSONObject json, String key) {
|
|
|
|
|
- JSONArray array = json.getJSONArray(key);
|
|
|
|
|
- return array != null ? array.toJSONString() : "[]";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 安全获取 JSONObject 字段(转为 JSON 字符串,null → "{}")
|
|
|
|
|
- */
|
|
|
|
|
- private String safeGetJsonObject(JSONObject json, String key) {
|
|
|
|
|
- JSONObject obj = json.getJSONObject(key);
|
|
|
|
|
- return obj != null ? obj.toJSONString() : "{}";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 返回 401 未授权响应(HTTP 200 + JSON body,与原有风格保持一致)
|
|
|
|
|
|
|
+ * 返回 401 未授权响应
|
|
|
*/
|
|
*/
|
|
|
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
|
|
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
|
|
|
exchange.getResponse().setStatusCode(HttpStatus.OK);
|
|
exchange.getResponse().setStatusCode(HttpStatus.OK);
|