前言
hello,好久没有写博客了。该博客写于2020正月初九,天寒地冻,本博主的手都有点僵硬,最近身体也不适,但是只要有空就得学习。正所谓:梦想在前方,那就风雨兼程吧~
想法
为啥要实现一个对外安全的接口呢?
本人也接过公司的其他项目组的对外接口,在安全这一块做到还可以,那我也要试着进行实现。
需要考虑的点
- 数据加密(如果直接传输比如通过 http 协议,那么用户传输的数据可以被任何人获取)
- 数据加签(现在很多服务在内网中都需要经过很多服务跳转,所以这里的加签可以防止内网中数据被篡改)
- 时间戳机制(可以使用时间戳机制,在每次请求中加入当前的时间,服务器端会拿到当前时间和消息中的时间相减,看看是否在一个固定的时间范围内比如 5 分钟内;这样恶意请求的数据包是无法更改里面时间的,所以 5 分钟后就视为非法请求了)
- AppId 机制(在调用的接口中需要提供 appid + 密钥,服务器端会进行相关的验证)
- 限流机制(如果此 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请求