工具類
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
/**
* @author wangwei
* @version v1.0.0
* @description 簽名
* @date 2019-01-12
*/
@RefreshScope
public class SignUtil {
private static Logger logger = LoggerFactory.getLogger(SignUtil.class);
public static SignUtil signUtil;
// 對外的密鑰
// @Value("${sign-secret-secret}")
public String secretkey = "mysecret123456";
/** 加密密鑰 */
//@Value("${sign.secret.appkey}")
public String appkey = "mykey123456";
// 間隔時間
//@Value("${sign.timeout}")
public int timeout = 1 * 30 * 1000;
/** 加密密鑰 */
// private final static String APP_KEY = "mykey123456";
// public final static String SECRET_KEY = "mysecret123456";
/** 字符編碼 */
private final static String INPUT_CHARSET = "UTF-8";
// /** 超時時間 */
// private final static int TIME_OUT = 1 * 30 * 1000;
public static SignUtil getInstance() {
if(signUtil == null) {
signUtil = new SignUtil();
}
return signUtil;
}
/**
* 請求參數Map轉換驗證Map
* @param requestParams 請求參數Map
* @param charset 是否要轉utf8編碼
* @return
* @throws UnsupportedEncodingException
*/
public static Map<String,String> toVerifyMap(Map<String, String[]> requestParams, boolean charset) {
Map<String,String> params = new HashMap<>();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
//亂碼解決,這段代碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉化
if(charset)
valueStr = getContentString(valueStr, INPUT_CHARSET);
params.put(name, valueStr);
}
return params;
}
/**
* 除去數組中的空值和簽名參數
* @param sArray 簽名參數組
* @return 去掉空值與簽名參數後的新簽名參數組
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 把數組所有元素排序,並按照“參數=參數值”的模式用“&”字符拼接成字符串
* @param params 需要排序並參與字符拼接的參數組
* @return 拼接後字符串
*/
public static String createLinkString(Map<String, String> params) {
return createLinkString(params, false);
}
/**
* 把數組所有元素排序,並按照“參數=參數值”的模式用“&”字符拼接成字符串
* @param params 需要排序並參與字符拼接的參數組
* @param encode 是否需要UrlEncode
* @return 拼接後字符串
*/
public static String createLinkString(Map<String, String> params, boolean encode) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (encode)
value = urlEncode(value, INPUT_CHARSET);
if (i == keys.size() - 1) {//拼接時,不包括最後一個&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
* 編碼轉換
* @param content
* @param charset
* @return
* @throws UnsupportedEncodingException
*/
private static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5簽名過程中出現錯誤,指定的編碼集不對,您目前指定的編碼集是:" + charset);
}
}
/**
* 編碼轉換
* @param content
* @param charset
* @return
*/
private static String getContentString(String content, String charset) {
if (charset == null || "".equals(charset)) {
return new String(content.getBytes());
}
try {
return new String(content.getBytes("ISO-8859-1"), charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("指定的編碼集不對,您目前指定的編碼集是:" + charset);
}
}
/**
* URL轉碼
* @param content
* @param charset
* @return
*/
private static String urlEncode(String content, String charset) {
try {
return URLEncoder.encode(content, charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("指定的編碼集不對,您目前指定的編碼集是:" + charset);
}
}
//TODO 簽名
/**
* 生成要請求的簽名參數數組
* @param sParaTemp 需要簽名的參數Map
* @return 要請求的簽名參數數組
*/
public static Map<String, String> signMap(Map<String, String[]> sParaTemp) {
//請求參數Map轉換驗證Map,並生成要請求的簽名參數數組
return SignUtil.getInstance().sign(toVerifyMap(sParaTemp, false));
}
/**
* 生成要請求的簽名參數數組
* @param sParaTemp 需要簽名的參數
* @return 要請求的簽名參數數組
*/
public Map<String, String> sign(Map<String, String> sParaTemp) {
//時間戳加入簽名參數組中
sParaTemp.put("timestamp", String.valueOf(System.currentTimeMillis()));
//除去數組中的空值和簽名參數
Map<String, String> sPara = paraFilter(sParaTemp);
//把數組所有元素,按照“參數=參數值”的模式用“&”字符拼接成字符串
String prestr = createLinkString(sPara);
//生成簽名結果
String mysign = DigestUtils.md5Hex(getContentBytes(prestr + appkey, INPUT_CHARSET));
//簽名結果加入請求提交參數組中
sPara.put("sign", mysign);
return sPara;
}
public static String getSignStr(Map<String, String> sParaTemp) {
return SignUtil.getInstance().sign(sParaTemp).get("sign");
}
/**
* 生成要請求的簽名參數字符串“參數=參數值”&鏈接
* @param sParaTemp 需要簽名的參數Map
* @return 請求的簽名參數字符串
*/
public static String signStringMap(Map<String, String[]> sParaTemp) {
//生成要請求的簽名參數數組
Map<String, String> sign = signMap(sParaTemp);
//生成要請求的簽名參數字符串“參數=參數值”&鏈接
return createLinkString(sign, true);
}
/**
* 生成要請求的簽名參數字符串“參數=參數值”&鏈接
* @param sParaTemp 需要簽名的參數
* @return
*/
public static String signString(Map<String, String> sParaTemp) {
//生成要請求的簽名參數數組
Map<String, String> sign = SignUtil.getInstance().sign(sParaTemp);
//生成要請求的簽名參數字符串“參數=參數值”&鏈接
return createLinkString(sign, true);
}
//TODO 驗證簽名
/**
* 根據反饋回來的信息,生成簽名結果
* @param paramsMap 通知返回來的請求參數Map
* @return 驗證結果
*/
public static boolean verifyMap(Map<String, String[]> paramsMap) {
//請求參數Map轉換驗證Map,並根據反饋回來的信息,生成簽名結果
return SignUtil.getInstance().verify(toVerifyMap(paramsMap, false));
}
/**
* 根據反饋回來的信息,生成簽名結果
* @param params 通知返回來的參數數組
* @return 驗證結果
*/
public boolean verify(Map<String, String> params) {
String sign = "";
if (params.get("sign") != null) {
sign = params.get("sign");
}else {
logger.info("sign is null");
return false;
}
String timestamp = "";
if (params.get("time") != null) {
timestamp = params.get("time");
}else {
return false;
}
//過濾空值、sign
Map<String, String> sParaNew = paraFilter(params);
//獲取待簽名字符串
String preSignStr = createLinkString(sParaNew);
//獲得簽名驗證結果
String mysign = DigestUtils.md5Hex(getContentBytes(preSignStr + appkey, INPUT_CHARSET));
if (mysign.equals(sign)) {
//是否超時
long curr = System.currentTimeMillis();
if ((curr - Long.valueOf(timestamp)) > timeout){
logger.info("api is time out" + curr);
return false;
}
return true;
} else {
return false;
}
}
}
接口控制器調用
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* @author wangwei
* @version v1.0.0
* @description 測試簽名用
* @date 2019-01-12
*/
@RestController
@RequestMapping("/api/open/rest/{version}/signtest")
public class SignTestController {
/**
* 模擬客戶端請求API接口
* @param request
* @return
*/
@RequestMapping("send")
public String send(HttpServletRequest request){
Map<String,String> param = new HashMap<>();
param.put("userId","9527");
param.put("amount","9.99");
param.put("productId","9885544154");
param.put("secretKey","mysecret123456");
try {
String postResult = HttpClient.get("http://127.0.0.1:8773/api/open/v1.0.1/ad/list", SignUtil.getInstance().sign(param));
return postResult;
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
/**
* showdoc
* @catalog v1.0.1/測試相關
* @title 模擬服務的API接口
* @description 模擬服務的API接口
* @method post
* @url /api/open/rest/v1.0.1/signtest/checkSign
* @param secretKey 必選 string 祕鑰
* @param time 必選 string 請求時間戳
* @param sign 必選 string 參數MD5簽名
* @return {"status":"200","message":"請求成功","data":,"page":null,"ext":null}
* @return_param code int 驗證碼
* @return_param status string 狀態
* @remark 這裏是備註信息
* @number 99
*/
@RequestMapping("checkSign")
public Resp checkSign(HttpServletRequest request){
//從request中獲取參數列表,轉成map
Map<String, String> map = SignUtil.toVerifyMap(request.getParameterMap(),false);
String secretKey = map.get("secretKey");
if (StringUtils.isEmpty(secretKey) || !map.get("secretKey").equals(SignUtil.getInstance().secretkey)){
System.out.println("secretKey is err");
return Resp.fail(ErrorCode.CODE_431);
}
if (SignUtil.getInstance().verify(map)){
return Resp.success();
}else {
return Resp.fail(ErrorCode.CODE_430);
}
}
}
簽名驗證過濾器
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
/**
* @author wangwei
* @version v1.0.0
* @description 簽名過濾器, 簽名不攔截open下的接口
* @date 2019-01-12
*/
@Component
@WebFilter(filterName="SignFilter", urlPatterns="/*")
@RefreshScope
public class SignFilter implements Filter {
@Value("${version}")
private String version;
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
String path = request.getRequestURI();
if(path.indexOf("/api/open/")> -1 ){
filterChain.doFilter(servletRequest, servletResponse);
return;
}
Map<String, String> map = SignUtil.toVerifyMap(request.getParameterMap(),false);
String secretKey = map.get("secretKey");
if (StringUtils.isEmpty(secretKey) || !map.get("secretKey").equals(SignUtil.getInstance().secretkey)){
System.out.println("secretKey is err");
PrintWriter writer = null;
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html; charset=utf-8");
try {
writer = servletResponse.getWriter();
String userJson = "{\"code\":\" "+ ErrorCode.CODE_431.getCode() +"\", \"message\": \""+ ErrorCode.CODE_431.getMessage() +"\"}";
writer.print(userJson);
} catch (IOException e1) {
} finally {
if (writer != null)
writer.close();
}
}
if (SignUtil.getInstance().verify(map)){
// 簽名成功
filterChain.doFilter(servletRequest, servletResponse);
return;
}else {
PrintWriter writer = null;
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html; charset=utf-8");
try {
writer = servletResponse.getWriter();
String userJson = "{\"code\":\" "+ ErrorCode.CODE_430.getCode() +"\", \"message\": \""+ ErrorCode.CODE_430.getMessage() +"\"}";
writer.print(userJson);
} catch (IOException e1) {
} finally {
if (writer != null)
writer.close();
}
}
}
@Override
public void destroy() {
}
}