动手实现对外安全的接口(写于天寒地冻的正月初九)

前言

  hello,好久没有写博客了。该博客写于2020正月初九,天寒地冻,本博主的手都有点僵硬,最近身体也不适,但是只要有空就得学习。正所谓:梦想在前方,那就风雨兼程吧~

想法

为啥要实现一个对外安全的接口呢?

本人也接过公司的其他项目组的对外接口,在安全这一块做到还可以,那我也要试着进行实现。

需要考虑的点

  1. 数据加密(如果直接传输比如通过 http 协议,那么用户传输的数据可以被任何人获取)
  2. 数据加签(现在很多服务在内网中都需要经过很多服务跳转,所以这里的加签可以防止内网中数据被篡改)
  3. 时间戳机制(可以使用时间戳机制,在每次请求中加入当前的时间,服务器端会拿到当前时间和消息中的时间相减,看看是否在一个固定的时间范围内比如 5 分钟内;这样恶意请求的数据包是无法更改里面时间的,所以 5 分钟后就视为非法请求了)
  4. AppId 机制(在调用的接口中需要提供 appid + 密钥,服务器端会进行相关的验证)
  5. 限流机制(如果此 appid 进行过很多非法操作,或者说专门有一个中黑系统,经过分析之后直接将此 appid 列入黑名单,所有请求直接返回错误码)

show the code

pom.xml

		<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.0.6</version>
        </dependency>

控制器

验签:md5加密,参数连在一起,加上appid=默认dajitui,header添加createTime时间戳在30分钟内

@RestController
public class TestController {


    /**
     * md5加密,参数连在一起,加上appid=默认dajitui,header添加createTime时间戳在30分钟内
     * @param student
     */
    @GetMapping("/rpc/test")
    public String a(Student student){
        System.out.println("student:"+student);
        return "123";
    }

    @PostMapping("/rpc/test1")
    public String a1(@RequestBody Student1 student1){
        System.out.println("list:"+student1);
        return "123";
    }

}

拦截器

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebAppConfig implements WebMvcConfigurer {
    //实现拦截器 要拦截的路径以及不拦截的路径
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器,添加拦截路径和排除拦截路径
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/rpc/**").excludePathPatterns("/loginPage", "/login");
    }
}
package com.example.demo;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.example.demo.entity.Student1;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(request.getRequestURI());
        String sign = request.getHeader("sign");
        if (valid(sign)) {
            throw new IllegalAccessException("验签非法");
        }
        StringBuilder builder = new StringBuilder();
        if ("POST".equals(request.getMethod())) {
            //System.out.println("post参数:" + new String(IoUtil.readBytes(request.getInputStream(), false)));
            String parameter = new String(IoUtil.readBytes(request.getInputStream(), false));
            builder.append(parameter);
        } else {
            String time = request.getHeader("createTime");
            if (valid(time)) {
                throw new IllegalAccessException("时间戳为空");
            }
            long second = DateUtil.between(new Date(), DateUtil.parse(time), DateUnit.MINUTE);
            if (!(second <= 30 && DateUtil.compare(new Date(), DateUtil.parse(time)) > 0)) {
                throw new IllegalAccessException("非法时间戳");
            }

            Map<String, String[]> map = request.getParameterMap();
            System.out.println(JSON.toJSONString(map));
            if (map == null || map.size() == 0) {
                return false;
            }
            builder.append("{");
            map.forEach((k, v) -> {
                builder.append(k);
                if (v.length == 1) {
                    builder.append(v[0]);
                } else {
                    new ArrayList<>(Arrays.asList(v)).forEach(builder::append);
                }
                builder.append(",");
            });
            builder.delete(builder.length() - 1, builder.length());
            builder.append("}");
        }
        System.out.println(builder.toString() + " " + SecureUtil.md5(builder.toString()));
        //Todo 这里只是对参数进行md5加密,可以再加上appid的配置,对应的密钥进行加密。
        if (SecureUtil.md5(builder.toString()).equals(sign)) {
            return true;
        }

        return false;
    }

    public static void main(String[] args) {
        /*Student student = new Student();
        student.setAge(20);
        student.setName("123");
        System.out.println(JSON.toJSONString(student.toString()));*/
        Student1 student1 = new Student1();
        student1.setList(Arrays.asList("1", "2"));
        System.out.println(student1.toString());
        System.out.println(SecureUtil.md5(student1.toString()));
        /*StringBuilder builder = new StringBuilder();
        builder.append("1234567");
        builder.append("89");
        builder.delete(builder.length()-1, builder.length());
        System.out.println(builder);*/
    }

    private boolean valid(String value) {
        return StrUtil.isBlank(value);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

Todo 这里只是对参数进行md5加密,可以再加上appid的配置,对应的密钥进行加密。

注意点

  1)其中sign是客户端对参数拼接完然后根据一定规则进行加密,appId可以对应一些密钥,特有的,保证签名的唯一。(这个加密还需要后期进行改进哦~~~

  2)要区分post,get去获取值。在post那里还有个坑,就是使用request.getRead(),如果参数是@RequestBody的话,会报错。因为这个方法只能调一次!!!

  3)IoUtil.readBytes(request.getInputStream(), false);这里要带false,不然会关闭request流

  4)参数的拼接,之前我们公司使用TreetSet将对象进行add进去。后面我看了下还是需要使用对象的toString()方法,来统一格式!!!而且获取参数那里要使用对象,这样才能重写toString来规范格式。

示范

@NoArgsConstructor
@Data
public class Student  {

    private String name;

    private int age;

    private User user;

    public Student(String name) {
        this.name = name;
        System.out.println("构造学生类");
    }
    

    @Override
    public String toString() {
        boolean hadFirst=false;
        StringBuilder builder = new StringBuilder();
        builder.append("{");
        if (name != null) {
            builder.append("name").append(name);
            hadFirst=true;
        }
        if (age > 0) {
            if(hadFirst) {
                builder.append(",");
            }
            builder.append("age").append(age);
        }
        if (user != null) {
            if(hadFirst) {
                builder.append(",");
            }
            builder.append("user").append(user);
        }
        builder.append("}");
        return builder.toString();
    }

}
@Data
public class Student1 {

    private List<String> list;

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("{");
        if (list != null) {
            builder.append("\"list\":[");
            list.forEach(e-> builder.append("\"").append(e).append("\"").append(","));
        }
        builder.delete(builder.length() - 1, builder.length());
        builder.append("]}");
        return builder.toString();
    }

}

postman

get请求
在这里插入图片描述

post请求

在这里插入图片描述

参考文章

如何设计一个安全的对外接口?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值