前后端分离开发基于api调用,或者在api系统接口开发中,api接口调用的合法性不可避免,参加高校微信小程序大赛,需要开发后台,暴露接口给前端小程序,在此过程中,为了保证接口调用的合法性以及处于安全性考虑,需要对于每个api接口调用合法性进行检验。
0x00 基本思路
对于每个请求都需要进行参数校验,而每个api接口需要的参数都不一致,所以无法进行统一性校验。由此,想到拦截器,采用前置拦截器对于每个请求先进行参数校验,判断参数的有效性,再考虑放不放行。
0x01 拦截器简介以及基本原理
方法名 | 含义 | 说明 |
---|---|---|
preHandle | 前置处理 | 在实际的Handle执行前执行;有Boolean类型的返回值,如果返回为False,则Handle本身及postHandle/afterCompletion以及后续的拦截器全部都不会再继续执行;为True则反之。 |
postHandle | 后置处理 | Handle执行后视图渲染前执行 |
afterCompletion | 处理完成后 | Handle执行且视图渲染完成后执行 |
拦截器只用用于Spring项目,非Spring项目拦截器不可用。过滤器只能是Web程序,是Servlet的的规范规定的,只在Servlet前后起作用。
0x02 代码实现
自定义拦截器
import com.wspb.utils.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.DigestUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* api 请求合法性校验
* @author totalo
* @time 2019-5-23
*/
public class ApiSignatureInterceptor extends HandlerInterceptorAdapter {
private static Logger logger = LoggerFactory.getLogger(ApiSignatureInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
Enumeration<String> paramNames = request.getParameterNames();
String timestamp = request.getParameter("timestap");
// 设置10分钟有效期
long timestampDate = Long.valueOf(timestamp) + 1000*60*10;
long currDate = System.currentTimeMillis() / 1000;
logger.info("" + currDate);
logger.info("" + timestampDate);
// 判断请求是否过期
if (timestampDate < currDate) {
response.setStatus(403);
return false;
}
// 判断sign参数是不是存在
String signature = request.getParameter("sign");
if (signature == null) {
response.setStatus(403);
return false;
}
Map map = new HashMap();
//获取所有的除了sign参数的请求参数
while (paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
String[] paramValues = request.getParameterValues(paramName);
if (paramValues.length > 0) {
String paramValue = paramValues[0];
if (paramValue.length() != 0 && !"sign".equals(paramName)) {
map.put(paramName, paramValue);
}
}
}
Set setKey = map.keySet();
Object[] keys = setKey.toArray();
// 将请求参数升序排序
Arrays.sort(keys);
StringBuilder strBuilder = new StringBuilder();
for (Object str : keys) {
strBuilder.append(str.toString());
strBuilder.append(map.get(str.toString()));
}
logger.info(strBuilder.toString());
// codekfsdAFASFASFASFASDtimestap1558670368
String encodeKey = PropertiesUtil.getProperty("encodeKey");
//
strBuilder.append(encodeKey);
// 加盐后md5编码再转换成大写
String newSignature = DigestUtils.md5DigestAsHex(strBuilder.toString().getBytes()).toUpperCase();
// 若和自带的sign不相等,返回无权操作。
if (!signature.equals(newSignature)) {
response.setStatus(403);
return false;
}
logger.info(newSignature);
return true;
}
}
注册拦截器
import com.wspb.interceptor.ApiSignatureInterceptor;
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 WebConfig implements WebMvcConfigurer {
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new ApiSignatureInterceptor());
}
}
此程序中,PropertiesUtil工具栏主要用于加载配置文件,配置文件在此处不表。