接口签名鉴权

Lewis
2022-04-09 / 0 评论 / 224 阅读 / 正在检测是否收录...

一:业务背景

  接口开发是各系统之间对接的重要方式,其数据是通过开放的互联网传输,对数据的安全性要有一定要求。为了提高传输过程参数的防篡改性,签名sign的方式是目前比较常用的方式。

二:实施方案

鉴权方案

1.公共鉴权参数(所有接口必须带有的参数,参数支持存放在HTTP headers【优先级高】或者url链接参数上)

l1r9psdt.png

2.签名生成规则

secret= 123456789(秘钥,内部协定)
①所有业务接口服务 请求方式均为 POST, 请求参数类型Content-Type=application/json
②sign 签名字符生成规则为 MD5( secret+client + format +time + version+ RequstBody(请求参数对象).toJSONString() + secret).toLowerCase()

3.签名流程

客户端: 按上述要求生成签名sign,并连同上述参数存入请求headers
服务端: 验证流程(timeout默认时长15分钟)
l1r9s68f.png

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

评论 (0)

取消