zhibing.pu
2024-08-09 bed6becab65745585281ba2e499fa39de561c29f
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java
@@ -50,52 +50,41 @@
    // 跨站脚本的 xss 配置,nacos自行添加
    @Autowired
    private XssProperties xss;
    @Value("${security.sign}")
    private boolean parameter_signature;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // xss开关未开启 或 通过nacos关闭,不过滤
        if (!xss.getEnabled())
        {
        if (!xss.getEnabled()) {
            return chain.filter(exchange);
        }
        // GET DELETE 不过滤
        HttpMethod method = request.getMethod();
        if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE)
        {
        if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) {
            return chain.filter(exchange);
        }
        // 非json类型,不过滤
        if (!isJsonRequest(exchange))
        {
        if (!isJsonRequest(exchange)) {
            return chain.filter(exchange);
        }
        // excludeUrls 不过滤
        String url = request.getURI().getPath();
        if (StringUtils.matches(url, xss.getExcludeUrls()))
        {
        if (StringUtils.matches(url, xss.getExcludeUrls())) {
            return chain.filter(exchange);
        }
        ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange);
        if(parameter_signature && !authSign(httpRequestDecorator)){
            log.error("[鉴权签名异常处理]请求路径:{}", exchange.getRequest().getPath());
            return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "签名校验失败", HttpStatus.BAD_REQUEST);
        }
        return chain.filter(exchange.mutate().request(httpRequestDecorator).build());
    }
    private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange)
    {
        ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest())
        {
    private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) {
        ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public Flux<DataBuffer> getBody()
            {
            public Flux<DataBuffer> getBody() {
                Flux<DataBuffer> body = super.getBody();
                return body.buffer().map(dataBuffers -> {
                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
@@ -116,8 +105,7 @@
            }
            @Override
            public HttpHeaders getHeaders()
            {
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                // 由于修改了请求体的body,导致content-length长度不确定,因此需要删除原先的content-length
@@ -135,104 +123,10 @@
     * 
     * @param exchange HTTP请求
     */
    public boolean isJsonRequest(ServerWebExchange exchange)
    {
    public boolean isJsonRequest(ServerWebExchange exchange) {
        String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
        return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
    }
    /**
     * 签名校验
     * @param httpRequestDecorator
     * @return
     */
    private boolean authSign(ServerHttpRequestDecorator httpRequestDecorator) {
        HttpHeaders headers = httpRequestDecorator.getHeaders();
        AtomicReference<JSONObject> jsonObject = new AtomicReference<>(new JSONObject());
        httpRequestDecorator.getBody().buffer().map(dataBuffers -> {
            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
            DataBuffer join = dataBufferFactory.join(dataBuffers);
            byte[] content = new byte[join.readableByteCount()];
            join.read(content);
            DataBufferUtils.release(join);
            String bodyStr = new String(content, StandardCharsets.UTF_8);
            jsonObject.set(JSON.parseObject(bodyStr));
            // 防xss攻击过滤
            bodyStr = EscapeUtil.clean(bodyStr);
            // 转成字节
            byte[] bytes = bodyStr.getBytes();
            NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
            DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
            buffer.write(bytes);
            return buffer;
        });
        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()