SpringCloud Gateway API接口安全设计(加密 、签名、安全)(二)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,经济版 1GB 1个月
简介: SpringCloud Gateway API接口安全设计(加密 、签名、安全)(二)
  • 确保URL唯一性
  • 修改前端请求参数
  • 后端增加验证RequestId
  • 增加签名
  • 增强读取Body类
  • 修改GatewayFilterConfig
  • 测试登录
  • 测试查询
  • 地址

确保URL唯一性

确保URL唯一性,前端请求中增加UUID,后端存入redis,有效时长为5分钟,5分钟重复提交拒绝服务

修改前端请求参数

微信图片_20220906153752.png

后端增加验证RequestId

微信图片_20220906153829.png

private String getRequestId(HttpHeaders headers) {
        List<String> list = headers.get("requestId");
        if (CollectionUtils.isEmpty(list)) {
            throw new IllegalArgumentException(ERROR_MESSAGE);
        }
        String requestId = list.get(0);
        //如果requestId存在redis中直接返回
        String temp = redisTemplate.opsForValue().get(requestId);
        if (StringUtils.isNotBlank(temp)) {
            throw new IllegalArgumentException(ERROR_MESSAGE);
        }
        redisTemplate.opsForValue().set(requestId, requestId, 5, TimeUnit.MINUTES);
        return requestId;
    }

增加签名

最后一步,添加签名

前端增加签名

跟前端约定好,json数据按照ASCII升序排序。

登录页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<h1>登录</h1>
<from id="from">
    账号:<input id="username" type="text"/>
    <br/>
    密码:<input id="password" type="password"/>
    <br/>
    <input id="btn_login" type="button" value="登录"/>
</from>
<script src="js/jquery.min.js"></script>
<script src="js/jsencrypt.js"></script>
<script src="js/md5.min.js"></script>
<script type="text/javascript">
    var encrypt = new JSEncrypt();
    encrypt.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB");
    $("#btn_login").click(function () {
        //表单
        const username = $("#username").val();
        const password = $("#password").val();
        const form = {};
        form.username = username;
        form.password = password;
        //生成签名,也可以加盐
        const timestamp = Date.parse(new Date());
        const data = JSON.stringify(sort_ASCII(form));
        const requestId = getUuid();
        const sign = MD5(data + requestId + timestamp);
        $.ajax({
            url: "http://localhost:9000/api/user/login",
            beforeSend: function (XMLHttpRequest) {
                XMLHttpRequest.setRequestHeader("timestamp", timestamp);
                XMLHttpRequest.setRequestHeader("requestId", requestId);
                XMLHttpRequest.setRequestHeader("sign", sign);
            },
            data: encrypt.encrypt(data),
            type: "POST",
            dataType: "json",
            contentType: "application/json;charset=utf-8",
            success: function (data) {
                console.log(data);
            }
        });
    });
    function getUuid() {
        var s = [];
        var hexDigits = "0123456789abcdef";
        for (var i = 0; i < 32; i++) {
            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
        }
        s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
        s[8] = s[13] = s[18] = s[23];
        var uuid = s.join("");
        return uuid;
    }
    function sort_ASCII(obj) {
        var arr = new Array();
        var num = 0;
        for (var i in obj) {
            arr[num] = i;
            num++;
        }
        var sortArr = arr.sort();
        var sortObj = {};
        for (var i in sortArr) {
            sortObj[sortArr[i]] = obj[sortArr[i]];
        }
        return sortObj;
    }
</script>
</body>
</html>

增强读取Body类

/**
 * @Description:
 * @Author: Rosh
 * @Date: 2021/10/27 11:03
 */
public class MyCachedBodyOutputMessage extends CachedBodyOutputMessage {
    private Map<String, Object> paramMap;
    private Long dateTimestamp;
    private String requestId;
    private String sign;
    public MyCachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {
        super(exchange, httpHeaders);
    }
    public void initial(Map<String, Object> paramMap, String requestId, String sign, Long dateTimestamp) {
        this.paramMap = paramMap;
        this.requestId = requestId;
        this.sign = sign;
        this.dateTimestamp = dateTimestamp;
    }
    public Map<String, Object> getParamMap() {
        return paramMap;
    }
    public Long getDateTimestamp() {
        return dateTimestamp;
    }
    public String getRequestId() {
        return requestId;
    }
    public String getSign() {
        return sign;
    }
}

修改GatewayFilterConfig

package com.demo.gateway.config;
public class GatewayFilterConfig implements GlobalFilter, Ordered {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String ERROR_MESSAGE = "拒绝服务";
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1 获取时间戳
        Long dateTimestamp = getDateTimestamp(exchange.getRequest().getHeaders());
        //2 获取RequestId
        String requestId = getRequestId(exchange.getRequest().getHeaders());
        //3 获取签名
        String sign = getSign(exchange.getRequest().getHeaders());
        //4 如果是登录不校验Token
        String requestUrl = exchange.getRequest().getPath().value();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        if (!pathMatcher.match("/user/login", requestUrl)) {
            String token = exchange.getRequest().getHeaders().getFirst(UserConstant.TOKEN);
            Claims claim = TokenUtils.getClaim(token);
            if (StringUtils.isBlank(token) || claim == null) {
                return FilterUtils.invalidToken(exchange);
            }
        }
        //5 修改请求参数,并获取请求参数
        Map<String, Object> paramMap;
        try {
            paramMap = updateRequestParam(exchange);
        } catch (Exception e) {
            return FilterUtils.invalidUrl(exchange);
        }
        //6 获取请求体,修改请求体
        ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            String encrypt = RSAUtils.decrypt(body, RSAConstant.PRIVATE_KEY);
            JSONObject jsonObject = JSON.parseObject(encrypt);
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                paramMap.put(entry.getKey(), entry.getValue());
            }
            checkSign(sign, dateTimestamp, requestId, paramMap);
            return Mono.just(encrypt);
        });
        //创建BodyInserter修改请求体
        BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        //创建CachedBodyOutputMessage并且把请求param加入,初始化校验信息
        MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers);
        outputMessage.initial(paramMap, requestId, sign, dateTimestamp);
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    Flux<DataBuffer> body = outputMessage.getBody();
                    if (body.equals(Flux.empty())) {
                        //验证签名
                        checkSign(outputMessage.getSign(), outputMessage.getDateTimestamp(), outputMessage.getRequestId(), outputMessage.getParamMap());
                    }
                    return outputMessage.getBody();
                }
            };
            return chain.filter(exchange.mutate().request(decorator).build());
        }));
    }
    public void checkSign(String sign, Long dateTimestamp, String requestId, Map<String, Object> paramMap) {
        String str = JSON.toJSONString(paramMap) + requestId + dateTimestamp;
        String tempSign = Md5Utils.getMD5(str.getBytes());
        if (!tempSign.equals(sign)) {
            throw new IllegalArgumentException(ERROR_MESSAGE);
        }
    }
    /**
     * 修改前端传的参数
     */
    private Map<String, Object> updateRequestParam(ServerWebExchange exchange) throws NoSuchFieldException, IllegalAccessException {
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        String query = uri.getQuery();
        if (StringUtils.isNotBlank(query) && query.contains("param")) {
            String[] split = query.split("=");
            String param = RSAUtils.decrypt(split[1], RSAConstant.PRIVATE_KEY);
            Field targetQuery = uri.getClass().getDeclaredField("query");
            targetQuery.setAccessible(true);
            targetQuery.set(uri, param);
            return getParamMap(param);
        }
        return new TreeMap<>();
    }
    private Map<String, Object> getParamMap(String param) {
        Map<String, Object> map = new TreeMap<>();
        String[] split = param.split("&");
        for (String str : split) {
            String[] params = str.split("=");
            map.put(params[0], params[1]);
        }
        return map;
    }
    private String getSign(HttpHeaders headers) {
        List<String> list = headers.get("sign");
        if (CollectionUtils.isEmpty(list)) {
            throw new IllegalArgumentException(ERROR_MESSAGE);
        }
        return list.get(0);
    }
    private Long getDateTimestamp(HttpHeaders httpHeaders) {
        List<String> list = httpHeaders.get("timestamp");
        if (CollectionUtils.isEmpty(list)) {
            throw new IllegalArgumentException(ERROR_MESSAGE);
        }
        long timestamp = Long.parseLong(list.get(0));
        long currentTimeMillis = System.currentTimeMillis();
        //有效时长为5分钟
        if (currentTimeMillis - timestamp > 1000 * 60 * 5) {
            throw new IllegalArgumentException(ERROR_MESSAGE);
        }
        return timestamp;
    }
    private String getRequestId(HttpHeaders headers) {
        List<String> list = headers.get("requestId");
        if (CollectionUtils.isEmpty(list)) {
            throw new IllegalArgumentException(ERROR_MESSAGE);
        }
        String requestId = list.get(0);
        //如果requestId存在redis中直接返回
        String temp = redisTemplate.opsForValue().get(requestId);
        if (StringUtils.isNotBlank(temp)) {
            throw new IllegalArgumentException(ERROR_MESSAGE);
        }
        redisTemplate.opsForValue().set(requestId, requestId, 5, TimeUnit.MINUTES);
        return requestId;
    }
    @Override
    public int getOrder() {
        return 80;
    }
}

测试登录

发现验签成功

微信图片_20220906153953.png

地址

https://gitee.com/zhurongsheng/springcloud-gateway-rsa

相关文章
|
2月前
|
供应链 安全 物联网
【接口加密】接口加密的未来发展与应用场景
【接口加密】接口加密的未来发展与应用场景
|
2月前
|
存储 安全 算法
【接口加密】Java中的接口加密实践
【接口加密】Java中的接口加密实践
|
2月前
|
安全 数据安全/隐私保护
【接口加密】理解接口加密的基础概念
【接口加密】理解接口加密的基础概念
|
2月前
|
JSON 机器人 数据安全/隐私保护
钉钉中,如何获取机器人发送群聊消息接口返回的加密消息id(processQueryKey)?
钉钉中,如何获取机器人发送群聊消息接口返回的加密消息id(processQueryKey)?【1月更文挑战第5天】【1月更文挑战第24篇】
106 5
|
21天前
|
Go 数据安全/隐私保护
go 基于gin编写encode、decode、base64加密接口
go 基于gin编写encode、decode、base64加密接口
15 2
|
2天前
|
Prometheus Kubernetes Cloud Native
云原生周刊:Argo Rollouts 支持 Kubernetes Gateway API 1.0 | 2024.7.1
探索开源世界:Kubetools的推荐系统[Krs](https://github.com/kubetoolsca/krs)助力K8s优化,追踪K8s组件清单,指引IAC集成。阅读建议: Prometheus与Thanos的进化故事,Adidas容器平台管理经验,K8s请求实现详解。关注云原生:Argo Rollouts支持Gateway API 1.0,Kubewarden v1.14强化策略与镜像安全。
|
2天前
|
API 开发工具
支付系统17------支付宝支付-----API预览以及签名验签说明,出现支付宝扫描二维码的操作,支付完成之后,查询订单的状态,支付成功之后,需要退款调用的接口,退款状态的接口,完成退款之后,通知
支付系统17------支付宝支付-----API预览以及签名验签说明,出现支付宝扫描二维码的操作,支付完成之后,查询订单的状态,支付成功之后,需要退款调用的接口,退款状态的接口,完成退款之后,通知
|
7天前
|
Java API 开发者
Spring Cloud Gateway中的GlobalFilter:构建强大的API网关过滤器
Spring Cloud Gateway中的GlobalFilter:构建强大的API网关过滤器
14 0
|
2月前
|
API 数据安全/隐私保护
单页源码加密屋zip文件加密API源码
单页源码加密屋zip文件加密API源码 api源码里面的参数已改好,往服务器或主机一丢就行,出现不能加密了就是加密次数达到上限了,告诉我在到后台修改加密次数
29 1
|
2月前
|
监控 Cloud Native 安全
【阿里云云原生专栏】云原生下的API管理:阿里云API Gateway的应用场景与优势
【5月更文挑战第23天】阿里云API Gateway是高性能的API托管服务,适用于微服务API聚合、安全管理及流量控制。它提供统一入口、多种认证方式和流量控制策略,确保服务稳定性。具备高度可扩展性、丰富插件生态和简化API生命周期管理等特点。通过简单步骤,如创建API、配置后端服务、设置认证和发布,即可快速上手。作为云原生时代的API管理解决方案,阿里云API Gateway助力企业高效、安全地管理API,推动业务创新和数字化转型。
47 1