spring-cloud-gateway网关鉴权总结

        最近公司的项目需要和某付宝进行接口对接,需要用到网关进行鉴权操作,并且需要把验证签名的操作也提取到网关层,减少代码的冗余。在开发过程中遇到了主要问题是:

        某付宝的接口虽然是使用post请求,但是参数是挂载在query上(http请求分为三处携带数据 header、query和body),这样的话当请求进入网关层会存在参数加密解密的问题,度娘了一圈没有发现类似情况,莫非只是我的特例嘛?.....然而当鉴权完毕转发到业务层处理时,又会涉及到加密解密的问题,对于参数中的 “=”、“空格”等会被识别为非法字符。

        折腾了好久,最后抱着试一试的态度竟然奇迹的成功了,期间出过各种坚决方案,但基本都是扶起来东墙,西墙又倒了。直接上代码先看下:

import com.alibaba.fastjson.JSON;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

/**
 * aikes 20201014
 * 网关过滤器
 */
@Component
public class AlipayFilterA implements GlobalFilter, Ordered {

    private static Logger logger = LoggerFactory.getLogger(AlipayFilterA.class);

    @Value("${sign.sign_type}")
    private String sign_type;
    @Value("${sign.app_id}")
    private String app_id;
    @Value("${sign.format}")
    private String format;
    @Value("${sign.charset}")
    private String charset;
    @Value("${sign.PUBLIC_KEY}")
    private String PUBLIC_KEY;
    @Value("${sign.PRIVATE_KEY}")
    private String PRIVATE_KEY;
    @Value("${sign.ALIPAY_PUBLIC_KEY}")
    private String ALIPAY_PUBLIC_KEY;
    @Value("${sign.version}")
    private String VERSION;

    @Override
    public int getOrder() {
        return -20;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse serverHttpResponse = exchange.getResponse();

        StringBuilder logBuilder = new StringBuilder();
        Map<String, String> params = parseRequest(exchange, logBuilder);
        logger.info("请求信息:" + logBuilder);
        Map<String, Object> tResult = checkSignature(params);//参数校验
        if (!(boolean) tResult.get("flag")) {
            logger.info("请求参数有误,访问被拒绝!");
            String resp = JSON.toJSONString(tResult.get("response"));
            logBuilder.append(",resp=").append(resp);
            DataBuffer bodyDataBuffer = serverHttpResponse.bufferFactory().wrap(resp.getBytes());
            serverHttpResponse.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
            return serverHttpResponse.writeWith(Mono.just(bodyDataBuffer));
        }
        DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        DataBufferUtils.release(dataBuffer);
                        String resp = new String(content, Charset.forName("UTF-8"));
                        logBuilder.append(",resp=").append(resp);
                        logger.info(logBuilder.toString());
                        byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                return super.writeWith(body);
            }
        };
        // 仅对请求参数加密处理
        URI uri = serverHttpRequest.getURI();
        StringBuilder query = new StringBuilder();
        params.forEach((k, v) -> {
            query.append(k);
            query.append("=");
            try {
                query.append(URLEncoder.encode(v, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            query.append("&");
        });
        // 替换参数
        URI newUri = UriComponentsBuilder.fromUri(uri)
                .replaceQuery(query.substring(0,query.length()-1))
                .build(true)
                .toUri();
        ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
        return chain.filter(exchange.mutate().request(request).build());
    }

    private Map<String,Object> checkSignature(Map<String, String> params) {
        //做参数校验
        return null;
    }

    private Map<String, String> parseRequest(ServerWebExchange exchange, StringBuilder logBuilder) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String method = serverHttpRequest.getMethodValue().toUpperCase();
        logBuilder.append(method).append(",").append(serverHttpRequest.getURI());
        MultiValueMap<String, String> query = serverHttpRequest.getQueryParams();
        logger.info("QueryParam 列表:" + JSON.toJSONString(query));
        Map<String, String> params = new HashMap<>();
        query.forEach((k, v) -> {             
            params.put(k, v.get(0));
        });
        return params;
    }
}

        当前版本也只是暂时的解决方案,后续还需要细化打磨。关于过滤器的基本知识和依赖配置等这里就不进行说明了,网上的大佬们有很多详细介绍,我选最核心的部分解释下问题处理思路,也就是下方这块在重写的filter方法中最下面的一段代码。

        // 仅对请求参数加密处理
        URI uri = serverHttpRequest.getURI();
        StringBuilder query = new StringBuilder();
        params.forEach((k, v) -> {
            query.append(k);
            query.append("=");
            try {
                query.append(URLEncoder.encode(v, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            query.append("&");
        });
        // 替换参数
        URI newUri = UriComponentsBuilder.fromUri(uri)
                .replaceQuery(query.substring(0,query.length()-1))
                .build(true)
                .toUri();
        ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
        return chain.filter(exchange.mutate().request(request).build());

        这段代码的主要作用就是针对http请求中query部分的参数进行加密操作,加密完成后,通过替换参数的方式获取到新的URI对象,然后再将生成的URI对象装载到copy出来的ServerHttpRequest对象中,最后通过过滤器链将请求流转下去。

        后续就会根据网关层的配置进行请求转发到对应的业务处理层的微服务中,匹配到对应的路径方法下,通过@RequestParam Map<String,Object> cMap 即可获取到所有参数(包括query部分和body部分),并且参数都是已经解密过的正常值。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aikes902

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值