返回顶部
首页 > 资讯 > 后端开发 > Python >详解SpringBoot是如何保证接口安全的
  • 538
分享到

详解SpringBoot是如何保证接口安全的

SpringBoot保证接口安全SpringBoot 接口安全 2023-02-02 12:02:09 538人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录为什么要保证接口安全防篡改如何解决防重放基于timestamp的方案基于nonce + timestamp 的方案代码实现1、构建请求头对象2、工具类从httpservletRe

为什么要保证接口安全

对于互联网来说,只要你系统的接口会暴露在外网,就避免不了接口安全问题。 如果你的接口在外网裸奔,只要让黑客知道接口的地址和参数就可以调用,那简直就是灾难。

举个例子:你的网站用户注册的时候,需要填写手机号,发送手机验证码,如果这个发送验证码的接口没有经过特殊安全处理,那这个短信接口早就被人盗刷不知道浪费多少钱了。

那如何保证接口安全呢?

一般来说,暴露在外网的api接口需要做到防篡改防重放才能称之为安全的接口。

防篡改

我们知道http 是一种无状态的协议,服务端并不知道客户端发送的请求是否合法,也并不知道请求中的参数是否正确。

举个例子, 现在有个充值的接口,调用后可以给用户增加对应的余额。

http://localhost/api/user/recharge?user_id=1001&amount=10

如果非法用户通过抓包获取到接口参数后,修改user_id 或 amount的值就可以实现给任意账户添加余额的目的。

如何解决

采用https协议可以将传输的明文进行加密,但是黑客仍然可以截获传输的数据包,进一步伪造请求进行重放攻击。如果黑客使用特殊手段让请求方设备使用了伪造的证书进行通信,那么https加密的内容也会被解密。

一般的做法有2种:

  • 采用https方式把接口的数据进行加密传输,即便是被黑客破解,黑客也花费大量的时间和精力去破解。
  • 接口后台对接口的请求参数进行验证,防止被黑客篡改;

  • 步骤1:客户端使用约定好的秘钥对传输的参数进行加密,得到签名值sign1,并且将签名值也放入请求的参数中,发送请求给服务端
  • 步骤2:服务端接收到客户端的请求,然后使用约定好的秘钥对请求的参数再次进行签名,得到签名值sign2。
  • 步骤3:服务端比对sign1和sign2的值,如果不一致,就认定为被篡改,非法请求。

防重放

防重放也叫防复用。简单来说就是我获取到这个请求的信息之后什么也不改,,直接拿着接口的参数去 重复请求这个充值的接口。此时我的请求是合法的, 因为所有参数都是跟合法请求一模一样的。重放攻击会造成两种后果:

  • 针对插入数据库接口:重放攻击,会出现大量重复数据,甚至垃圾数据会把数据库撑爆。
  • 针对查询的接口:黑客一般是重点攻击慢查询接口,例如一个慢查询接口1s,只要黑客发起重放攻击,就必然造成系统被拖垮,数据库查询被阻塞死。

对于重放攻击一般有两种做法:

基于timestamp的方案

每次HTTP请求,都需要加上timestamp参数,然后把timestamp和其他参数一起进行数字签名。因为一次正常的HTTP请求,从发出到达服务器一般都不会超过60s,所以服务器收到HTTP请求之后,首先判断时间戳参数与当前时间比较,是否超过了60s,如果超过了则认为是非法请求。

一般情况下,黑客从抓包重放请求耗时远远超过了60s,所以此时请求中的timestamp参数已经失效了。 如果黑客修改timestamp参数为当前的时间戳,则sign1参数对应的数字签名就会失效,因为黑客不知道签名秘钥,没有办法生成新的数字签名。

但是这种方式的漏洞也是显而易见,如果在60s之内进行重放攻击,那就没办法了,所以这种方式不能保证请求仅一次有效。

老鸟们一般会采取下面这种方案,既可以解决接口重放问题,又可以解决一次请求有效的问题。

基于nonce + timestamp 的方案

nonce的意思是仅一次有效的随机字符串,要求每次请求时该参数要保证不同。实际使用用户信息+时间戳+随机数等信息做个哈希之后,作为nonce参数。

此时服务端的处理流程如下:

  • 去 redis 中查找是否有 key 为 nonce:{nonce} 的 string
  • 如果没有,则创建这个 key,把这个 key 失效的时间和验证 timestamp 失效的时间一致,比如是 60s。
  • 如果有,说明这个 key 在 60s 内已经被使用了,那么这个请求就可以判断为重放请求。

这种方案nonce和timestamp参数都作为签名的一部分传到后端,基于timestamp方案可以让黑客只能在60s内进行重放攻击,加上nonce随机数以后可以保证接口只能被调用一次,可以很好的解决重放攻击问题。

代码实现

接下来以SpringBoot项目为例看看如何实现接口的防篡改和防重放功能。

1、构建请求头对象

@Data
@Builder
public class RequestHeader {
   private String sign ;
   private Long timestamp ;
   private String nonce;
}

2、工具类从HttpServletRequest获取请求参数

@Slf4j
@UtilityClass
public class HttpDataUtil {
    
    public  SortedMap<String, String> getBodyParams(final HttpServletRequest request) throws ioException {
        byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream());
        String body = new String(requestBody);
        return JSONUtil.json2Object(body, SortedMap.class);
    }
​
​
    
    public static SortedMap<String, String> getUrlParams(HttpServletRequest request) {
        String param = "";
        SortedMap<String, String> result = new TreeMap<>();
​
        if (StringUtils.isEmpty(request.getQueryString())) {
            return result;
        }
​
        try {
            param = URLDecoder.decode(request.getQueryString(), "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
​
        String[] params = param.split("&");
        for (String s : params) {
            String[] array=s.split("=");
            result.put(array[0], array[1]);
        }
        return result;
    }
}

这里的参数放入SortedMap中对其进行字典排序前端构建签名时同样需要对参数进行字典排序。

3、签名验证工具类

@Slf4j
@UtilityClass
public class SignUtil {
    
    @SneakyThrows
    public  boolean verifySign(SortedMap<String, String> map, RequestHeader requestHeader) {
        String params = requestHeader.getNonce() + requestHeader.getTimestamp() + JsonUtil.object2Json(map);
        return verifySign(params, requestHeader);
    }
​
    
    public boolean verifySign(String params, RequestHeader requestHeader) {
        log.debug("客户端签名: {}", requestHeader.getSign());
        if (StringUtils.isEmpty(params)) {
            return false;
        }
        log.info("客户端上传内容: {}", params);
        String paramsSign = DigestUtils.md5DigestAsHex(params.getBytes()).toUpperCase();
        log.info("客户端上传内容加密后的签名结果: {}", paramsSign);
        return requestHeader.getSign().equals(paramsSign);
    }
}

4、HttpServletRequest包装类

public class SignRequestWrapper extends HttpServletRequestWrapper {
    //用于将流保存下来
    private byte[] requestBody = null;
​
    public SignRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        requestBody = StreamUtils.copyToByteArray(request.getInputStream());
    }
​
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
​
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
​
            @Override
            public boolean isReady() {
                return false;
            }
​
            @Override
            public void setReadListener(ReadListener readListener) {
​
            }
​
            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
​
    }
​
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}

防篡改和防重放我们会通过springBoot Filter来实现,而编写的filter过滤器需要读取request数据流,但是request数据流只能读取一次,需要自己实现HttpServletRequestWrapper对数据流包装,目的是将request流保存下来。

5、创建过滤器实现安全校验

@Configuration
public class SignFilterConfiguration {
    @Value("${sign.maxTime}")
    private String signMaxTime;
​
    //filter中的初始化参数
    private Map<String, String> initParametersMap =  new HashMap<>();
​
    @Bean
    public FilterReGIStrationBean contextFilterRegistrationBean() {
        initParametersMap.put("signMaxTime",signMaxTime);
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(signFilter());
        registration.setInitParameters(initParametersMap);
        registration.addUrlPatterns("/sign
        long now = System.currentTimeMillis() / 1000;
​
        if (now - requestHeader.getTimestamp() > signMaxTime) {
            responseFail(httpResponse,ReturnCode.REPLAY_ERROR);
            return;
        }
​
        //2. 判断nonce
        boolean nonceExists = redisUtil.hasKey(NONCE_KEY + requestHeader.getNonce());
        if(nonceExists){
            //请求重复
            responseFail(httpResponse,ReturnCode.REPLAY_ERROR);
            return;
        }else {
            redisUtil.set(NONCE_KEY+requestHeader.getNonce(), requestHeader.getNonce(), signMaxTime);
        }
​
​
        boolean accept;
        SortedMap<String, String> paramMap;
        switch (httpRequest.getMethod()){
            case "GET":
                paramMap = HttpDataUtil.getUrlParams(requestWrapper);
                accept = SignUtil.verifySign(paramMap, requestHeader);
                break;
            case "POST":
                paramMap = HttpDataUtil.getBodyParams(requestWrapper);
                accept = SignUtil.verifySign(paramMap, requestHeader);
                break;
            default:
                accept = true;
                break;
        }
        if (accept) {
            filterChain.doFilter(requestWrapper, servletResponse);
        } else {
            responseFail(httpResponse,ReturnCode.ARGUMENT_ERROR);
            return;
        }
​
    }
​
    private void responseFail(HttpServletResponse httpResponse, ReturnCode returnCode)  {
        ResultData<Object> resultData = ResultData.fail(returnCode.getCode(), returnCode.getMessage());
        WEBUtils.writeJson(httpResponse,resultData);
    }
​
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String signTime = filterConfig.getInitParameter("signMaxTime");
        signMaxTime = Long.parseLong(signTime);
    }
}

6、Redis工具类

@Component
public class RedisUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
​
    
    public boolean hasKey(String key) {
        try {
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
​
​
    
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
​
    
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
​
}

以上就是详解SpringBoot是如何保证接口安全的的详细内容,更多关于SpringBoot保证接口安全的资料请关注编程网其它相关文章!

--结束END--

本文标题: 详解SpringBoot是如何保证接口安全的

本文链接: https://lsjlt.com/news/193945.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 详解SpringBoot是如何保证接口安全的
    目录为什么要保证接口安全防篡改如何解决防重放基于timestamp的方案基于nonce + timestamp 的方案代码实现1、构建请求头对象2、工具类从HttpServletRe...
    99+
    2023-02-02
    SpringBoot保证接口安全 SpringBoot 接口安全
  • 详解CopyOnWriteArrayList是如何保证线程安全
    目录一:前言二:成员变量分析三:源码分析1.空参构造2.传入一个Collection对象的构造方法3.传入一个数组的构造方法四:总结一:前言 在我们需要保证线程安全的时候,如果使用到...
    99+
    2024-04-02
  • php接口安全性怎么保证
    保证PHP接口的安全性可以采取以下几个措施: 使用HTTPS协议:通过使用HTTPS协议,可以加密通信数据,防止数据在传输过程中...
    99+
    2023-10-27
    php
  • 如何使用签名保证ASP.NET MVC OR WEBAPI的接口安全
    目录签名算法 签名的参数 验证签名 ApiController基类 预防Replay Attack 客户端调用 当我们开发一款App的时候,App需要跟后台服务进行通信获取或者提交数...
    99+
    2024-04-02
  • 浅谈springboot如何保证多线程安全
    目录如何保证多线程安全1.springboot在多线程并发访问下是怎么做的2.controller在多线程下如何尽可能保证线程安全,如何取舍3.小结一下单例模式与线程安全问题踩的坑下...
    99+
    2024-04-02
  • 接口数据安全保证的10种方式
    目录引言1.数据加密,防止报文明文传输。1.1 数据如何加密呢?1.2 小伙伴们,是否还记得https的原理呢?2. 数据加签验签2.1 什么是加签验签呢?2.2 有了https等加...
    99+
    2024-04-02
  • php接口安全性怎么得到保证
    要确保PHP接口的安全性,可以采取以下几个步骤: 输入验证:对所有从外部接收的输入数据进行验证和过滤,以防止恶意用户提交恶意代码...
    99+
    2023-10-27
    php
  • ConcurrentHashMap是如何保证线程安全
    目录JDK 1.7 底层实现JDK 1.7 线程安全实现JDK 1.8 底层实现JDK 1.8 线程安全实现总结ConcurrentHashMap 是 HashMap 的多线程版本,...
    99+
    2024-04-02
  • 如何设计一个安全的API接口详解
    目录前言一 安全性问题 1.1 调用接口的先决条件-token 1.2 使用POST作为接口请求方式 1.3 客户端IP白名单 1.4 单个接口针对ip限流 1.5 记录接口请求日志...
    99+
    2024-04-02
  • HTTPS是怎么保证安全的
    本篇内容介绍了“HTTPS是怎么保证安全的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、安全特性在什么是HTTP这篇文章中,我们了解到H...
    99+
    2023-06-17
  • PHP 接口日志同步,如何保证数据的安全性和稳定性?
    随着互联网技术的不断发展,越来越多的企业开始使用接口进行数据的交互。而对于这些接口的日志,如何进行同步呢?这是一个关键性问题,需要我们认真思考。 一、背景介绍 在进行接口开发时,我们通常会记录下接口的请求和响应数据,以便于后续的分析和优化...
    99+
    2023-10-11
    接口 日志 同步
  • 详解docker API管理接口增加CA安全认证
    目录一、模拟创建CA证书(中间边框的部分)二、签发服务器端证书(右边蓝色背景部分)三、签发客户端证书(左边黄色背景的部分)四、签发证书收尾工作五、配置docker服务端六、IDEA配...
    99+
    2024-04-02
  • 如何保证Linux服务器的安全
      很少见有人马上为一台新安装的服务器做安全措施,然而我们生活所在的这个社会使得这件事情是必要的。不过为什么仍旧这么多人把它拖在最后?我已经做了相同的事情,它常常可以归结为想要马上进入有趣的东西。希望这篇文章将向...
    99+
    2022-06-03
    Linux 服务器
  • 如何保证小程序的安全性
    要保证小程序的安全性,可以采取以下措施: 使用合法的开发工具和平台:确保使用官方或可信赖的开发工具和平台进行小程序开发,避免使用...
    99+
    2024-04-17
    小程序
  • 如何使用 Laravel 打包 PHP 接口并确保接口安全性?
    Laravel 是一个流行的 PHP 框架,它提供了很多强大的工具和功能,使得我们可以轻松地构建和管理我们的 PHP 应用程序。在本文中,我们将讨论如何使用 Laravel 打包 PHP 接口,并确保接口的安全性。我们将通过演示代码来说明这...
    99+
    2023-11-14
    laravel 接口 打包
  • 数据库接口数据安全保证的方法有哪些
    本篇内容主要讲解“数据库接口数据安全保证的方法有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“数据库接口数据安全保证的方法有哪些”吧!1.数据加密,防止报文明文传输。我们都知道,数据在网络传...
    99+
    2023-07-02
  • Go保证并发安全底层实现详解
    目录引言CSPChannel的基本使用channel如何保证并发安全channel的底层实现waitqbuffeLock引言 上一部分主要写了锁,本篇主要介绍Channel chan...
    99+
    2024-04-02
  • 云存储如何保证数据安全
    云存储保证数据安全的方法:1、将云存储的数据进行加密存储,从而提高云存储数据安全性和隐私性;2、做好完整性审计策略,保证云存储数据的完整性;3、访问获取云存储数据时,需要做好密文访问控制,避免黑客轻易破解窃取数据信息。具体内容如下:加密存储...
    99+
    2024-04-02
  • 高并发如何保证数据安全
    高并发保证数据安全的方法:在java中我们可以使用 setnx 的原子性来实现分布式锁保证数据唯一性。尽量让HTML静态化。将图片与页面进行分离。使用缓存、镜像、负载均衡的方法。需要使用数据库集群或者库表散列。...
    99+
    2024-04-02
  • API开发中如何保证安全性?
    随着互联网技术的发展,API已经成为了许多公司的标配,它们可以让公司更好地提供服务、管理数据,并与其他应用程序进行交互。但是,在API开发过程中,安全性是需要考虑的重要因素之一。本文将介绍API开发中需要考虑的安全性问题,并提供一些解决方...
    99+
    2023-10-17
    npm api 二维码
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作