前言

后端在写对外的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&timestamp=now&nonce=random";

拼接密钥accessSecret stringSignTemp="AccessKey=access&home=world&name=hello&work=java&timestamp=now&nonce=random&accessSecret=secret";

MD5并转换为大写生成签名 sign=MD5(stringSignTemp).toUpperCase();

JAVA代码如下:params是从request里面获取的所有参数map,accessSecret是加密密钥

private String createSign(Map params, String accessSecret) throws UnsupportedEncodingException {

Set 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 result = new HashMap();

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 params = new HashMap();

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 map = new ConcurrentHashMap();

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 result = new HashMap();

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 params = new HashMap();

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 params, String accessSecret) throws UnsupportedEncodingException {

Set 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();

}

}

java api接口签名验证失败_简单API接口签名验证相关推荐

  1. api接口参数加密_解决API接口开发安全性的四种方案

    如今各种API接口层出不穷,一个API的好与不好有很多方面可以考量,其中"安全性"是一个API接口最基本也是最重要的一个特点.尤其是对于充值缴费类的API接口来说,如话费充值API ...

  2. java监听com口_简单了解Java接口+事件监听机制

    1.接口: 定义方法: public interface interName //extends interName2, interName3...可继承多个接口 在接口里只能定义常量和抽象方法. p ...

  3. java给第三方接口发送数据_对接第三方接口--使用post请求发送json数据

    对接第三方接口–使用post请求发送json数据 实习4个多月,终于转正!终于可以安心好好上班,好好学习!第一篇播客记录下工作中的中的小知识点. 本文记录的内容如下: 1.使用HttpClient相关 ...

  4. android api在线文档_通过 API 远程管理 Jenkins

    背景介绍 最近接到一个需求,需要对公司内部的Android性能测试平台的分支管理模块进行改造. 为了更好地说明问题,在下图中展示了一个精简的持续集成测试系统. 在该系统中,Jenkins负责定时检测代 ...

  5. Java编程基础10——面向对象_多态抽象类接口

    1.多态的概述及其成员访问特点代码体现 A:多态(polymorphic)概述 事物存在的多种形态 B:多态前提- 1.要有继承关系 2.要有方法重写 3.要有父类引用指向子类对象. C:多态中的成员 ...

  6. 气象接口返回图标_天气预报查询接口

    全国天气预报接口文档说明 全国天气预报接口地址 https://api.ip138.com/weather/ https://api.ip138.com/weather/ 示例1(Linux命令行下执 ...

  7. 无法创建接口的实例_什么是接口?

    接口 接口概述 接口,是java语言中一种类型,是方法的集合,如果说 类的内部封装了成员变量,构造 方法,和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法,默认方法和静态方法 接口是对功能的 ...

  8. 华为路由器接口如何区分_华为路由器接口介绍

    E1-F和T1-F接口 E1-F和T1-F接口是指部分(Fractional)化E1.T1接口,它们分别是CE1/PRI和CT1/PRI接口的简化版本.在E1/T1接入应用中,如果不需要划分出多个通道 ...

  9. java验证签名_简单API接口签名验证

    前言 后端在写对外的API接口时,一般会对参数进行签名来保证接口的安全性,在设计签名算法的时候,主要考虑的是这几个问题: 1. 请求的来源是否合法 2. 请求参数是否被篡改 3. 请求的唯一性 我们的 ...

  10. java给朋友发微信_微信api接口,给微信好友收发消息

    微信api接口,给微信好友收发消息 /** * 给微信好友发消息 * @author wechatno:tangjinjinwx * @blog http://www.wlkankan.cn */ @ ...

最新文章

  1. 入门Python之后还是搞不定面试、做不来项目,推荐读读这本书
  2. plsql tables 没有表_天长视唱练耳辅导班收费表,安徽高考音乐培训学校,你知道吗...
  3. selecte设置不可用使用disabled属性注意
  4. Dubbo——Dubbo协议整合Jackson序列化解决方案
  5. 外挂学习之路(11)--- 背包数据的遍历
  6. CYQ.Data 数据框架 V4.0 开源版本发布(源码提供下载,秋色园V2.5版本标配框架)
  7. html无序列表中的正方形点点,CSS 有序或者无序列表的前面的标记 list-style-type 属性的实现...
  8. php-fpm容易假死,实现自动重启php服务的脚本 通过后
  9. 我的MYSQL学习心得(九) 索引
  10. yagmail和keyring的安装与注册
  11. SQL笔试经典50题及答案解析(题目41-50)
  12. Spyder5 显示器校准 色彩校准
  13. TJA1042T/3与国产CAN芯片SIT1042T/3性能对比
  14. 课程列表和整合阿里云视频点播
  15. JQuery淡出淡入动画
  16. 2015-华为招聘公开测试题目-单词迷宫
  17. 阿里实名认证Java版(详细教程)
  18. 最短路径(加权有向图)
  19. 普通索引 唯一索引 主键索引 组合索引 全文索引
  20. 关于一款开源远程控制软件(gh0st)的源码分析(一)

热门文章

  1. 崩坏3区号+86_中国大陆国际区号是“ +86”,还是“ +086”、“ +0086”
  2. 创业经验谈(转自:ouravr.com)
  3. VMware Workstation 安装ssh服务器
  4. AvalonDock的基本用法
  5. 云端软件关闭的原因是什么?
  6. C++模板函数 学习记录
  7. ghostscript的坑
  8. BZOJ4399: 魔法少女LJJ
  9. 2021-10-12 CHIP类PCB封装的创建
  10. 测量法的三种测量方法计算机,圆度的测量方法有哪几种