简单API接口签名验证
前言
后端在写对外的API接口时,一般会对参数进行签名来保证接口的安全性,在设计签名算法的时候,主要考虑的是这几个问题: 1. 请求的来源是否合法 2. 请求参数是否被篡改 3. 请求的唯一性 我们的签名加密也是主要针对这几个问题来实现
设计
基于上述的几个问题,我们来通过已下步骤来实现签名加密: 1. 通过分配给APP对应的app_key和app_secret来验证身份 2. 通过将请求的所有参数按照字母先后顺序排序后拼接再MD5加密老保证请求参数不被篡改 3. 请求里携带时间戳参数老保证请求的唯一和过期,重复的请求在指定时间(可配置)内有效
实现
-
签名生成:
- 生成当前时间戳timestamp=now
- 按照请求参数名的字母升序排列非空请求参数(包含accessKey)
stringA="AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random";
- 拼接密钥accessSecret
stringSignTemp="AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random&accessSecret=secret";
- MD5并转换为大写生成签名
sign=MD5(stringSignTemp).toUpperCase();
JAVA代码如下:params是从request里面获取的所有参数map,accessSecret是加密密钥
private String createSign(Map<String, Object> params, String accessSecret) throws UnsupportedEncodingException { Set<String> keysSet = params.keySet(); Object[] keys = keysSet.toArray(); Arrays.sort(keys); StringBuilder temp = new StringBuilder(); boolean first = true; for (Object key : keys) { if (first) { first = false; } else { temp.append("&"); } temp.append(key).append("="); Object value = params.get(key); String valueString = ""; if (null != value) { valueString = String.valueOf(value); } temp.append(valueString); } temp.append("&").append(ACCESS_SECRET).append("=").append(accessSecret); return MD5Util.MD52(temp.toString()).toUpperCase(); }
-
签名校验:
- 参数格式校验
- 超时校验
- 验证签名
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Map<String, Object> result = new HashMap<String, Object>(); String timestamp = request.getParameter(TIMESTAMP_KEY); String accessKey = request.getParameter(ACCESS_KEY); String accessSecret = map.get(accessKey); if (!org.apache.commons.lang.StringUtils.isNumeric(timestamp)) { result.put("code", 1000); result.put("msg", "请求时间戳不合法"); WebUtils.writeJsonByObj(result, response, request); return false; } // 检查KEY是否合理 if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(accessSecret)) { result.put("code", 1001); result.put("msg", "加密KEY不合法"); WebUtils.writeJsonByObj(result, response, request); return false; } Long ts = Long.valueOf(timestamp); // 禁止超时签名 if (System.currentTimeMillis() - ts > SIGN_EXPIRED_TIME) { result.put("code", 1002); result.put("msg", "请求超时"); WebUtils.writeJsonByObj(result, response, request); return false; } if (!verificationSign(request, accessKey, accessSecret)) { result.put("code", 1003); result.put("msg", "签名错误"); WebUtils.writeJsonByObj(result, response, request); return false; } return true; }
校验签名:
private boolean verificationSign(HttpServletRequest request, String accessKey, String accessSecret) throws UnsupportedEncodingException { Enumeration<?> pNames = request.getParameterNames(); Map<String, Object> params = new HashMap<String, Object>(); while (pNames.hasMoreElements()) { String pName = (String) pNames.nextElement(); if (SIGN_KEY.equals(pName)) continue; Object pValue = request.getParameter(pName); params.put(pName, pValue); } String originSign = request.getParameter(SIGN_KEY); String sign = createSign(params, accessSecret); return sign.equals(originSign); }
- 完整代码:
这里通过拦截器来实现接口拦截,可自行替换
package com.mlcs.mop.common.web.interceptor; import com.mlcs.core.conf.ZKClient; import com.mlcs.mop.common.web.util.MD5Util; import com.mlcs.mop.common.web.util.WebUtils; import org.apache.zookeeper.KeeperException; import org.springframework.util.StringUtils; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Author: Kelin * Date: 2018/5/16 * Description: */ @SuppressWarnings("SuspiciousMethodCalls") public class SimpleApiSignInterceptor extends HandlerInterceptorAdapter { // 签名超时时长,默认时间为5分钟,ms private static final int SIGN_EXPIRED_TIME = 5 * 60 * 1000; private static final String API_SIGN_KEY_CONFIG_PATH = "/mop/common/system/api_sign_key_mapping.properties"; private static final String SIGN_KEY = "sign"; private static final String TIMESTAMP_KEY = "timestamp"; private static final String ACCESS_KEY = "accessKey"; private static final String ACCESS_SECRET = "accessSecret"; private static Map<String, String> map = new ConcurrentHashMap<String, String>(); static { // 从zk加载key映射到内存里面 try { String data = ZKClient.get().getStringData(API_SIGN_KEY_CONFIG_PATH); Properties properties = new Properties(); properties.load(new StringReader(data)); for (Object key : properties.keySet()) { map.put(String.valueOf(key), properties.getProperty(String.valueOf(key))); } } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Map<String, Object> result = new HashMap<String, Object>(); String timestamp = request.getParameter(TIMESTAMP_KEY); String accessKey = request.getParameter(ACCESS_KEY); String accessSecret = map.get(accessKey); if (!org.apache.commons.lang.StringUtils.isNumeric(timestamp)) { result.put("code", 1000); result.put("msg", "请求时间戳不合法"); WebUtils.writeJsonByObj(result, response, request); return false; } // 检查KEY是否合理 if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(accessSecret)) { result.put("code", 1001); result.put("msg", "加密KEY不合法"); WebUtils.writeJsonByObj(result, response, request); return false; } Long ts = Long.valueOf(timestamp); // 禁止超时签名 if (System.currentTimeMillis() - ts > SIGN_EXPIRED_TIME) { result.put("code", 1002); result.put("msg", "请求超时"); WebUtils.writeJsonByObj(result, response, request); return false; } if (!verificationSign(request, accessKey, accessSecret)) { result.put("code", 1003); result.put("msg", "签名错误"); WebUtils.writeJsonByObj(result, response, request); return false; } return true; } private boolean verificationSign(HttpServletRequest request, String accessKey, String accessSecret) throws UnsupportedEncodingException { Enumeration<?> pNames = request.getParameterNames(); Map<String, Object> params = new HashMap<String, Object>(); while (pNames.hasMoreElements()) { String pName = (String) pNames.nextElement(); if (SIGN_KEY.equals(pName)) continue; Object pValue = request.getParameter(pName); params.put(pName, pValue); } String originSign = request.getParameter(SIGN_KEY); String sign = createSign(params, accessSecret); return sign.equals(originSign); } private String createSign(Map<String, Object> params, String accessSecret) throws UnsupportedEncodingException { Set<String> keysSet = params.keySet(); Object[] keys = keysSet.toArray(); Arrays.sort(keys); StringBuilder temp = new StringBuilder(); boolean first = true; for (Object key : keys) { if (first) { first = false; } else { temp.append("&"); } temp.append(key).append("="); Object value = params.get(key); String valueString = ""; if (null != value) { valueString = String.valueOf(value); } temp.append(valueString); } temp.append("&").append(ACCESS_SECRET).append("=").append(accessSecret); return MD5Util.MD52(temp.toString()).toUpperCase(); } }
原文地址:https://my.oschina.net/KelinM/blog/1925209
相关推荐
-
绝对良心的 Java 中发邮件功能 Java基础
2019-3-25
-
“吃人”的那些Java名词:对象、引用、堆、栈 Java基础
2019-5-8
-
Java Collection 移除元素的几种方式 Java基础
2020-6-13
-
深入分析String.intern和String常量的实现原理 Java基础
2019-1-24
-
Web 跨域请求问题的解决方案- CORS 方案 Java基础
2020-6-14
-
Java8 基于spring @Async方法和Lambda实现任意代码块异步执行 Java基础
2020-5-30
-
Java 微服务框架新选择:Spring 5 Java基础
2020-6-13
-
Java爬虫–Https绕过证书 Java基础
2019-10-7
-
hrpc:简单的rpc框架(spring+zookeeper+netty) Java基础
2020-5-30
-
分布式共享Session之SpringSession源码细节 Java基础
2020-5-30