Spring Boot實現(xiàn)接口簽名驗證的過程
項目場景:
開放接口是指不需要登錄憑證就允許被第三方系統(tǒng)調(diào)用的接口。為了防止開放接口被惡意調(diào)用,開放接口一般都需要驗簽才能被調(diào)用。
在Spring Boot中實現(xiàn)接口校驗簽名通常是為了保證接口請求的安全性和數(shù)據(jù)的完整性。簽名校驗通常涉及對請求參數(shù)的簽名計算和驗證,以確保請求是由可信的發(fā)送方發(fā)送,并且在傳輸過程中沒有被篡改。下面,我將詳細介紹如何在Spring Boot應(yīng)用中實現(xiàn)接口校驗簽名的過程。
解決方案:
1.配置簽名密鑰
簽名密鑰是用于生成和驗證簽名的秘密信息,生成規(guī)則自己定義,需要把生成的密鑰提供給第三方。
比如:
appId:360aa3a3ba074da6a7bb17ae55e72d26
appSecret:81343DC5-6E80-483A-A427-E3DF5FA4E5F3
/**
* 生成應(yīng)用id和密鑰
*/
@RequestMapping(value = "/getSecret", method = RequestMethod.GET)
public Map<String, String> getSecret() {
//生成應(yīng)用id和密鑰提供給第三方使用,具體生成規(guī)則自己定
String appId = UUID.randomUUID().toString().replace("-", "").toLowerCase();
String appSecret = UUID.randomUUID().toString().toUpperCase();
System.out.println("appId:"+appId);
System.out.println("appSecret:"+appSecret);
Map<String, String> map = new HashMap<>();
map.put("appId", appId);
map.put("appSecret", appSecret);
return map;
}2.定義簽名算法
一個用于生成簽名,另一個用于驗證簽名。生成簽名的方法通常將請求參數(shù)按照特定規(guī)則計算出一個簽名值。常見的簽名算法有HMAC-SHA1、HMAC-SHA256等。
驗證簽名的方法則是對接收到的請求參數(shù)進行同樣的處理,并計算出一個簽名值,然后與請求中攜帶的簽名值進行比對。
package com.test.utils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Signature {
/**
* 獲取簽名
* @param secretKey 密鑰
* @param data 需要簽名的數(shù)據(jù)
* @return 簽名
*/
public static String signWithHmacSha1(String secretKey, String data) {
try {
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes("UTF-8")));
} catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 驗證簽名
* @param secretKey 密鑰
* @param data 需要簽名的數(shù)據(jù)
* @param hmac 已經(jīng)簽名的數(shù)據(jù)
* @return true:簽名一致
*/
public static boolean verify(String secretKey, String data, String hmac) {
String calculatedHmac = signWithHmacSha1(secretKey, data);
return calculatedHmac.equals(hmac);
}
}3.攔截器或過濾器實現(xiàn)
使用Spring的攔截器(Interceptor)或過濾器(Filter)來實現(xiàn)對接口請求的簽名校驗。在攔截器或過濾器中,你可以獲取到請求的參數(shù),并調(diào)用簽名驗證方法來校驗簽名的有效性。
package com.test.aop;
import com.test.utils.Signature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 簽名攔截器
*/
@Component
@Slf4j
public class SignInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//分配的應(yīng)用id
String appId = request.getHeader("appId");
//時間戳
String timestampStr = request.getHeader("timestamp");
//簽名
String signature = request.getHeader("signature");
if(StringUtils.isBlank(appId) || StringUtils.isBlank(timestampStr) || StringUtils.isBlank(signature)){
response.setStatus(500);
response.getWriter().println("參數(shù)錯誤!");
return false;
}
//這個密鑰實際應(yīng)該根據(jù)appId到數(shù)據(jù)庫里查出來
String appSecret = "81343DC5-6E80-483A-A427-E3DF5FA4E5F3";
//如果密鑰沒查到
// if(flag){
// response.setStatus(500);
// response.getWriter().println("密鑰不存在!");
// }
//拼接數(shù)據(jù)
String origin = appId + "\n" + appSecret + "\n" + timestampStr;
if(!Signature.verify(appSecret, origin, signature)){
response.setStatus(500);
response.getWriter().println("簽名錯誤!");
return false;
}
//業(yè)務(wù)的時間戳
long timestamp = Long.parseLong(timestampStr);
//當(dāng)前時間戳
long currentTimestamp = System.currentTimeMillis() / 1000;
//10分鐘內(nèi)有效
long timeDifference = 10 * 60;
if(Math.abs(timestamp - currentTimestamp) > timeDifference){
response.setStatus(500);
response.getWriter().println("簽名過期!");
return false;
}
//放行
return true;
}
}配置攔截器
package com.test.config;
import com.test.aop.SignInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* WebMvc配置
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Resource
private SignInterceptor signInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//不攔截的地址
List<String> excludedList = new ArrayList<>();
//swagger地址
excludedList.add("/swagger-ui.html");
excludedList.add("/swagger-ui.html/**");
excludedList.add("/webjars/**");
excludedList.add("/swagger/**");
excludedList.add("/doc.html");
excludedList.add("/doc.html/**");
excludedList.add("/swagger-resources/**");
excludedList.add("/v2/**");
excludedList.add("/favicon.ico");
//生成應(yīng)用id和密鑰接口不攔截
excludedList.add("/getSecret");
registry.addInterceptor(signInterceptor)
.addPathPatterns("/**")//攔截所有請求
.excludePathPatterns(excludedList);//排除的請求
super.addInterceptors(registry);
}
}4.測試接口
controller定義一個測試接口
/**
* 測試簽名
*/
@RequestMapping(value = "/sign", method = RequestMethod.GET)
public String sign() {
return "success";
}模擬第三方調(diào)用:
我們需要把接口請求頭所需參數(shù)和簽名的方法告知第三方。
接口請求頭參數(shù)如下:
| 參數(shù)名稱 | 中文 | 參數(shù)值 |
|---|---|---|
| appId | 應(yīng)用id | 360aa3a3ba074da6a7bb17ae55e72d26 |
| timestamp | 當(dāng)前時間戳,精確到秒 | 1713838208 |
| signature | 簽名 | bjvXebFiHi2+I93BNs+8+Tl2I7k= |
簽名方法:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Signature {
/**
* 獲取簽名
* @param secretKey 密鑰
* @param data 需要簽名的數(shù)據(jù)
* @return 簽名
*/
public static String signWithHmacSha1(String secretKey, String data) {
try {
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes("UTF-8")));
} catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}生成簽名示例:
public static void main(String[] args) {
//這里的應(yīng)用id和密鑰已實際分配的為準
String appId = "360aa3a3ba074da6a7bb17ae55e72d26";
String appSecret = "81343DC5-6E80-483A-A427-E3DF5FA4E5F3";
//當(dāng)前時間戳,精確到秒,示例:1713838208
long timestamp = System.currentTimeMillis() / 1000;
//拼接數(shù)據(jù):appId、appSecret、timestamp
String origin = appId + "\n" + appSecret + "\n" + timestamp;
String signature = Signature.signWithHmacSha1(appSecret, origin);
//需要加到請求頭的參數(shù)
System.out.println("appId:"+appId);
System.out.println("timestamp:"+timestamp);
System.out.println("signature:"+signature);
}當(dāng)?shù)谌街澜涌谡埱箢^所需參數(shù)和簽名的方法后,就可以調(diào)用接口了
curl調(diào)用:
curl -X GET \ http://localhost:8080/testservice/test/sign \ -H 'appId: 360aa3a3ba074da6a7bb17ae55e72d26' \ -H 'signature: bjvXebFiHi2+I93BNs+8+Tl2I7k=' \ -H 'timestamp: 1713843128'
總結(jié) 這里簽名由appId + "\n" + appSecret + "\n" + timestamp,生成簽名字串時間戳用于保證簽名的有效性,即使簽名被盜用,也只能在有效時間內(nèi)使用appId、appSecret自己定義生成規(guī)則,保存到數(shù)據(jù)庫中
源碼百度網(wǎng)盤下載地址:
鏈接: https://pan.baidu.com/s/1dhtbspb3AC3M_sWBG2D-Xg?pwd=k57m
提取碼: k57m
到此這篇關(guān)于Spring Boot實現(xiàn)接口簽名驗證的文章就介紹到這了,更多相關(guān)Spring Boot接口簽名驗證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java serialVersionUID解決序列化類版本不一致問題面試精講
這篇文章主要為大家介紹了serialVersionUID解決序列化類版本不一致問題的面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10
如何在攔截器中獲取url路徑里面@PathVariable的參數(shù)值
這篇文章主要介紹了如何在攔截器中獲取url路徑里面@PathVariable的參數(shù)值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

