java實現(xiàn)Api接口加密通信方式
接口加密通信思路
1)約定雙方通信的秘鑰,如:appKey = wenzhou
2)通信安全校驗通過簽名sign
1.生成時間戳、隨機數(shù)或隨機字符串等,如:時間戳:time=677899002
2.將通信秘鑰、時間戳、接口傳遞的參數(shù)通過雙方約定的拼接方式拼接在一起,如:
- 約定方式例一:
key1=value1&key2=value2&key3=&key4=value4&appKey=wenzhou&time=677899002&
- 約定方式例二:
key1value1key2value2key3value3key4value4appKeywenzhoutime677899002
除此之外還有很多,用戶自己約定即可
3.約定簽名的復(fù)雜規(guī)則,如:可以替換掉拼接字符串的指定字符、字符串前后字符對調(diào)、字符左移或右移等
- 如給約定方式例一做字符串對調(diào):
&200998776=emit&uohznew=yeKppa&4eulav=4yek&=3yek&2eulav=2yek&1eulav=1yek
- 如給約定方式例二做字符串中y字符替換為*:
ke*1value1ke*2value2ke*3value3ke*4value4appKe*wenzhoutime677899002
方式很多,用戶自行約定即可,此步驟也可以不做
4.雙方約定使用相同的加密方式,對最終字符串進行加密,生成簽名sign(還可以將生成的簽名全部大寫一下)
MD5 、Base64 、RSA 等等加密算法或者自己編寫加密算法
3)調(diào)用方將用上述方法生成的簽名連同參與生成簽名的接口傳參的參數(shù)一起傳遞給接收方
4)接收方使用相同的方式生成簽名,對比簽名是否一致
5)接收方校驗appKey是否一致
6)接收方校驗時間戳time加上指定時長,如時間戳加5秒(時間戳有效時長為5秒),若當(dāng)前時間已經(jīng)大于時間戳加5秒后的時間,則此簽名過期,拒絕訪問
約定雙方通信的秘鑰(接口提供方,需將appKey配置下來)
這里,我配置在application.properties中。小伙伴可根據(jù)自己需要自行決定配置方式
MD5加密工具類(使用MD5加密生成簽名)
這里加密拼接后的字符串生成簽名,加密方式有很多種,如:RSA 、MD5、Base64
package com.gwssi.common.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.MessageDigest; public class MD5Util { final static Logger log = LoggerFactory.getLogger(MD5Util.class); /** * 十六進制下數(shù)字到字符的映射數(shù)組 */ private final static String[] HEX_DIGITS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; /** * md5加密算法 * @param originString 待加密字符串 * @return 加密后的字符串 */ public static String encodeByMD5(String originString){ if (originString != null){ try{ //創(chuàng)建具有指定算法名稱的信息摘要 MessageDigest md = MessageDigest.getInstance("MD5"); //使用指定的字節(jié)數(shù)組對摘要進行最后更新,然后完成摘要計算 byte[] results = md.digest(originString.getBytes()); //將得到的字節(jié)數(shù)組變成字符串返回 String resultString = byteArrayToHexString(results); return resultString.toUpperCase(); } catch(Exception ex){ log.error("md5加密算法失敗", ex); } } return null; } /** * 轉(zhuǎn)換字節(jié)數(shù)組為十六進制字符串 * @param * @return 十六進制字符串 */ private static String byteArrayToHexString(byte[] b){ StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++){ resultSb.append(byteToHexString(b[i])); } return resultSb.toString(); } /** 將一個字節(jié)轉(zhuǎn)化成十六進制形式的字符串 */ private static String byteToHexString(byte b){ int n = b; if (n < 0) { n = 256 + n; } int d1 = n / 16; int d2 = n % 16; return HEX_DIGITS[d1] + HEX_DIGITS[d2]; } }
api接口提供方演示接口簽名校驗
package com.gwssi.device.component; import com.gwssi.common.entity.Constant; import com.gwssi.common.entity.ResponseCode; import com.gwssi.common.entity.Result; import com.gwssi.common.utils.BgUtils; import com.gwssi.common.utils.MD5Util; import com.gwssi.common.utils.ThreadCache; import net.sf.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Map; @Component public class WenZhouComponent { Logger log = LoggerFactory.getLogger(WenZhouComponent.class); @Value("${wenzhou.appKey}") String localAppKey; @SuppressWarnings("unchecked") public Map<String,Object> wenzhouApis(){ //獲取請求參數(shù) JSONObject js = JSONObject.fromObject(ThreadCache.getData(Constant.HTTP_PARAM)); Map<String,Object> map = (Map<String,Object>) js; //map的數(shù)據(jù)是調(diào)取方傳遞的所有參數(shù),_sign調(diào)取方通過約定規(guī)則生成的簽名,time調(diào)取方生成的時間戳,appKey雙方約定的秘鑰,type為普通必傳參數(shù) //必傳參數(shù)校驗 if(!BgUtils.cheakParamIsNull(map, Arrays.asList("_sign","time","type","appKey"))){ Result.putValue(ResponseCode.CodeEnum.REQUIRED_PARAM_NULL); return null; } //簽名校驗 if(!checkSign(map.get("_sign").toString(),map.get("appKey").toString(),Long.parseLong(map.get("time").toString()),map)){ return null; } int type = Integer.parseInt(map.get("type").toString()); //進行接口主體操作 return null; } //api連接安全檢驗 private boolean checkSign(String getSign,String appKey,long time,Map<String,Object> map){ //接收方校驗appKey是否一致 //appKey配置是否一致 log.info("配置本地appkey:"+localAppKey+" 傳遞appkey:"+appKey); if(!appKey.equals(localAppKey)){ Result.putValue(ResponseCode.CodeEnum.APP_KEY_ERROR); return false; } //接收方使用相同的方式生成簽名,對比簽名是否一致 //此處簽名規(guī)則是,將所有傳入的參數(shù)(除sign簽名參數(shù)),按照順序,依次key=value&拼接在一起,然后進行MD5加密,再將加密后的結(jié)果全部大寫,即為 簽名生成規(guī)則 StringBuffer str = new StringBuffer(""); map.keySet().stream().filter(c->!c.equals("_sign")).forEach(c->str.append(c).append("=").append(map.get(c).toString()).append("&")); String mySign = MD5Util.encodeByMD5(str.toString()); //簽名計算規(guī)則是否一致 log.info("加密字符串:"+str.toString()+" 計算簽名:"+mySign+" 傳遞簽名:"+getSign); if(!getSign.equals(mySign.toUpperCase())){ Result.putValue(ResponseCode.CodeEnum.PARAM_SIGN_INCORRECT); return false; } //接收方校驗時間戳time加上指定時長,如時間戳加5秒(時間戳有效時長為5秒),若當(dāng)前時間已經(jīng)大于時間戳加5秒后的時間,則此簽名過期,拒絕訪問 //簽名過期判斷(時間戳有效期當(dāng)且僅有5秒) if(new Date(time+5000).compareTo(new Date()) < 0){ log.info("簽名已在"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(time+5000))+"后過期"); Result.putValue(ResponseCode.CodeEnum.SIGN_EXPIRE); return false; } return true; } }
api接口地址演示
@ApiOperation("溫州對外提供的api接口地址封裝") @RequestMapping("/device/wenzhouApis") public void wenzhouApis(){ wenZhouComponent.wenzhouApis(); }
請求演示
模擬調(diào)取方請求:
接收方:
2021-05-18 17:34:24.682 [ http-nio-9080-exec-2 ] - [ INFO ] [ o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] : 180 ] - Initializing Spring FrameworkServlet 'dispatcherServlet'
2021-05-18 17:34:24.683 [ http-nio-9080-exec-2 ] - [ INFO ] [ org.springframework.web.servlet.DispatcherServlet : 494 ] - FrameworkServlet 'dispatcherServlet': initialization started
2021-05-18 17:34:24.761 [ http-nio-9080-exec-2 ] - [ INFO ] [ org.springframework.web.servlet.DispatcherServlet : 509 ] - FrameworkServlet 'dispatcherServlet': initialization completed in 78 ms
2021-05-18 17:34:24.891 [ http-nio-9080-exec-2 ] - [ INFO ] [ com.gwssi.filter.ParameterFilter : 79 ] - http-nio-9080-exec-2 --- Request Begin --- /device/wenzhouApis,body={
"_sign":"EC7ED24DF32F306E8F4E822E263289A3",
"time":"677899002",
"type":"1",
"appKey":"wenzhou"
}
2021-05-18 17:34:24.891 [ http-nio-9080-exec-2 ] - [ INFO ] [ com.gwssi.filter.ParameterFilter : 83 ] - http-nio-9080-exec-2 --- Request Begin --- /device/wenzhouApis,sysuid=
2021-05-18 17:34:25.053 [ http-nio-9080-exec-2 ] - [ INFO ] [ com.gwssi.device.component.WenZhouComponent : 58 ] - 配置本地appkey:wenzhou 傳遞appkey:wenzhou
2021-05-18 17:34:25.057 [ http-nio-9080-exec-2 ] - [ INFO ] [ com.gwssi.device.component.WenZhouComponent : 67 ] - 加密字符串:time=677899002&type=1&appKey=wenzhou& 計算簽名:EC7ED24DF32F306E8F4E822E263289A3 傳遞簽名:EC7ED24DF32F306E8F4E822E263289A3
2021-05-18 17:34:25.058 [ http-nio-9080-exec-2 ] - [ INFO ] [ com.gwssi.device.component.WenZhouComponent : 75 ] - 簽名已在1970-01-09 04:18:24后過期
2021-05-18 17:34:25.149 [ http-nio-9080-exec-2 ] - [ INFO ] [ com.gwssi.interceptor.ParameterInterceptor : 33 ] - http-nio-9080-exec-2 --- /device/wenzhouApis --- Request End --- {"code":14,"data":null,"msg":"簽名過期"}
2021-05-18 17:34:25.150 [ http-nio-9080-exec-2 ] - [ INFO ] [ com.gwssi.interceptor.ParameterInterceptor : 34 ] - http-nio-9080-exec-2 --- /device/wenzhouApis --- Request Time --- 2021-05-18 17:34:24.905 used 245ms
在線MD5加密展示:可以看到跟我們使用MD5Util加密結(jié)果一致
總結(jié)
1)接口的提供方和調(diào)取方約定好統(tǒng)一的參數(shù)加密算法,得到一個簽名sign
2)調(diào)取方在調(diào)取參數(shù)時,將計算簽名sign的參數(shù)、需要的參數(shù)、簽名一起傳遞給接口
3)接口提供方將拿到的參數(shù)按照約定的參數(shù)加密算法進行加密得到一個簽名sign,比較兩個簽名是否一致,不一致,則接口停止剩下的操作
4)簽名校驗通過以后,如果用戶還有簽名有效時長設(shè)置,還可以要求提供方傳遞時間戳(要求接收方與提供方時間保持一致或者計算時冗余時間差),時間戳+有效時長 對比當(dāng)前時間,若小于當(dāng)前時長,時間戳過期,則接口停止剩下的操作
5)為了進一步保證安全性,也可以雙方約定公鑰用于加密,此公鑰一般可以自己保存不作為參數(shù)傳遞(本案例作為參數(shù)傳遞了,一般不建議)
6)推介使用https協(xié)議代替http
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot實現(xiàn)接口數(shù)據(jù)的加解密功能
這篇文章主要介紹了SpringBoot實現(xiàn)接口數(shù)據(jù)的加解密功能,對接口的加密解密操作主要有兩種實現(xiàn)方式,文中給大家詳細介紹,需要的朋友可以參考下2019-10-10基于@RequestParam注解之Spring MVC參數(shù)綁定的利器
這篇文章主要介紹了基于@RequestParam注解之Spring MVC參數(shù)綁定的利器,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03自定義注解和springAOP捕獲Service層異常,并處理自定義異常操作
這篇文章主要介紹了自定義注解和springAOP捕獲Service層異常,并處理自定義異常操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06