package com.ruoyi.gateway.filter; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.core.constant.HttpStatus; import com.ruoyi.common.core.constant.TokenConstants; import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.gateway.config.properties.IgnoreWhiteProperties; import com.ruoyi.gateway.config.properties.SignProperties; 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.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.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; /** * 网关鉴权 * * @author ruoyi */ @Component public class SignFilter implements GlobalFilter, Ordered { private static final Logger log = LoggerFactory.getLogger(SignFilter.class); @Autowired private IgnoreWhiteProperties ignoreWhite; @Autowired private SignProperties signProperties; @Override public Mono 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 url = request.getURI().getPath(); if (StringUtils.matches(url, ignoreWhite.getWhites())) { return chain.filter(exchange); } String sign = request.getHeaders().getFirst(TokenConstants.SIGN); String nonce_str = request.getHeaders().getFirst(TokenConstants.NONCE_STR); if (signProperties.getEnable() && StringUtils.isEmpty(sign)) { return unauthorizedResponse(exchange, "签名不能为空!"); } if (signProperties.getEnable() && StringUtils.isEmpty(nonce_str)) { return unauthorizedResponse(exchange, "签名不能为空!"); } if(signProperties.getEnable()){ return authSign(exchange, chain, sign, nonce_str); } return chain.filter(exchange.mutate().request(mutate.build()).build()); } private Mono unauthorizedResponse(ServerWebExchange exchange, String msg) { log.error("[签名异常处理]请求路径:{}", exchange.getRequest().getPath()); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.BAD_REQUEST); } /** * 校验签名 * @return */ private Mono 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 cachedFlux = Flux.defer(() -> { DataBuffer buffer = exchange.getResponse().bufferFactory() .wrap(bytes); return Mono.just(buffer); }); ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux 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 keySet = new ArrayList<>(params.keySet()); // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) Collections.sort(keySet, new Comparator() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); // 构造签名键值对的格式 StringBuilder sb = new StringBuilder(); for (String k : keySet) { Object o = params.get(k); if(o instanceof JSONObject || o instanceof JSONArray){ continue; } String v = params.getString(k); if(StringUtils.isNotEmpty(v)){ sb.append(k + "=" + v + "&"); } } String signUrl = ""; if(sb.length() != 0){ 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; } }