package com.ruoyi.gateway.filter;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONObject;
|
import com.ruoyi.common.core.constant.CacheConstants;
|
import com.ruoyi.common.core.constant.HttpStatus;
|
import com.ruoyi.common.core.constant.SecurityConstants;
|
import com.ruoyi.common.core.constant.TokenConstants;
|
import com.ruoyi.common.core.utils.JwtUtils;
|
import com.ruoyi.common.core.utils.ServletUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.redis.service.RedisService;
|
import com.ruoyi.gateway.config.properties.IgnoreWhiteProperties;
|
import io.jsonwebtoken.Claims;
|
import org.apache.commons.codec.binary.Base64;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Value;
|
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.core.io.buffer.DataBufferUtils;
|
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpMethod;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
import org.springframework.stereotype.Component;
|
import org.springframework.web.server.ServerWebExchange;
|
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Mono;
|
|
import java.nio.charset.StandardCharsets;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.List;
|
import java.util.concurrent.atomic.AtomicReference;
|
|
/**
|
* 网关鉴权
|
*
|
* @author ruoyi
|
*/
|
@Component
|
public class SignFilter implements GlobalFilter, Ordered {
|
private static final Logger log = LoggerFactory.getLogger(SignFilter.class);
|
|
// 排除过滤的 uri 地址,nacos自行添加
|
@Autowired
|
private IgnoreWhiteProperties ignoreWhite;
|
|
@Autowired
|
private RedisService redisService;
|
|
@Value("${security.sign}")
|
private boolean parameter_signature;
|
|
|
@Override
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
ServerHttpRequest request = exchange.getRequest();
|
ServerHttpRequest.Builder mutate = request.mutate();
|
|
HttpMethod method = request.getMethod();
|
if(method != HttpMethod.POST){
|
return chain.filter(exchange.mutate().request(mutate.build()).build());
|
}
|
String sign = request.getHeaders().getFirst(TokenConstants.SIGN);
|
String nonce_str = request.getHeaders().getFirst(TokenConstants.NONCE_STR);
|
if (parameter_signature && StringUtils.isEmpty(sign)) {
|
return unauthorizedResponse(exchange, "签名不能为空!");
|
}
|
if (parameter_signature && StringUtils.isEmpty(nonce_str)) {
|
return unauthorizedResponse(exchange, "签名不能为空!");
|
}
|
if(parameter_signature){
|
return authSign(exchange, chain, sign, nonce_str);
|
}
|
return chain.filter(exchange.mutate().request(mutate.build()).build());
|
}
|
|
|
|
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
|
log.error("[签名异常处理]请求路径:{}", exchange.getRequest().getPath());
|
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.BAD_REQUEST);
|
}
|
|
|
/**
|
* 校验签名
|
* @return
|
*/
|
private Mono<Void> authSign(ServerWebExchange exchange, GatewayFilterChain chain, String sign, String nonce_str){
|
return DataBufferUtils.join(exchange.getRequest().getBody())
|
.flatMap(dataBuffer -> {
|
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
dataBuffer.read(bytes);
|
String bodyString = new String(bytes, StandardCharsets.UTF_8);
|
log.info("请求参数:{}", bodyString);
|
if(!authSign(JSON.parseObject(bodyString), sign, nonce_str)){
|
return unauthorizedResponse(exchange, "签名验证失败!");
|
}
|
DataBufferUtils.release(dataBuffer);
|
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
|
DataBuffer buffer = exchange.getResponse().bufferFactory()
|
.wrap(bytes);
|
return Mono.just(buffer);
|
});
|
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
|
@Override
|
public Flux<DataBuffer> getBody() {
|
return cachedFlux;
|
}
|
};
|
return chain.filter(exchange.mutate().request(mutatedRequest)
|
.build());
|
});
|
}
|
|
|
|
/**
|
* 签名校验
|
* @return
|
*/
|
private boolean authSign(JSONObject jsonStr, String sign, String nonce_str) {
|
System.err.println("请求签名:" + sign);
|
String signUrlEncode = localSignUrl(jsonStr, nonce_str);
|
signUrlEncode = signUrlEncode.replaceAll("& #40;", "\\(")
|
.replaceAll("& #41;", "\\)");
|
if(sign.equals(signUrlEncode)){
|
return true;
|
}
|
System.err.println("签名值:" + signUrlEncode);
|
return false;
|
}
|
|
|
/**
|
* 组装签名路径
|
* @param params
|
* @return
|
*/
|
public static String localSignUrl(JSONObject params, String key) {
|
List<String> keySet = new ArrayList<>(params.keySet());
|
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
|
Collections.sort(keySet, new Comparator<String>() {
|
@Override
|
public int compare(String o1, String o2) {
|
return o1.compareTo(o2);
|
}
|
});
|
// 构造签名键值对的格式
|
StringBuilder sb = new StringBuilder();
|
for (String k : keySet) {
|
String v = params.getString(k);
|
if(StringUtils.isNotEmpty(v)){
|
sb.append(k + "=" + v + "&");
|
}
|
}
|
String signUrl = sb.substring(0, sb.length() - 1);
|
System.err.println("签名串:" + signUrl);
|
return signUrlEncode(signUrl, key);
|
}
|
|
|
/**
|
* 签名字符串加密
|
* @param signUrl
|
* @param encryptKey
|
* @return
|
*/
|
public static String signUrlEncode(String signUrl, String encryptKey) {
|
byte[] signByte = new byte[0];
|
try {
|
signByte = HMACSHA1.HmacSHA1Encrypt(signUrl, encryptKey);
|
} catch (Exception e) {
|
e.printStackTrace();
|
throw new RuntimeException(e);
|
}
|
String localSign = Base64.encodeBase64String(signByte);
|
return localSign;
|
}
|
|
|
@Override
|
public int getOrder()
|
{
|
return -200;
|
}
|
}
|