如何使用Spring Boot實(shí)現(xiàn)接口時(shí)間戳鑒權(quán)
Spring Boot實(shí)現(xiàn)接口時(shí)間戳鑒權(quán),簽名(sign)和時(shí)間戳(ts)放入請求頭(Header)。
一、請求頭參數(shù)設(shè)計(jì)
| 參數(shù)名 | 類型 | 說明 |
|---|---|---|
| ts | Long | 13位時(shí)間戳(Unix毫秒值),必填,標(biāo)識(shí)請求有效期 |
| sign | String | 簽名值,必填,用于校驗(yàn)請求合法性 |
二、簽名算法調(diào)整(Header版)
1. 構(gòu)造原始字符串
str = key + url_encode(path) + ts
path:請求的URL路徑部分(不含查詢參數(shù)和域名),例如:/api/v1/user。
url_encode(path):需對路徑中的特殊字符進(jìn)行URL編碼(如中文、/等無需編碼,但空格需轉(zhuǎn)義為%20)。
2. 生成簽名 SIGN
sgin = md5(S).toLowerCase()
結(jié)果轉(zhuǎn)換為小寫字符串,與請求頭中的sign對比校驗(yàn)。
三、Spring Boot實(shí)現(xiàn)流程
1. 創(chuàng)建攔截器(Interceptor)
用于攔截請求,校驗(yàn)ts和sign的合法性。
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Objects;
public class AuthInterceptor implements HandlerInterceptor {
private final String secretKey; // 從配置文件獲取密鑰,例如application.properties
private final long maxClockSkew = 60 * 1000; // 時(shí)間窗口:60秒(允許客戶端與服務(wù)端的時(shí)間誤差)
public AuthInterceptor(String secretKey) {
this.secretKey = secretKey;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 1. 校驗(yàn)時(shí)間戳是否存在
String ts = request.getHeader("ts");
if (StringUtils.isEmpty(ts)) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType("application/json");
// 返回錯(cuò)誤信息:缺少時(shí)間戳
return false;
}
// 2. 校驗(yàn)時(shí)間戳格式及有效性
long ts;
try {
ts = Long.parseLong(ts );
} catch (NumberFormatException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
// 返回錯(cuò)誤信息:時(shí)間戳格式錯(cuò)誤
return false;
}
long currentTime = System.currentTimeMillis();
if (currentTime - ts > maxClockSkew|| ts - currentTime > maxClockSkew) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 返回錯(cuò)誤信息:請求已過期
return false;
}
// 3. 校驗(yàn)簽名是否存在
String sign= request.getHeader("sign");
if (StringUtils.isEmpty(sign)) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
// 返回錯(cuò)誤信息:缺少簽名
return false;
}
// 4. 構(gòu)造原始簽名字符串并生成簽名
String path = request.getRequestURI(); // 獲取路徑,例如"/api/v1/user"
String encodedPath = URLEncoder.encode(path, StandardCharsets.UTF_8.toString())
.replace("+", "%20") // 處理空格編碼差異(URLEncoder默認(rèn)用+,此處統(tǒng)一為%20)
.replace("%7E", "~"); // 保留波浪線~的原始格式
String rawString = secretKey + encodedPath + tsHeader;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(rawString.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b)); // 轉(zhuǎn)換為小寫16進(jìn)制字符串
}
String generatedSign = sb.toString();
// 5. 對比簽名
if (!generatedSign.equals(sign)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 返回錯(cuò)誤信息:簽名校驗(yàn)失敗
return false;
}
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return false;
}
return true; // 校驗(yàn)通過,允許請求繼續(xù)
}
}
2. 配置攔截器(WebMvcConfigurer)
將攔截器注冊到Spring Boot的攔截器鏈中,指定需要鑒權(quán)的接口路徑。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final String secretKey; // 從配置文件獲取密鑰,例如通過@Value注入
public WebConfig(String secretKey) {
this.secretKey = secretKey;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
AuthInterceptor authInterceptor = new AuthInterceptor(secretKey);
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/v1/**") // 需要鑒權(quán)的接口路徑(如/api/v1下所有接口)
.excludePathPatterns("/api/v1/public/**"); // 無需鑒權(quán)的公共接口(可選)
}
}
3. 配置文件(application.properties)
# 密鑰(建議從環(huán)境變量或配置中心獲取,而非硬編碼) auth.secret-key=your_secret_key_here
四、客戶端請求示例(以Postman為例)
1. 請求頭參數(shù)
| 名稱 | 值 |
|---|---|
| ts | 1686048000000(當(dāng)前時(shí)間戳,13位Long) |
| sign | 計(jì)算得到的簽名值(如e10adc3949ba59abbe56e057f20f883e) |
2. 簽名計(jì)算步驟(JavaScript示例)
function generateSignature(key, path, ts) {
const encodedPath = encodeURIComponent(path).replace(/[!'()*]/g, encodeURIComponent); // 嚴(yán)格編碼特殊字符
const rawString = key + encodedPath + ts;
const md5 = require('crypto-js/md5'); // 需要安裝crypto-js庫
return md5(rawString).toString().toLowerCase();
}
// 示例調(diào)用
const key = 'your_secret_key';
const path = '/api/v1/user'; // 接口路徑
const ts = Date.now(); // 當(dāng)前時(shí)間戳(13位)
const sign = generateSignature(key, path, ts);
console.log(sign); // 輸出簽名值
到此這篇關(guān)于如何使用Spring Boot實(shí)現(xiàn)接口時(shí)間戳鑒權(quán)的文章就介紹到這了,更多相關(guān)Spring Boot接口時(shí)間戳鑒權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot?根據(jù)配置決定服務(wù)(集群、單機(jī))是否使用某些主件的操作代碼
這篇文章主要介紹了Spring?Boot根據(jù)配置決定服務(wù)(集群、單機(jī))是否使用某些主件,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2025-04-04
spring中自動(dòng)注入注解的實(shí)現(xiàn)方式
在Spring框架中,AutowiredAnnotationBeanPostProcessor負(fù)責(zé)處理@Autowired和@Value注解,實(shí)現(xiàn)依賴注入,首先通過TypeMappedAnnotations獲取注解,并根據(jù)注解屬性構(gòu)建InjectionMetadata,存入緩存2024-09-09
Java服務(wù)調(diào)用失敗報(bào)Service com.oneinfinite.adflow.api.service.T
在Java開發(fā)中,服務(wù)調(diào)用是常見的操作,尤其是在微服務(wù)架構(gòu)中,然而,服務(wù)調(diào)用過程中可能會(huì)遇到各種問題,下面我們來看看如何解決Service com.oneinfinite.adflow.api.service.TestService with version 0.0.0 not found的問題吧2025-03-03
Mybatis-Plus動(dòng)態(tài)表名的實(shí)現(xiàn)示例
面對復(fù)雜多變的業(yè)務(wù)需求,動(dòng)態(tài)表名的處理變得愈發(fā)重要,本文主要介紹了Mybatis-Plus動(dòng)態(tài)表名的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07
Java Web十條開發(fā)實(shí)用小知識(shí)
這篇文章主要介紹了Java Web十條開發(fā)實(shí)用小知識(shí)的相關(guān)資料,需要的朋友可以參考下2016-05-05

