常见的签名方式实现一般分为以下几个步骤 :
1 . 将所有(或者特殊)请求参数按特定规则排序;
2 . 将请求参数按特定规则拼装为加密字符串;
3 . 加密算法对加密字符串进行加密,得到签名。
下面自己写了一个常见的实现方式,以便记录,这里只是示例说明基本常规实现,使用则还是根据项目的真实情况去选择。
例如,下面我简单实现了一个restful接口,/signTest去验证签名。
1.我这里使用了MD5加密方式,首先在pom.xml加入依赖。
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
2 . 因为会对请求的参数进行处理,主要是为了获取到所有的请求参数,我写了一个自定义的HttpUtils.java :
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Created by EalenXie on 2018/6/13 12:47
*/
public enum HttpUtils {
getHttpUtil;
//获取请求IP
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getHeader("Proxy-Client-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getHeader("WL-Proxy-Client-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getHeader("HTTP_CLIENT_IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getRemoteAddr();
return ip;
}
//将请求参数转换成Map
public static Map<String, String> getUrlParams(HttpServletRequest request) {
String param = "";
try {
param = URLDecoder.decode(request.getQueryString(), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Map<String, String> result = new HashMap<>();
String[] params = param.split("&");
for (String s : params) {
Integer index = s.indexOf("=");
result.put(s.substring(0, index), s.substring(index + 1));
}
return result;
}
//从请求中获取所有参数
public static SortedMap<String, String> getAllParams(HttpServletRequest request, Map<String, String> postParams) {
SortedMap<String, String> result = new TreeMap<>();
Map<String, String> urlParams = getUrlParams(request);
for (Map.Entry entry : urlParams.entrySet()) {
result.put((String) entry.getKey(), (String) entry.getValue());
}
if (postParams != null) {
for (Map.Entry entry : postParams.entrySet()) {
result.put((String) entry.getKey(), (String) entry.getValue());
}
}
return result;
}
}
3 . 签名工具类,实现签名机制 SignUtils.java :
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.SortedMap;
/**
* Created by EalenXie on 2018/6/13 9:31
*/
public enum SignUtil {
getSignUtil;
private static final Logger logger = LoggerFactory.getLogger(SignUtil.class);
/**
* @param params 所有的请求参数都会在这里进行排序加密
* @return 得到签名
*/
public String getSign(SortedMap<String, String> params) {
StringBuilder sb = new StringBuilder();
for (Map.Entry entry : params.entrySet()) {
if (!entry.getKey().equals("sign")) { //拼装参数,排除sign
if (!StringUtils.isEmpty(entry.getKey()) && !StringUtils.isEmpty(entry.getValue()))
sb.append(entry.getKey()).append(entry.getValue());
}
}
logger.info("Before Sign : {}", sb.toString());
return DigestUtils.md5Hex(sb.toString()).toUpperCase();
}
/**
* @param params 所有的请求参数都会在这里进行排序加密
* @return 验证签名结果
*/
public boolean verifySign(SortedMap<String, String> params) {
if (params == null || StringUtils.isEmpty(params.get("sign"))) return false;
String sign = getSign(params);
logger.info("verify Sign : {}", sign);
return !StringUtils.isEmpty(sign) && params.get("sign").equals(sign);
}
}
4 . SendGoodsController.java 提供Rest接口 /signTest。
package com.wuxicloud.web;
import com.wuxicloud.util.HttpUtils;
import com.wuxicloud.util.SignUtil;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.Null;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
/**
* Created by EalenXie on 2018/6/13 11:54
*/
@RestController
@CrossOrigin //加个注解,解决前端调接口跨域,这里只是为了方便测试,真实情况慎用。
public class SendGoodsController {
@RequestMapping("/signTest")
@ResponseBody
public Map<String, String> sendGood(@RequestBody(required = false) Map<String, String> params, HttpServletRequest request) {
SortedMap<String, String> allParams = HttpUtils.getAllParams(request, params);
boolean isSigned = SignUtil.getSignUtil.verifySign(allParams);
Map<String,String> result= new HashMap<>();
if (isSigned) result.put("flag","Signed Success!");
else result.put("flag","Signed Fail");
return result;
}
}
5 . 启动类,此时服务器端的代码已经准备好了。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Created by EalenXie on 2018/6/13 11:52
*/
@SpringBootApplication
public class GoodsApplication {
public static void main(String[] args) {
SpringApplication.run(GoodsApplication.class, args);
}
}
6。此时,假设某前端页面要调用该接口,应该怎么做呢?
1 . 将请求接口的参数按照与服务器相同规则进行排序;
2 . 将请求接口的参数按照与服务器相同规则拼装为加密字符串;
3 . 与服务器相同的加密算法实现加密,得到签名;
针对本例中的签名方式,我下面写了一个基本的实现。
1.因为使用到MD5,到网上去下载了一个md5.min.js(这个js有网上一大把);
2.下个jquery.min.js;
3.自己实现的签名方式 Sign.js :
/**
* Created by EalenXie on 2018/6/13 15:11
*/
/**
* @param url 请求的url,应该包含请求参数(url的?后面的参数)
* @param requestParams 请求参数(POST的JSON参数)
* @returns {string} 获取签名
*/
function getSign(url, requestParams) {
var signString = "";
var urlParams = parseQueryString(url);
var requestBody = sortObject(mergeObject(urlParams, requestParams));
for (var i in requestBody) {
signString += i + requestBody[i];
}
return md5.hex(signString).toUpperCase();
}
/**
* @param url 请求的url
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
*/
function parseQueryString(url) {
var urlReg = /^[^\?]+\?([\w\W]+)$/,
paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g,
urlArray = urlReg.exec(url),
result = {};
if (urlArray && urlArray[1]) {
var paramString = urlArray[1], paramResult;
while ((paramResult = paramReg.exec(paramString)) != null) {
result[paramResult[1]] = paramResult[2];
}
}
return result;
}
/**
* @param object 传入要进行属性排序的对象
* @returns {{}} 将对象进行属性排序(按首字母顺序进行排序)
*/
function sortObject(object) {
var objectKeys = Object.keys(object).sort();
var result = {};
for (var i in objectKeys) {
result[objectKeys[i]] = object[objectKeys[i]];
}
return result;
}
/**
* @returns {*} 将两个对象合并成一个
*/
function mergeObject(objectOne, objectTwo) {
if (objectTwo != null) {
for (var key in objectTwo) {
objectOne[key] = objectTwo[key]
}
}
return objectOne;
}
进行测试的test.html :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="jquery.min.js"></script>
<script src="md5.min.js"></script>
<script src="sign.js"></script>
<script>
var url = "localhost:8080/signTest?time=2018-06-13 15:43";
var requestParam = {"username": "EalenXie", "password": "admin", "gender": "男", "age": 23};
function test() {
var sign = getSign(url, requestParam); //根据Url和请求参数得到签名
var requestUrl = url += "&sign=" + sign; //将签名添加在请求参数后面去请求接口
$.ajax({
type: 'POST', //GET请求效果相同
headers: {
'Access-Control-Allow-Origin': "*",
"content-type": "application/json",
"Accept": "application/json"
},
async: false,
url: requestUrl,
data: JSON.stringify(requestParam),
dataType: 'json',
success: function (response) {
alert(response.flag);
}
});
}
</script>
<title>Title</title>
</head>
<body style="align-content: center">
<button onclick="test()">点击测试</button>
</body>
</html>
7.此时,启动后端项目。再打开test.html点击进行测试。
8.点击发现,得到服务器返回的验证签名调用成功的响应了。