api接口不再裸奔——签名认证

网友投稿 611 2022-10-13

api接口不再裸奔——签名认证

api接口不再裸奔——签名认证

在第三方调用api接口的时候,可能会存在以下几个问题

请求身份是否合法?请求参数是否被篡改?请求是否唯一?

解决上述三个问题分为如下流程

1、合法性,通过请求许可来进行判断

为开发者分配AccessKey(开发者标识,确保唯一)和SecretKey(用于接口加密,确保不易被穷举,生成算法不易被猜测)

目前广泛使用token和AccessKey作用一样,都是第三方合法性的认证标示

2、防止被伪造,利用签名来解决

通常的做法利用参数名升序使用键值对+SecretKey+timestamp(后面将为什么要加时间戳)生成字符串sign,然后使用加密算法对sign进行加密(例如MD5),生成唯一的signkey,timestamp两个标示放到class SignHandlerInterceptor extends HandlerInterceptorAdapter implements IResponseWithjson { /** * 签名超时时长,默认时间为5分钟,ms */ private int expiredTime = 5 * 60 * 1000; private String signKey = "sign"; private static final String TIMESTAMP_KEY = "timestamp"; private ICacheOperation cacheOperation; /** * 渠道类型 */ private Map channelModelMap = new HashMap<>(); public SignHandlerInterceptor() { } public SignHandlerInterceptor(String expiredTime, String signKey, ICacheOperation cacheOperation, List channelModels) { if (CollectionUtil.isNotEmpty(channelModels)) { Map> tempMap = channelModels.stream() .distinct() .collect(Collectors.groupingBy(CheckChannelModel::getChannelCode)); tempMap.forEach((code, checkChannelModels) -> { this.channelModelMap.put(code, checkChannelModels.get(0)); }); } if (!Strings.isNullOrEmpty(signKey)) { this.signKey = signKey; } if (StringUtils.isNumeric(expiredTime)) { this.expiredTime = Integer.parseInt(expiredTime); } this.cacheOperation = cacheOperation; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { TokenUser tokenUser = RequestData.getTokenUser(); //判断token为空,而且 开启签名认证,并且不存在白名单中 String channelCode = request.getHeader("channel"); if (tokenUser == null && CollectionUtil.isNotEmpty(channelModelMap)) { if (Strings.isNullOrEmpty(channelCode) || !channelModelMap.keySet().contains(Integer.parseInt(channelCode.trim()))) { responseWithJson(request, response, JsonData.error("渠道不合法!")); return false; } else if (channelModelMap.get(Integer.parseInt(channelCode.trim())).getOnOffSwitch() != 0) { return true; } else { //1、验证签名是否正确 String timestamp = request.getHeader(TIMESTAMP_KEY); String sign = request.getHeader(this.signKey); //2、判断时间戳是否符合格式要求 if (StringUtil.isEmpty(timestamp) || !StringUtil.isNumeric(timestamp)) { responseWithJson(request, response, JsonData.error("请求时间戳不合法!")); return false; } //3、判断时间戳是否在超时 long ts = Long.parseLong(timestamp); if (System.currentTimeMillis() - ts > expiredTime) { responseWithJson(request, response, JsonData.error("请求过期!")); return false; } //4、判断签名是否存在 if (StringUtil.isEmpty(sign)) { log.error("sign签名为空"); responseWithJson(request, response, JsonData.error("认证无法通过!")); return false; } //5、判断签名是否正确 String secret = channelModelMap.get(Integer.parseInt(channelCode.trim())).getChannelSecret(); if (!verificationSign(request, secret, timestamp)) { responseWithJson(request, response, JsonData.error("认证无法通过!")); return false; } //6、判断认证是否被使用过,没有使用过则放入缓存中 byte[] signs = cacheOperation.get(sign.getBytes()); if (signs == null || signs.length == 0) { cacheOperation.setnx(sign.getBytes(), expiredTime, "1".getBytes()); } else { responseWithJson(request, response, JsonData.error("认证已失效!")); return false; } } } return true; } private boolean verificationSign(HttpServletRequest request, String accessSecret, String timestamp) throws IOException { Enumeration pNames = request.getParameterNames(); Map params = new HashMap<>(); while (pNames.hasMoreElements()) { String pName = (String) pNames.nextElement(); Object pValue = request.getParameter(pName); params.put(pName, pValue); } if (CollectionUtil.isEmpty(params)) { BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); String body = IOUtils.read(reader); if (StringUtil.isNotEmpty(body)) { params.put("body", body); } } String originSign = request.getHeader(signKey); String sign = createSign(params, accessSecret, timestamp); return sign.equals(originSign); } private String createSign(Map params, String accessSecret, String timestamp) { boolean append = false; StringBuilder temp = new StringBuilder(); if (CollectionUtil.isNotEmpty(params)) { Set keysSet = params.keySet(); Object[] keys = keysSet.toArray(); Arrays.sort(keys); for (Object key : keys) { if (!append) { append = true; } else { temp.append("&"); } temp.append(key).append("="); Object value = params.get(key); String valueString = ""; if (null != value) { valueString = String.valueOf(value); } temp.append(valueString); } } if (append) { temp.append("&"); } temp.append(TIMESTAMP_KEY).append("=").append(timestamp); temp.append("&").append(signKey).append("=").append(accessSecret); return DigestUtils.md5Hex(temp.toString()).toUpperCase(); }}

public interface IResponseWithJson { default void responseWithJson(HttpServletRequest request, HttpServletResponse response, Object responseObject) { String jsonpCallback = request.getParameter("callback"); response.setCharacterEncoding("UTF-8"); PrintWriter out; if (Strings.isNullOrEmpty(jsonpCallback)) { response.setContentType("application/json; charset=utf-8"); out = null; try { out = response.getWriter(); out.append(JSONObject.toJSONString(responseObject)); } catch (IOException var18) { var18.printStackTrace(); } finally { if (out != null) { out.close(); } } } else { response.setContentType("text/plain"); response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0L); out = null; try { out = response.getWriter(); out.append(jsonpCallback + "(" + JSONObject.toJSONString(responseObject) + ")"); } catch (IOException var17) { var17.printStackTrace(); } finally { if (out != null) { out.close(); } } } }}

备注:参考内容如下​​《开放API接口签名验证,让你的接口从此不再裸奔》​​

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:jxyz- mvc框架(足金58是什么意思)
下一篇:【配置中心】——配置中心选型
相关文章

 发表评论

暂时没有评论,来抢沙发吧~