在为第三方系统提供接口的时候,肯定是要考虑接口数据安全问题
比如:
接口中数据是否有篡改,如果有大佬在你的接口数据中增加或者修改数据,那岂不是对业务系统造成不可避免的损失
请求是否已经超时。
请求是否重复提交等问题。
如何使做到统一校验:
本次就使用spring web 拦截器进行统一校验
如何防止上述问题的出现呢 需要向数据发送方进行约定传入参数进行校验
因此在做接口主要解决如下几个问题:
接受方系统会校验发送方timestamp时间戳不得超过当前时间 360秒
//进行限制 如果当前时间 - 发送时间 > 3600 则认定超时
int time = 360;
long now = System.currentTimeMillis() / 1000;
if ((now - Long.valueOf(timeStamp)) > time) {
map.put("code", "ERROR");
map.put("message", "请求发起时间超过服务器规定得时间");
return map;
}
接受方系统需要校验发送的APPID是否正确顺便取出需要得sate值
private static final Map appSate = new HashMap<>();
static {
appSate.put("123", "321");
appSate.put("789", "987");
}
//目前先写死appid 正常应该从数据库中进行配置
String sate = appSate.get(appid);
if (StringUtils.isEmpty(sate)) {
map.put("code", "ERROR");
map.put("message", "appid填写不正确,请填写正确得appid或联系管理员进行处理!");
return map;
}
接受方系统需要需要根据发送方的sn值来判断是否是重复请求
1.使用Redis(推荐)
2.使用Redisson(推荐)
3.使用ConcurrentHasMap(也可以)
在此我是用的是ConcurrentHashMap使用其中putIfAbsent(Redist同样的方法)
如果所指定的 key 已经在 map中存在,返回和这个 key 值对应的 value, 如果所指定的 key 不在 map中存在,则返回 null。
注意:使用redis时加上key的过期时间
//判断是否 是重复请求 再次使用ConcurrentHashMap有条件同学或者正式环境可以使用Redis或者Redisson
{
//首先先从map中或者redis中获取是否存在该sn
//原则上sn不允许重复 也可以设置规则 sn 在第一天中不能重复
String s = toKenMap.putIfAbsent(toKenMapPrefix + sn, sn);
if (s != null) {
map.put("code", "ERROR");
map.put("message", "sn->重复,检测为重复请求!");
return map;
}
}
示例:
json字符串为:{"name":"张三","age": "15"}
需要组合成:age=15&name=张三&sate=123
最后进行md5加密:fcae857780607eb61ff81b0f70e43d1
接受方系统需要需要根据发送方的sign值来判断是否篡改
TreeMap body = getBody(request);
String body1 = getBody(body, sate);
String s = signDataParam(body1);
if (!sign.equals(s)) {
map.put("code", "ERROR");
map.put("message", "无效的Sign值");
return map;
}
String getBody(SortedMap map, String sate) {
StringBuffer sb = new StringBuffer();
if (map != null) {
for (String key : map.keySet()) {
sb.append(key + "=" + map.get(key) + "&");
}
}
sb.append("sate" + "=" + sate);
return sb.toString();
}
TreeMap getBody(HttpServletRequest request) {
String bodyString = HttpHelper.getBodyString(request);
TreeMap o = JSONObject.parseObject(bodyString, TreeMap.class);
return o;
}
public class WebInterceptors implements HandlerInterceptor {
private final ConcurrentHashMap toKenMap = new ConcurrentHashMap<>();
private final String toKenMapPrefix = "TOKENCHECK";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//实现校验逻辑
Map map = check(request);
if ("SUCCESS".equals(map.get("code"))) {
System.out.println("成功");
return true;
} else {
//校验失败
System.out.println("失败!");
return error(response, map.get("message"));
}
}
@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 {
}
private static final Map appSate = new HashMap<>();
static {
appSate.put("123", "321");
appSate.put("789", "987");
}
/***
* 校验请求头
* 1.先看所有的参数是否为空
* 2.timeStamp 时间戳是否所起
* 3.校验appid 是否存在
* 4.校验是否重复请求
* @param request
* @return
*/
public Map check(HttpServletRequest request) {
Map map = new HashMap<>();
String sign = request.getHeader("sign");
String timeStamp = request.getHeader("timeStamp");
String sn = request.getHeader("sn");
String appid = request.getHeader("appid");
if (StringUtils.isEmpty(timeStamp) || StringUtils.isEmpty(sn) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(appid)) {
map.put("code", "ERROR");
map.put("message", "请求参数填写不规范!");
return map;
}
//进行限制 如果当前时间 - 发送时间 > 360 则认定超时
int time = 360;
long now = System.currentTimeMillis() / 1000;
if ((now - Long.valueOf(timeStamp)) > time) {
map.put("code", "ERROR");
map.put("message", "请求发起时间超过服务器规定得时间");
return map;
}
//目前先写死appid 正常应该从数据库中进行配置
String sate = appSate.get(appid);
if (StringUtils.isEmpty(sate)) {
map.put("code", "ERROR");
map.put("message", "appid填写不正确,请填写正确得appid或联系管理员进行处理!");
return map;
}
//判断是否 是重复请求 再次使用ConcurrentHashMap有条件或者正式环境可以使用Redis或者Redisson
{
//首先先从map中或者redis中获取是否存在该sn
//原则上sn不允许重复 也可以设置规则 sn 在第一天中不能重复
String s = toKenMap.putIfAbsent(toKenMapPrefix + sn, sn);
if (s != null) {
map.put("code", "ERROR");
map.put("message", "sn->重复,检测为重复请求!");
return map;
}
}
TreeMap body = getBody(request);
String body1 = getBody(body, sate);
String s = signDataParam(body1);
if (!sign.equals(s)) {
map.put("code", "ERROR");
map.put("message", "无效的Sign值");
return map;
}
return map;
}
private String signDataParam(String params) {
if (Objects.isNull(params)) {
return "";
}
return MD5Utils.string2MD5(params);
}
String getBody(SortedMap map, String sate) {
StringBuffer sb = new StringBuffer();
if (map != null) {
for (String key : map.keySet()) {
sb.append(key + "=" + map.get(key) + "&");
}
}
sb.append("sate" + "=" + sate);
return sb.toString();
}
TreeMap getBody(HttpServletRequest request) {
String bodyString = HttpHelper.getBodyString(request);
TreeMap o = JSONObject.parseObject(bodyString, TreeMap.class);
return o;
}
private boolean error(HttpServletResponse httpServletResponse, String errorMsg) throws IOException {
ObjectMapper om = new ObjectMapper();
Map map = new HashMap();
map.put("code", 5000);
map.put("msg", errorMsg);
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
om.writeValue(httpServletResponse.getOutputStream(), map);
return false;
}