首页 > 其他分享 >接口安全处理

接口安全处理

时间:2023-04-05 17:59:13浏览次数:36  
标签:return String requestHeader 处理 接口 安全 new public 请求

一、为什么要保证接口安全

在我们日常开发中,存在一些接口是敏感且重要的,比如充值接口,如果在你调用充值接口的时候被别人抓包了,然后就可以修改充值的金额,本来充值10元可以改成充值10w,产生重大生产问题,再或者说被被人抓包了,别人可以不限制的调用该充值10元的接口,调用几万次,也是会导致重大问题,那么我们该如何保证接口安全呢?

二、接口安全的几种方式

  • 数据参数合法性校验

接口数据的安全性保证,还需要我们的系统,有个数据合法性校验,简单来说就是参数校验,比如身份证长度,手机号长度,是否是数字等等

  • token授权认证方式

一般我们系统都会使用token鉴权登陆校验用户登陆状态和用户权限,访问接口前先校验token的合法性

在这里插入图片描述

  • 数据加密,防止报文明文传输

说到数据加密,我们不难想到使用HTTPS进行传输,HTTPS使用了RSA和AES加密的方式保证了数据传输中的安全问题,具体的HTTPS的加密原理,请看HTTPS原理

数据在传输过程中被加密了,理论上,即使被抓包,数据也不会被篡改。但是https不是绝对安全的哦。还有一个点:https加密的部分只是在外网,然后有很多服务是内网相互跳转的,签名验证也可以在这里保证不被中间人篡改,所以一般转账类安全性要求高的接口开发,都需要加签验签

  • 签名验证

https虽然保证了在外网上数据不会被篡改,但是不能保证在内网中数据篡改的风险,所以需要有签名验证的环节

  1. 客户端把参数以特定顺序进行md5加密形成签名sign,一并同参数传递到服务端
  2. 服务端接收到签名和参数,也以一定的顺序对参数进行md5加密,对比传递来的sign判断是否是否被篡改

这样做的好处就是,在数据传输过程中,可以保证数据不会被篡改,如果篡改了的话sign就会不一致,验证不通过

但是这仅仅只是解决了篡改问题,那如果我拿到请求后不修改参数,原样数据多次调用,还是会产生问题,这时候就需要增加防重放功能

  • timestamp+nonce方案防止重放攻击

  1. timestamp是时间戳超时机制,当一个请求超过该时间后,则认定为该请求失效,需要重新发送请求,默认60s,但是如果在60s内多次调用岂不是也会导致问题?
  2. 通常来说,从抓包到重放的时间绝对不止60s,为了避免此类问题发生,我们可以在客户端发送请求的时候随机产生一个nonce随机数
  3. nonce令牌是一个随机数,每次请求后都会存入redis,过期时间60s,这样就没个请求只能请求一次,避免了多次调用的问题
  • 白名单黑名单

三、防重放和防篡改拦截器

这里我们使用timestamp+nonce+sign对接口进行安全处理

在这里插入图片描述

1. 构建请求头

@Data
@Builder
public class RequestHeader {

    /**
     * 签名
     */
    private String sign;
    /**
     * 时间戳
     */
    private Long timestamp;
    /**
     * 临时的数据
     */
    private String nonce;

}

2. 保存请求流对象

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

3. 创建请求数据处理工具

@Slf4j
public class HttpDataUtil {
    /**
     * post请求处理:获取 Body 参数,转换为SortedMap
     *
     * @param request
     */
    public static SortedMap<String, String> getBodyParams(final HttpServletRequest request) throws IOException {
        byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream());
        String body = new String(requestBody);
        return JsonUtils.parseObject(body, SortedMap.class);
    }


    /**
     * get请求处理:将URL请求参数转换成SortedMap
     */
    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;
    }
}

4. 签名验证工具

@Slf4j
public class SignUtil {

    /**
     * 验证签名
     * 验证算法:把timestamp + JsonUtil.object2Json(SortedMap)合成字符串,然后MD5
     */
    @SneakyThrows
    public static boolean verifySign(SortedMap<String, String> map, RequestHeader requestHeader) {
        String params = requestHeader.getNonce() + requestHeader.getTimestamp() + JsonUtils.toJsonString(map);
        return verifySign(params, requestHeader);
    }

    /**
     * 验证签名
     */
    public static 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);
    }

}

5. 创建拦截器SignFilter

@Slf4j
public class SignFilter implements Filter {

    private static final Long signMaxTime = 60L;

    private static final String NONCE_KEY = "x-nonce-";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        log.info("过滤URL:{}", httpRequest.getRequestURI());

        //request数据流只能读取一次,这里保存request流
        HttpServletRequestWrapper requestWrapper = new SignRequestWrapper(httpRequest);

        //构建请求头
        String nonceHeader = httpRequest.getHeader("X-Nonce");
        String timeHeader = httpRequest.getHeader("X-Time");
        String signHeader = httpRequest.getHeader("X-Sign");

        //验证请求头是否存在
        if (StringUtils.isEmpty(nonceHeader) || ObjectUtils.isEmpty(timeHeader) || StringUtils.isEmpty(signHeader)) {
            throw new RuntimeException("请求头不存在");
        }

        RequestHeader requestHeader = RequestHeader.builder()
                .nonce(httpRequest.getHeader("X-Nonce"))
                .timestamp(Long.parseLong(httpRequest.getHeader("X-Time")))
                .sign(httpRequest.getHeader("X-Sign")).build();
        /*
         * 1.验证签名是否过期,防止重放
         * 判断timestamp时间戳与当前时间是否操过60s(过期时间根据业务情况设置),如果超过了就提示签名过期。
         */
        long now = System.currentTimeMillis() / 1000;
        if (now - requestHeader.getTimestamp() > signMaxTime) {
            throw new RuntimeException("签名过期");
        }

        //2. 判断nonce,是否重复发送
        boolean nonceExists = RedisUtils.hasKey(NONCE_KEY + requestHeader.getNonce());
        if (nonceExists) {
            //请求重复
            throw new RuntimeException("请求重复");
        } else {
            RedisUtils.set(NONCE_KEY + requestHeader.getNonce(), requestHeader.getNonce(), signMaxTime);
        }

        // 3. 验证签名,防止篡改
        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 {
            throw new RuntimeException("签名有误,请重新请求");
        }

    }

}

6. 配置拦截器

@Configuration
public class SignFilterConfiguration {

    @Bean
    public FilterRegistrationBean contextFilterRegistrationBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new SignFilter());
        registration.addUrlPatterns("/sign/*");
        registration.setName("SignFilter");
        // 设置过滤器被调用的顺序
        registration.setOrder(1);
        return registration;
    }

}

7. 测试

@RequestMapping("")
@RestController
public class SignDemoController {

    @PostMapping("/sign/demo1")
    public R demo1(@RequestBody DemoDto demoDto) {
        System.out.println("===执行了demo1");
        return R.ok();
    }

    @GetMapping("/demo2")
    public R demo2() {
        System.out.println("执行了demo2====");
        return R.ok();
    }

}

@Data
class DemoDto {
    private Integer age;

    private String username;

    private Long id;
}
{
  "age": 11,
  "username": "zhangsan",
  "id": 1
}

在这里插入图片描述
在这里插入图片描述

标签:return,String,requestHeader,处理,接口,安全,new,public,请求
From: https://www.cnblogs.com/sun2020/p/17290058.html

相关文章

  • PentestLab-web安全命令注入-EXP2
    我们打开靶机选择“Example2”观察页面我们尝试http://192.168.29.148/commandexec/example2.php?ip=127.0.0.1;ifconfig结果我们尝试%0a于是payload为http://192.168.29.148/commandexec/example2.php?ip=127.0.0.1%0acat%20/etc/passwd结果我们观察靶机源码使用了正则来匹配IP地......
  • C++库封装JNI接口——实现java调用c++
    1.JNI原理概述通常为了更加灵活高效地实现计算逻辑,我们一般使用C/C++实现,编译为动态库,并为其设置C接口和C++接口。用C++实现的一个库其实是一个或多个类的简单编译链接产物。然后暴露其实现类构造方法和纯虚接口类。这样就可以通过多态调用到库内部的实现类及其成员方法。进一步......
  • OpenJDK源码研究笔记(十):枚举的高级用法,枚举实现接口,竟是别有洞天
    在研究OpenJDK,Java编译器javac源码的过程中,发现以下代码。顿时发现枚举类竟然也有如此“高端大气上档次”的用法。沙场点兵(用法源码)com.sun.tools.javac.file.JavacFileManager.SortFilesprotectedenumSortFilesimplementsComparator<File>{FORWARD{......
  • Jenkins持续集成,接口测试报告发送企业微信
    一、前置条件已经安装Jenkins(361.1)已经安装jdk(jdk17)Jdk和Jenkins版本相互兼容二、启动Jenkins(注意不要关闭dos窗口)切换到Jenkins目录,输入命令:java-jarjenkins.war        2.dos窗口出现“Jenkinsisfullyupandrunning”表示启动成功   ......
  • 接口收藏哦
    http://c.m.163.com/nc/article/headline/T1348647853363/0-40.html头条http://c.3g.163.com/nc/article/list/T1467284926140/0-20.html精选http://c.3g.163.com/nc/article/list/T1348648517839/0-20.html娱乐http://c.m.163.com/nc/auto/list/5bmz6aG25bGx/0-20.html汽车......
  • 知乎微信接口
    微信精选段子http://v.juhe.cn/weixin/query?key=d046cd1f569ed13d951f0258902ef9b2&ps=10知乎最新日报列表http://news-at.zhihu.com/api/4/news/latest知乎详情http://news-at.zhihu.com/api/4//news/{KaTeXparseerror:Expected'EOF',got'}'atposition3:......
  • yaml-cpp YAML格式处理库的介绍和使用(面向业务编程-文件格式处理)
    yaml-cppYAML格式处理库的介绍和使用(面向业务编程-文件格式处理)YAML格式介绍YAML的格式介绍,有关ini、json和xml或许很多人已经很了解了,但是关于YAML,还有许多人不了解。YAML被设计成更适合人类阅读(我想正因为如此,所以相对来说更灵活,就导致到使用的时候很多人会觉得它看起来并不......
  • 云安全包括哪些问题
    l用户身份安全问题云计算通过网络提供弹性可变的IT服务,用户需要登录到云端来使用应用与服务,系统需要确保使用者身份的合法性,才能为其提供服务。如果非法用户取得了用户身份,则会危及合法用户的数据和业务。l共享业务安全问题云计算的底层架构(IaaS和PaaS层)是通过虚拟化技术实现资源......
  • 走进Java接口测试之Mock(概念篇)
    引言实际工作中,测试人员可能会遇到如下情况:场景一:依赖接口不通,甲开发A模块,乙开发B模块,甲的进度比乙快,但A模块的方法依赖于B模块,要测试A模块接口怎么办?场景二:异常数据难模拟,当需要测试接口一些异常数据,接口正常情况是否无法提供异常数据的。那么如何简便地构造接口的异常数据?场景三:......
  • 秒懂HTTPS接口(实现篇)
    HTTPS接口实现下面我们来实践使用Java实现一个简单HTTPS接口示例项目结构:1.springbootdemo2.├─config配置信息类3.├─controller控制器类4.├─entity实体类5.├─enums枚举类6.├─exception异常类7.├─handler捕获类8.├─repository数据访问类9.├......