Merge pull request 'feat(微服务治理工具): 登录状态异常拦截处理' (#834) from otto/microservices:third-party-tool-forward into third-party-tool-forward
This commit is contained in:
commit
63e9d65b60
|
@ -0,0 +1,17 @@
|
||||||
|
package com.microservices.common.core.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常信息常量
|
||||||
|
*
|
||||||
|
* @author otto
|
||||||
|
*/
|
||||||
|
public class ExceptionMsgConstants {
|
||||||
|
/**
|
||||||
|
* 平台通用异常
|
||||||
|
*/
|
||||||
|
public static final String SYSTEM_EXEC_ERROR = "请求处理异常,请联系管理员处理";
|
||||||
|
/**
|
||||||
|
* 重试异常
|
||||||
|
*/
|
||||||
|
public static final String RETRY_ERROR = "请求发生异常,请重新请求";
|
||||||
|
}
|
|
@ -90,6 +90,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
||||||
try {
|
try {
|
||||||
claims = JwtUtils.parseToken(token);
|
claims = JwtUtils.parseToken(token);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
log.error("令牌解析失败:{}", e.getMessage());
|
||||||
return unauthorizedResponse(exchange, "令牌格式不正确!");
|
return unauthorizedResponse(exchange, "令牌格式不正确!");
|
||||||
}
|
}
|
||||||
if (claims == null) {
|
if (claims == null) {
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
package com.microservices.gateway.filter;
|
package com.microservices.gateway.filter;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.microservices.common.core.constant.CacheConstants;
|
||||||
|
import com.microservices.common.core.constant.ExceptionMsgConstants;
|
||||||
|
import com.microservices.common.core.utils.StringUtils;
|
||||||
import com.microservices.common.core.web.domain.AjaxResult;
|
import com.microservices.common.core.web.domain.AjaxResult;
|
||||||
|
import com.microservices.common.redis.service.RedisService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
|
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
|
||||||
|
@ -12,6 +18,7 @@ import org.springframework.core.io.buffer.DataBuffer;
|
||||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
||||||
|
@ -25,16 +32,18 @@ import java.nio.charset.StandardCharsets;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class GlobalResponseFilter implements GlobalFilter, Ordered {
|
public class GlobalResponseFilter implements GlobalFilter, Ordered {
|
||||||
|
@Autowired
|
||||||
|
private RedisService redisService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
ServerHttpRequest request = exchange.getRequest();
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
|
|
||||||
String url = request.getURI().getPath();
|
String lowerUrl = request.getURI().getPath().toLowerCase();
|
||||||
|
ServerHttpResponse originalResponse = exchange.getResponse();
|
||||||
|
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
|
||||||
// 当请求为Nacos时,Nacos返回的所有非JSON响应都会被自动转换为结构化的错误信息,同时将HTTP状态码设置为200,适合用于规范化微服务架构的响应格式。
|
// 当请求为Nacos时,Nacos返回的所有非JSON响应都会被自动转换为结构化的错误信息,同时将HTTP状态码设置为200,适合用于规范化微服务架构的响应格式。
|
||||||
if (url.toLowerCase().startsWith("/nacos")) {
|
if (lowerUrl.startsWith("/nacos")) {
|
||||||
ServerHttpResponse originalResponse = exchange.getResponse();
|
|
||||||
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
|
|
||||||
|
|
||||||
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
|
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,6 +52,22 @@ public class GlobalResponseFilter implements GlobalFilter, Ordered {
|
||||||
if (!status.isError()) {
|
if (!status.isError()) {
|
||||||
return super.writeWith(body);
|
return super.writeWith(body);
|
||||||
}
|
}
|
||||||
|
if (status == HttpStatus.FORBIDDEN) {
|
||||||
|
log.error("nacos鉴权异常,已清理redis中nacos token");
|
||||||
|
// 设置新的状态码为 200
|
||||||
|
setStatusCode(HttpStatus.OK);
|
||||||
|
// 设置响应内容类型为 JSON
|
||||||
|
getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
// 清理Sentinel失效token
|
||||||
|
redisService.deleteObject(CacheConstants.NACOS_TOKEN);
|
||||||
|
// 创建自定义的 JSON 响应内容
|
||||||
|
String jsonBody = JSON.toJSONString(AjaxResult.error(ExceptionMsgConstants.RETRY_ERROR));
|
||||||
|
byte[] bytes = jsonBody.getBytes(StandardCharsets.UTF_8);
|
||||||
|
getHeaders().setContentLength(bytes.length);
|
||||||
|
DataBuffer dataBuffer = bufferFactory.wrap(bytes);
|
||||||
|
// 返回新的响应内容
|
||||||
|
return super.writeWith(Mono.just(dataBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
return Flux.from(body).<String>handle((dataBuffer, synchronousSink) -> {
|
return Flux.from(body).<String>handle((dataBuffer, synchronousSink) -> {
|
||||||
if (status.isError()) {
|
if (status.isError()) {
|
||||||
|
@ -71,6 +96,107 @@ public class GlobalResponseFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
return chain.filter(exchange.mutate().response(decoratedResponse).build());
|
return chain.filter(exchange.mutate().response(decoratedResponse).build());
|
||||||
}
|
}
|
||||||
|
if (lowerUrl.startsWith("/portainer")) {
|
||||||
|
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
|
||||||
|
@Override
|
||||||
|
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||||
|
HttpStatus status = getStatusCode() != null ? getStatusCode() : HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
if (!status.isError()) {
|
||||||
|
return super.writeWith(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Flux.from(body).<String>handle((dataBuffer, synchronousSink) -> {
|
||||||
|
if (status.isError()) {
|
||||||
|
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||||
|
dataBuffer.read(bytes);
|
||||||
|
//释放掉内存
|
||||||
|
DataBufferUtils.release(dataBuffer);
|
||||||
|
String result = new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
synchronousSink.next(result);
|
||||||
|
}
|
||||||
|
synchronousSink.complete();
|
||||||
|
}).flatMap(resStr -> {
|
||||||
|
log.error("Portainer响应处理异常,异常信息:{}", resStr);
|
||||||
|
originalResponse.setStatusCode(HttpStatus.OK);
|
||||||
|
byte[] bytes;
|
||||||
|
if (JSON.isValid(resStr)) {
|
||||||
|
JSONObject resJson = JSONObject.parseObject(resStr);
|
||||||
|
if (resJson.containsKey("message")) {
|
||||||
|
String message = resJson.getString("message");
|
||||||
|
log.error("Portainer鉴权异常,已清理redis中Portainer token");
|
||||||
|
if (StringUtils.isNotBlank(message) && message.contains("A valid authorisation token is missing")) {
|
||||||
|
//token失效清除redis缓存
|
||||||
|
redisService.deleteObject(CacheConstants.PORTAINER_TOKEN);
|
||||||
|
bytes = JSON.toJSONBytes(AjaxResult.error(ExceptionMsgConstants.RETRY_ERROR));
|
||||||
|
} else {
|
||||||
|
bytes = JSON.toJSONBytes(AjaxResult.error(message));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bytes = JSON.toJSONBytes(AjaxResult.error(ExceptionMsgConstants.SYSTEM_EXEC_ERROR));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bytes = JSON.toJSONBytes(AjaxResult.error(ExceptionMsgConstants.SYSTEM_EXEC_ERROR));
|
||||||
|
}
|
||||||
|
getHeaders().setContentLength(bytes.length);
|
||||||
|
DataBuffer buffer = bufferFactory.wrap(bytes);
|
||||||
|
return super.writeWith(Mono.just(buffer));
|
||||||
|
}).then();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return chain.filter(exchange.mutate().response(decoratedResponse).build());
|
||||||
|
}
|
||||||
|
if (lowerUrl.startsWith("/sentinel")) {
|
||||||
|
|
||||||
|
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
|
||||||
|
@Override
|
||||||
|
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||||
|
HttpStatus status = getStatusCode() != null ? getStatusCode() : HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
if (!status.isError()) {
|
||||||
|
return super.writeWith(body);
|
||||||
|
}
|
||||||
|
if (status == HttpStatus.UNAUTHORIZED) {
|
||||||
|
log.error("sentinel鉴权异常,已清理redis中sentinel token");
|
||||||
|
// 设置新的状态码为 200
|
||||||
|
setStatusCode(HttpStatus.OK);
|
||||||
|
// 设置响应内容类型为 JSON
|
||||||
|
getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
// 清理Sentinel失效token
|
||||||
|
redisService.deleteObject(CacheConstants.SENTINEL_TOKEN);
|
||||||
|
// 创建自定义的 JSON 响应内容
|
||||||
|
String jsonBody = JSON.toJSONString(AjaxResult.error(ExceptionMsgConstants.RETRY_ERROR));
|
||||||
|
byte[] bytes = jsonBody.getBytes(StandardCharsets.UTF_8);
|
||||||
|
getHeaders().setContentLength(bytes.length);
|
||||||
|
DataBuffer dataBuffer = bufferFactory.wrap(bytes);
|
||||||
|
// 返回新的响应内容
|
||||||
|
return super.writeWith(Mono.just(dataBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Flux.from(body).<String>handle((dataBuffer, synchronousSink) -> {
|
||||||
|
if (status.isError()) {
|
||||||
|
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||||
|
dataBuffer.read(bytes);
|
||||||
|
//释放掉内存
|
||||||
|
DataBufferUtils.release(dataBuffer);
|
||||||
|
String result = new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
synchronousSink.next(result);
|
||||||
|
}
|
||||||
|
synchronousSink.complete();
|
||||||
|
}).flatMap(resStr -> {
|
||||||
|
log.error("Sentinel响应处理异常,异常信息:{}", resStr);
|
||||||
|
byte[] bytes;
|
||||||
|
bytes = JSON.toJSONBytes(AjaxResult.error(ExceptionMsgConstants.SYSTEM_EXEC_ERROR));
|
||||||
|
setStatusCode(HttpStatus.OK);
|
||||||
|
|
||||||
|
getHeaders().setContentLength(bytes.length);
|
||||||
|
DataBuffer buffer = bufferFactory.wrap(bytes);
|
||||||
|
return super.writeWith(Mono.just(buffer));
|
||||||
|
}).then();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return chain.filter(exchange.mutate().response(decoratedResponse).build());
|
||||||
|
}
|
||||||
return chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.microservices.gateway.service.impl;
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.microservices.common.core.constant.CacheConstants;
|
import com.microservices.common.core.constant.CacheConstants;
|
||||||
|
import com.microservices.common.core.constant.ExceptionMsgConstants;
|
||||||
import com.microservices.common.core.constant.TokenConstants;
|
import com.microservices.common.core.constant.TokenConstants;
|
||||||
import com.microservices.common.core.exception.ServiceException;
|
import com.microservices.common.core.exception.ServiceException;
|
||||||
import com.microservices.common.core.utils.CookieUtil;
|
import com.microservices.common.core.utils.CookieUtil;
|
||||||
|
@ -60,12 +61,12 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> handleRequestForThirdPartyTools(ServerWebExchange exchange, ServerHttpRequest request, ServerHttpRequest.Builder mutate) {
|
public Mono<Void> handleRequestForThirdPartyTools(ServerWebExchange exchange, ServerHttpRequest request, ServerHttpRequest.Builder mutate) {
|
||||||
String url = request.getURI().getPath();
|
String lowerUrl = request.getURI().getPath().toLowerCase();
|
||||||
if (StringUtils.isEmpty(url)) {
|
if (StringUtils.isEmpty(lowerUrl)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// 处理Sentinel请求
|
// 处理Sentinel请求
|
||||||
if (url.toLowerCase().startsWith("/sentinel")) {
|
if (lowerUrl.startsWith("/sentinel")) {
|
||||||
// 检查Cookie中是否携带sentinel cookie
|
// 检查Cookie中是否携带sentinel cookie
|
||||||
String cookie = request.getHeaders().getFirst(TokenConstants.Cookie);
|
String cookie = request.getHeaders().getFirst(TokenConstants.Cookie);
|
||||||
if (StringUtils.isNotEmpty(CookieUtil.getCookieValue(cookie, TokenConstants.Sentinel_Token_Key))) {
|
if (StringUtils.isNotEmpty(CookieUtil.getCookieValue(cookie, TokenConstants.Sentinel_Token_Key))) {
|
||||||
|
@ -98,7 +99,7 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 处理Nacos请求
|
// 处理Nacos请求
|
||||||
if (url.toLowerCase().startsWith("/nacos")) {
|
if (lowerUrl.startsWith("/nacos")) {
|
||||||
String nacosToken = null;
|
String nacosToken = null;
|
||||||
if (redisService.hasKey(CacheConstants.NACOS_TOKEN)) {
|
if (redisService.hasKey(CacheConstants.NACOS_TOKEN)) {
|
||||||
nacosToken = redisService.getCacheObject(CacheConstants.NACOS_TOKEN);
|
nacosToken = redisService.getCacheObject(CacheConstants.NACOS_TOKEN);
|
||||||
|
@ -117,6 +118,9 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
|
||||||
return exceptionResponse(exchange, str, res.status());
|
return exceptionResponse(exchange, str, res.status());
|
||||||
}
|
}
|
||||||
JSONObject resJsonObject = JSONObject.parseObject(str);
|
JSONObject resJsonObject = JSONObject.parseObject(str);
|
||||||
|
if (resJsonObject == null) {
|
||||||
|
return exceptionResponse(exchange, str, res.status());
|
||||||
|
}
|
||||||
nacosToken = resJsonObject.getString("accessToken");
|
nacosToken = resJsonObject.getString("accessToken");
|
||||||
Long tokenTtl = resJsonObject.getLong("tokenTtl");
|
Long tokenTtl = resJsonObject.getLong("tokenTtl");
|
||||||
if (nacosToken != null && tokenTtl != null) {
|
if (nacosToken != null && tokenTtl != null) {
|
||||||
|
@ -136,7 +140,8 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理portainer请求
|
// 处理portainer请求
|
||||||
if (url.toLowerCase().startsWith("/portainer")) {
|
if (lowerUrl.startsWith("/portainer")) {
|
||||||
|
|
||||||
String portainerToken = null;
|
String portainerToken = null;
|
||||||
if (redisService.hasKey(CacheConstants.PORTAINER_TOKEN)) {
|
if (redisService.hasKey(CacheConstants.PORTAINER_TOKEN)) {
|
||||||
portainerToken = redisService.getCacheObject(CacheConstants.PORTAINER_TOKEN);
|
portainerToken = redisService.getCacheObject(CacheConstants.PORTAINER_TOKEN);
|
||||||
|
@ -156,6 +161,9 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
|
||||||
}
|
}
|
||||||
JSONObject resJsonObject = JSONObject.parseObject(str);
|
JSONObject resJsonObject = JSONObject.parseObject(str);
|
||||||
portainerToken = resJsonObject.getString("jwt");
|
portainerToken = resJsonObject.getString("jwt");
|
||||||
|
if (portainerToken == null) {
|
||||||
|
return exceptionResponse(exchange, str, res.status());
|
||||||
|
}
|
||||||
String[] tokenSplit = portainerToken.split("\\.");
|
String[] tokenSplit = portainerToken.split("\\.");
|
||||||
String tokenBodyStr = new String(Base64.getDecoder().decode(tokenSplit[1]), StandardCharsets.UTF_8);
|
String tokenBodyStr = new String(Base64.getDecoder().decode(tokenSplit[1]), StandardCharsets.UTF_8);
|
||||||
JSONObject tokenBody = JSONObject.parseObject(tokenBodyStr);
|
JSONObject tokenBody = JSONObject.parseObject(tokenBodyStr);
|
||||||
|
@ -183,7 +191,7 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
|
||||||
|
|
||||||
|
|
||||||
private Mono<Void> exceptionResponse(ServerWebExchange exchange, String msg, int code) {
|
private Mono<Void> exceptionResponse(ServerWebExchange exchange, String msg, int code) {
|
||||||
log.error("[第三方工具请求处理异常]请求路径:{}", exchange.getRequest().getPath());
|
log.error("[第三方工具请求处理异常]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), msg);
|
||||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, code);
|
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), ExceptionMsgConstants.SYSTEM_EXEC_ERROR, code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue