package com.ruoyi.gateway.filter;
|
|
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.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.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 sing = request.getHeaders().getFirst(TokenConstants.SING);
|
String nonce_str = request.getHeaders().getFirst(TokenConstants.NONCE_STR);
|
if (parameter_signature && StringUtils.isEmpty(sing)) {
|
return unauthorizedResponse(exchange, "签名不能为空!");
|
}
|
if (parameter_signature && StringUtils.isEmpty(nonce_str)) {
|
return unauthorizedResponse(exchange, "签名不能为空!");
|
}
|
if(parameter_signature && !authSign(exchange)){
|
return unauthorizedResponse(exchange, "签名不通过!");
|
}
|
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);
|
}
|
|
|
/**
|
* 校验签名
|
* @param exchange
|
* @return
|
*/
|
private boolean authSign(ServerWebExchange exchange){
|
return false;
|
}
|
|
|
|
/**
|
* 签名校验
|
* @param httpRequestDecorator
|
* @return
|
*/
|
private boolean authSign(ServerHttpRequestDecorator httpRequestDecorator) {
|
HttpHeaders headers = httpRequestDecorator.getHeaders();
|
Flux<DataBuffer> body = httpRequestDecorator.getBody();
|
AtomicReference<JSONObject> jsonObject = new AtomicReference<>(new JSONObject());
|
httpRequestDecorator.getBody().map(dataBuffers -> dataBuffers.toString());
|
JSONObject params = jsonObject.get();
|
String sign = headers.getFirst(TokenConstants.SING);
|
if(StringUtils.isEmpty(sign)){
|
return false;
|
}
|
String nonce_str = headers.getFirst(TokenConstants.NONCE_STR);
|
if(StringUtils.isEmpty(nonce_str)){
|
return false;
|
}
|
|
String signUrlEncode = localSignUrl(params, nonce_str);
|
signUrlEncode = signUrlEncode.replaceAll("& #40;", "\\(")
|
.replaceAll("& #41;", "\\)")
|
.replaceAll("\\+", " ");
|
if(sign.equals(signUrlEncode)){
|
return true;
|
}
|
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);
|
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) {
|
throw new RuntimeException(e);
|
}
|
String localSign = Base64.encodeBase64String(signByte);
|
return localSign;
|
}
|
|
|
@Override
|
public int getOrder()
|
{
|
return -200;
|
}
|
}
|