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

本文介绍了如何在Java中设计并实现一个简单的API接口签名验证机制,以确保接口安全性。主要内容包括通过分配的app_key和app_secret验证身份,对请求参数进行排序和MD5加密,以及携带时间戳来保证请求的唯一性和防止重复请求。文中提供了完整的签名生成和校验的Java代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

后端在写对外的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();

}

}

转载至链接:https://my.oschina.net/u/3057247/blog/1925209

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值