一:业务背景
接口开发是各系统之间对接的重要方式,其数据是通过开放的互联网传输,对数据的安全性要有一定要求。为了提高传输过程参数的防篡改性,签名sign的方式是目前比较常用的方式。
二:实施方案
鉴权方案
1.公共鉴权参数(所有接口必须带有的参数,参数支持存放在HTTP headers【优先级高】或者url链接参数上)
2.签名生成规则
secret= 123456789(秘钥,内部协定)
①所有业务接口服务 请求方式均为 POST, 请求参数类型Content-Type=application/json
②sign 签名字符生成规则为 MD5( secret+client + format +time + version+ RequstBody(请求参数对象).toJSONString() + secret).toLowerCase()
3.签名流程
客户端: 按上述要求生成签名sign,并连同上述参数存入请求headers
服务端: 验证流程(timeout默认时长15分钟)
4.技术方案:后端使用过滤器进行统一接口鉴权,aksk+请求有效时长+有效时长内请求id不能重复,这样能鉴权+防篡改+防重放
@Slf4j
public class ApiSignAuthFilter implements Filter {
/**
* 秘钥
*/
@Value
private String secret;
/**
* 签名参数名称集合
*/
private static String[] params = new String[]{"client", "cuid","format", "time", "version", "sign"};
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//特殊场景,可以跳过验证(具体看业务)
if (!StrUtil.equalsIgnoreCase(request.getMethod(), HttpMethod.POST.name())
|| !StrUtil.containsIgnoreCase(request.getHeader(HttpHeaders.CONTENT_TYPE),MediaType.APPLICATION_JSON_VALUE)) {
chain.doFilter(req, res);
}else {
//api参数签名校验
try {
chain.doFilter(verifySign(request), res);
}catch (BusinessException e) {
HttpServletResponse response = (HttpServletResponse) res;
response.setContentType("application/json;charset=UTF-8");
String originalURL = request.getHeader("Origin");
if (originalURL != null) {
response.addHeader("Access-Control-Allow-Origin", originalURL);
}
response.addHeader("Access-Control-Allow-Credentials", "true");
PrintWriter out = response.getWriter();
out.append(JSONHelper.toJSONString(ResponseBean.error(e.getErrorCode(),e.getMessage())));
}
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
/***
* 签名校验
* @param request
*/
private HttpServletRequestWrapper verifySign(HttpServletRequest request) {
String client = getRequestParam(request, params[0]); // 客户端类型
if (StringUtil.isEmpty(cuid) && "wx".equals(client)) {
cuid = getIpAddr(request);
}
String format = getRequestParam(request, params[2]); // 请求格式 json
String time = getRequestParam(request, params[3]); // 请求时间
String version = getRequestParam(request, params[4]); // 应用版本号
String sign = getRequestParam(request, params[5]); // 验证签名
if (StringUtil.isEmpty(client) || StringUtil.isEmpty(version) || StringUtil.isEmpty(cuid)
|| StringUtil.isEmpty(format) || StringUtil.isEmpty(time) || StringUtil.isEmpty(sign)) {
throw new BusinessException(BizExceptionEnum.SIGN_ERROR_1.getResponseCode(),BizExceptionEnum.SIGN_ERROR_1.getResponseMsg());
}
if(DateUtil.between(new Date(Convert.toLong(time,0l)), new Date(), DateUnit.MINUTE) > 15) {
throw new BusinessException(BizExceptionEnum.SIGN_ERROR_3.getResponseCode(),BizExceptionEnum.SIGN_ERROR_3.getResponseMsg());
}
String bodyJsonStr = StrUtil.EMPTY;
BodyReaderHttpServletRequestWrapper requestWrapper = null;
if (!request.getRequestURI().contains("upload")) {
try {
requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
bodyJsonStr = requestWrapper.getRequestPostStr(requestWrapper);
if(StrUtil.isBlank(bodyJsonStr)) {
bodyJsonStr = StrUtil.EMPTY;
}
} catch (IOException e) {
log.error("解析请求body异常",e);
throw new BusinessException(BizExceptionEnum.SIGN_ERROR_2.getResponseCode(),BizExceptionEnum.SIGN_ERROR_2.getResponseMsg());
}
}
//拼接签名
StringBuilder signSource = new StringBuilder();
signSource.append(secret).append(client).append(cuid).append(format).append(time).append(version)
.append(bodyJsonStr).append(secret);
String keySign = MD5Implementor.MD5Encode(signSource.toString()).toLowerCase();
if (!StrUtil.equals(keySign,sign)) {
log.error("接口{}签名验证失败,请求签名串:{},sign:{}",request.getRequestURL(), signSource, sign);
throw new BusinessException(BizExceptionEnum.SIGN_ERROR.getResponseCode(),BizExceptionEnum.SIGN_ERROR.getResponseMsg());
}
return requestWrapper;
}
/**
* 从request中获取参数,先重head获取没有再从请求url上的参数中获取
* @param request
* @param key
* @return
*/
private String getRequestParam(HttpServletRequest request, String key) {
String value = request.getHeader(key);
if(StrUtil.isBlank(value)) {
value = request.getParameter(key);
}
return value;
}
}
评论 (0)