Compare commits
No commits in common. "main" and "f-debug" have entirely different histories.
|
|
@ -1 +1 @@
|
||||||
ddddddddddddddddddddddddddddddddddddddddddddddddddd=edddd2d1234567891011的堆堆
|
2d1234567891011
|
||||||
|
|
|
||||||
|
|
@ -49,25 +49,7 @@ public class AuthFilter implements GlobalFilter, Ordered
|
||||||
// 跳过不需要验证的路径
|
// 跳过不需要验证的路径
|
||||||
if (StringUtils.matches(url, ignoreWhite.getWhites()))
|
if (StringUtils.matches(url, ignoreWhite.getWhites()))
|
||||||
{
|
{
|
||||||
// 白名单路径也尝试解析token并传递用户信息(如果有token的话)
|
return chain.filter(exchange);
|
||||||
String token = getToken(request);
|
|
||||||
if (StringUtils.isNotEmpty(token))
|
|
||||||
{
|
|
||||||
Claims claims = JwtUtils.parseToken(token);
|
|
||||||
if (claims != null)
|
|
||||||
{
|
|
||||||
String userkey = JwtUtils.getUserKey(claims);
|
|
||||||
String userid = JwtUtils.getUserId(claims);
|
|
||||||
String username = JwtUtils.getUserName(claims);
|
|
||||||
// 设置用户信息到请求
|
|
||||||
addHeader(mutate, SecurityConstants.USER_KEY, userkey);
|
|
||||||
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
|
|
||||||
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 内部请求来源参数清除
|
|
||||||
removeHeader(mutate, SecurityConstants.FROM_SOURCE);
|
|
||||||
return chain.filter(exchange.mutate().request(mutate.build()).build());
|
|
||||||
}
|
}
|
||||||
String token = getToken(request);
|
String token = getToken(request);
|
||||||
if (StringUtils.isEmpty(token))
|
if (StringUtils.isEmpty(token))
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,6 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
|
||||||
ServerHttpRequest request = exchange.getRequest();
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
|
|
||||||
// 非登录/注册请求或验证码关闭,不处理
|
// 非登录/注册请求或验证码关闭,不处理
|
||||||
// 已禁用验证码校验,直接放行
|
|
||||||
return chain.filter(exchange);
|
|
||||||
|
|
||||||
// 以下代码已注释,不再校验验证码
|
|
||||||
/*
|
|
||||||
if (!StringUtils.equalsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
|
if (!StringUtils.equalsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
|
||||||
{
|
{
|
||||||
return chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
|
|
@ -66,7 +61,6 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
|
||||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
|
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
|
||||||
}
|
}
|
||||||
return chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
package com.ruoyi.gateway.filter;
|
|
||||||
|
|
||||||
import com.ruoyi.gateway.utils.WvpTokenClient;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
|
||||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
|
||||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WVP Access Token 过滤器
|
|
||||||
* 用于为 WVP 请求添加或替换 access-token 请求头
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class WvpAccessTokenFilter extends AbstractGatewayFilterFactory<WvpAccessTokenFilter.Config> {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(WvpAccessTokenFilter.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WVP token 过期时间(分钟)- 从 WVP 源码中获取
|
|
||||||
*/
|
|
||||||
private static final long TOKEN_EXPIRATION_MINUTES = 30;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提前刷新时间(分钟)- 在 token 过期前 5 分钟刷新
|
|
||||||
*/
|
|
||||||
private static final long REFRESH_BEFORE_EXPIRATION_MINUTES = 5;
|
|
||||||
|
|
||||||
private static String cachedAccessToken = null;
|
|
||||||
private static LocalDateTime tokenExpirationTime = null;
|
|
||||||
private static final Object LOCK = new Object();
|
|
||||||
|
|
||||||
private final WebClient webClient = WebClient.builder().build();
|
|
||||||
|
|
||||||
public WvpAccessTokenFilter() {
|
|
||||||
super(Config.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GatewayFilter apply(Config config) {
|
|
||||||
return (exchange, chain) -> {
|
|
||||||
String path = exchange.getRequest().getURI().getPath();
|
|
||||||
String query = exchange.getRequest().getURI().getQuery();
|
|
||||||
String fullPath = query != null ? path + "?" + query : path;
|
|
||||||
|
|
||||||
log.info("WVP 请求 - 路径: {}", fullPath);
|
|
||||||
|
|
||||||
// 使用 WebClient 直接调用 WVP,支持自动重试
|
|
||||||
return callWvpWithRetry(fullPath, exchange, 0)
|
|
||||||
.flatMap(responseBody -> {
|
|
||||||
// 将响应写回客户端
|
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
|
||||||
response.setStatusCode(HttpStatus.OK);
|
|
||||||
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
|
|
||||||
|
|
||||||
DataBufferFactory bufferFactory = response.bufferFactory();
|
|
||||||
DataBuffer buffer = bufferFactory.wrap(responseBody.getBytes(StandardCharsets.UTF_8));
|
|
||||||
|
|
||||||
return response.writeWith(Mono.just(buffer));
|
|
||||||
})
|
|
||||||
.onErrorResume(e -> {
|
|
||||||
log.error("WVP 请求失败", e);
|
|
||||||
return chain.filter(exchange);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调用 WVP 接口,支持自动重试
|
|
||||||
*
|
|
||||||
* @param path 请求路径
|
|
||||||
* @param exchange ServerWebExchange
|
|
||||||
* @param retryCount 重试次数
|
|
||||||
* @return 响应体
|
|
||||||
*/
|
|
||||||
private Mono<String> callWvpWithRetry(String path, ServerWebExchange exchange, int retryCount) {
|
|
||||||
try {
|
|
||||||
String accessToken = getAccessToken();
|
|
||||||
String wvpUrl = "http://wvp-pro:18978" + path;
|
|
||||||
|
|
||||||
log.debug("调用 WVP 接口: {}, access-token: {}", wvpUrl, accessToken);
|
|
||||||
|
|
||||||
return webClient.get()
|
|
||||||
.uri(wvpUrl)
|
|
||||||
.header("access-token", accessToken)
|
|
||||||
.retrieve()
|
|
||||||
.bodyToMono(String.class)
|
|
||||||
.onErrorResume(e -> {
|
|
||||||
log.error("WVP 请求失败,尝试刷新 token 并重试", e);
|
|
||||||
if (retryCount < 1) {
|
|
||||||
// 清除缓存的 token,下次会重新获取
|
|
||||||
clearCachedToken();
|
|
||||||
// 重试一次
|
|
||||||
return callWvpWithRetry(path, exchange, retryCount + 1);
|
|
||||||
}
|
|
||||||
return Mono.error(e);
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("获取 access token 失败", e);
|
|
||||||
return Mono.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 access token,使用缓存机制并支持提前刷新
|
|
||||||
* 如果缓存为空或即将过期,则调用 WvpTokenClient.login 获取新的 token
|
|
||||||
*/
|
|
||||||
private String getAccessToken() throws Exception {
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
|
||||||
|
|
||||||
// 检查 token 是否需要刷新(不存在或即将过期)
|
|
||||||
if (cachedAccessToken == null || tokenExpirationTime == null ||
|
|
||||||
now.plusMinutes(REFRESH_BEFORE_EXPIRATION_MINUTES).isAfter(tokenExpirationTime)) {
|
|
||||||
|
|
||||||
synchronized (LOCK) {
|
|
||||||
// 双重检查
|
|
||||||
now = LocalDateTime.now();
|
|
||||||
if (cachedAccessToken == null || tokenExpirationTime == null ||
|
|
||||||
now.plusMinutes(REFRESH_BEFORE_EXPIRATION_MINUTES).isAfter(tokenExpirationTime)) {
|
|
||||||
|
|
||||||
log.info("Token 不存在或即将过期,正在刷新 WVP access token...");
|
|
||||||
WvpTokenClient.LoginResponse loginResponse = WvpTokenClient.login("admin", "admin");
|
|
||||||
|
|
||||||
if (loginResponse.getCode() == 0 && loginResponse.getData() != null) {
|
|
||||||
cachedAccessToken = loginResponse.getData().getAccessToken();
|
|
||||||
// 设置过期时间为当前时间 + TOKEN_EXPIRATION_MINUTES
|
|
||||||
tokenExpirationTime = now.plusMinutes(TOKEN_EXPIRATION_MINUTES);
|
|
||||||
log.info("WVP 登录成功,获取到 access token,过期时间: {}", tokenExpirationTime);
|
|
||||||
} else {
|
|
||||||
throw new Exception("WVP 登录失败: " + loginResponse.getMsg());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cachedAccessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除缓存的 access token(可用于 token 过期时重新获取)
|
|
||||||
*/
|
|
||||||
public static void clearCachedToken() {
|
|
||||||
synchronized (LOCK) {
|
|
||||||
cachedAccessToken = null;
|
|
||||||
tokenExpirationTime = null;
|
|
||||||
log.info("已清除缓存的 WVP access token");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置类
|
|
||||||
*/
|
|
||||||
public static class Config {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
package com.ruoyi.gateway.utils;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WVP Token 客户端
|
|
||||||
* 用于获取 WVP-PRO 的 access token
|
|
||||||
*/
|
|
||||||
public class WvpTokenClient {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(WvpTokenClient.class);
|
|
||||||
|
|
||||||
// WVP 服务地址 - 使用容器名称进行容器间通信
|
|
||||||
private static final String WVP_BASE_URL = "http://wvp-pro:18978";
|
|
||||||
|
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WVP登录响应对象
|
|
||||||
*/
|
|
||||||
public static class LoginResponse {
|
|
||||||
private int code;
|
|
||||||
private String msg;
|
|
||||||
private LoginData data;
|
|
||||||
|
|
||||||
public int getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCode(int code) {
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMsg() {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMsg(String msg) {
|
|
||||||
this.msg = msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginData getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(LoginData data) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录数据对象
|
|
||||||
*/
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
public static class LoginData {
|
|
||||||
private String accessToken;
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccessToken(String accessToken) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsername(String username) {
|
|
||||||
this.username = username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算MD5哈希值
|
|
||||||
*
|
|
||||||
* @param input 输入字符串
|
|
||||||
* @return MD5哈希值(小写)
|
|
||||||
* @throws Exception 如果计算失败
|
|
||||||
*/
|
|
||||||
private static String md5(String input) throws Exception {
|
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
||||||
byte[] messageDigest = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
|
||||||
StringBuilder hexString = new StringBuilder();
|
|
||||||
for (byte b : messageDigest) {
|
|
||||||
String hex = Integer.toHexString(0xff & b);
|
|
||||||
if (hex.length() == 1) {
|
|
||||||
hexString.append('0');
|
|
||||||
}
|
|
||||||
hexString.append(hex);
|
|
||||||
}
|
|
||||||
return hexString.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WVP用户登录
|
|
||||||
*
|
|
||||||
* @param username 用户名
|
|
||||||
* @param password 密码(明文)
|
|
||||||
* @return 登录响应对象
|
|
||||||
* @throws Exception 如果请求失败
|
|
||||||
*/
|
|
||||||
public static LoginResponse login(String username, String password) throws Exception {
|
|
||||||
// 计算密码的MD5值
|
|
||||||
String passwordMd5 = md5(password);
|
|
||||||
|
|
||||||
// 构建 URL 参数
|
|
||||||
StringBuilder urlBuilder = new StringBuilder(WVP_BASE_URL);
|
|
||||||
urlBuilder.append("/api/user/login?");
|
|
||||||
urlBuilder.append("username=").append(URLEncoder.encode(username, StandardCharsets.UTF_8.name()));
|
|
||||||
urlBuilder.append("&password=").append(passwordMd5);
|
|
||||||
|
|
||||||
// 创建 HTTP 连接
|
|
||||||
URL url = new URL(urlBuilder.toString());
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
||||||
connection.setRequestMethod("GET");
|
|
||||||
connection.setConnectTimeout(10000);
|
|
||||||
connection.setReadTimeout(10000);
|
|
||||||
|
|
||||||
// 获取响应
|
|
||||||
int responseCode = connection.getResponseCode();
|
|
||||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
|
||||||
BufferedReader reader = new BufferedReader(
|
|
||||||
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
|
|
||||||
StringBuilder response = new StringBuilder();
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
response.append(line);
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
|
|
||||||
// 解析JSON响应
|
|
||||||
return objectMapper.readValue(response.toString(), LoginResponse.class);
|
|
||||||
} else {
|
|
||||||
throw new Exception("HTTP 请求失败,状态码: " + responseCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
Spring Boot Version: ${spring-boot.version}
|
Spring Boot Version: ${spring-boot.version}
|
||||||
Spring Application Name: ${spring.application.name}
|
Spring Application Name: ${spring.application.name}
|
||||||
_ _
|
_ _
|
||||||
(_) | |
|
(_) | |
|
||||||
_ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _
|
_ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _
|
||||||
| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | |
|
| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | |
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue