|
|
|
|
@@ -1,28 +1,169 @@
|
|
|
|
|
package cn.meowrain.aioj.backend.gateway.filter;
|
|
|
|
|
|
|
|
|
|
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
|
|
|
|
import cn.meowrain.aioj.backend.framework.core.exception.RemoteException;
|
|
|
|
|
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
|
|
|
|
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
|
|
|
|
import cn.meowrain.aioj.backend.gateway.config.GatewayPropertiesConfiguration;
|
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
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.http.HttpHeaders;
|
|
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
|
|
import org.springframework.http.MediaType;
|
|
|
|
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
|
|
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
|
|
|
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
import org.springframework.util.AntPathMatcher;
|
|
|
|
|
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.util.List;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
@Component
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
|
|
|
|
|
|
|
|
|
private final WebClient.Builder webClientBuilder;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private GatewayPropertiesConfiguration gatewayPropertiesConfiguration;
|
|
|
|
|
|
|
|
|
|
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
|
|
|
|
|
|
|
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 不需要认证的路径
|
|
|
|
|
*/
|
|
|
|
|
private static final String[] DEFAULT_WHITE_LIST = {
|
|
|
|
|
"/api/v1/auth/login",
|
|
|
|
|
"/api/v1/auth/register",
|
|
|
|
|
"/api/v1/auth/refresh",
|
|
|
|
|
"/api/v1/user/info"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
|
|
|
return null;
|
|
|
|
|
ServerHttpRequest request = exchange.getRequest();
|
|
|
|
|
String path = request.getURI().getPath();
|
|
|
|
|
|
|
|
|
|
log.info("Auth filter processing request: {}", path);
|
|
|
|
|
|
|
|
|
|
// 检查是否在白名单中
|
|
|
|
|
if (isWhiteListPath(path)) {
|
|
|
|
|
log.info("Path {} is in whitelist, skip authentication", path);
|
|
|
|
|
return chain.filter(exchange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取Authorization头
|
|
|
|
|
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
|
|
|
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
|
|
|
|
log.warn("No valid authorization header found for path: {}", path);
|
|
|
|
|
return handleUnauthorized(exchange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String token = authHeader.substring(7);
|
|
|
|
|
|
|
|
|
|
// 调用auth服务验证token
|
|
|
|
|
return validateToken(token)
|
|
|
|
|
.flatMap(isValid -> {
|
|
|
|
|
if (isValid) {
|
|
|
|
|
log.info("Token validation successful for path: {}", path);
|
|
|
|
|
return chain.filter(exchange);
|
|
|
|
|
} else {
|
|
|
|
|
log.warn("Token validation failed for path: {}", path);
|
|
|
|
|
return handleUnauthorized(exchange);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.onErrorResume(throwable -> {
|
|
|
|
|
log.error("Token validation error for path: {}", path, throwable);
|
|
|
|
|
return handleUnauthorized(exchange);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查路径是否在白名单中
|
|
|
|
|
*/
|
|
|
|
|
private boolean isWhiteListPath(String path) {
|
|
|
|
|
// 先检查默认白名单
|
|
|
|
|
for (String whitePath : DEFAULT_WHITE_LIST) {
|
|
|
|
|
if (antPathMatcher.match(whitePath, path)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查配置文件中的白名单
|
|
|
|
|
if (gatewayPropertiesConfiguration.getWhiteList() != null && !gatewayPropertiesConfiguration.getWhiteList().isEmpty()) {
|
|
|
|
|
for (String whitePath : gatewayPropertiesConfiguration.getWhiteList()) {
|
|
|
|
|
if (antPathMatcher.match(whitePath, path)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 调用auth服务验证token
|
|
|
|
|
*/
|
|
|
|
|
private Mono<Boolean> validateToken(String token) {
|
|
|
|
|
return webClientBuilder.build()
|
|
|
|
|
.post()
|
|
|
|
|
.uri("lb://auth-service/api/v1/auth/validate")
|
|
|
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.retrieve()
|
|
|
|
|
.bodyToMono(String.class)
|
|
|
|
|
.map(response -> {
|
|
|
|
|
try {
|
|
|
|
|
// 解析响应,判断是否有效
|
|
|
|
|
Result result = objectMapper.readValue(response, Result.class);
|
|
|
|
|
return Objects.equals(result.getCode(), Result.SUCCESS_CODE);
|
|
|
|
|
} catch (JsonProcessingException e) {
|
|
|
|
|
log.error("Failed to parse auth response", e);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.defaultIfEmpty(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理未授权的请求
|
|
|
|
|
*/
|
|
|
|
|
private Mono<Void> handleUnauthorized(ServerWebExchange exchange) {
|
|
|
|
|
ServerHttpResponse response = exchange.getResponse();
|
|
|
|
|
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
|
|
|
|
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
|
|
|
|
|
|
Result<Void> result = Results.failure(new RemoteException(ErrorCode.NO_AUTH_ERROR));
|
|
|
|
|
String responseBody;
|
|
|
|
|
try {
|
|
|
|
|
responseBody = objectMapper.writeValueAsString(result);
|
|
|
|
|
} catch (JsonProcessingException e) {
|
|
|
|
|
responseBody = "{\"code\":401,\"message\":\"Unauthorized\",\"data\":null}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DataBuffer buffer = response.bufferFactory().wrap(responseBody.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
return response.writeWith(Mono.just(buffer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getOrder() {
|
|
|
|
|
return 0;
|
|
|
|
|
// 设置较高的优先级,确保在其他过滤器之前执行
|
|
|
|
|
return -100;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|