| 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<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 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<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) { | 
|             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; | 
|     } | 
| } |