前言
現(xiàn)在的項(xiàng)目都采用前后端分類的方式開發(fā)了,前后端的通訊方式都通過API進(jìn)行傳輸兆龙。我們知道杖爽,如果是管理后臺的開發(fā),可以通過shiro或springSecurity進(jìn)行權(quán)限控制紫皇,進(jìn)而保證API接口的安全性慰安,但是,當(dāng)我們在進(jìn)行APP或小程序開發(fā)的時候聪铺,因?yàn)樾枰脩糸L期登錄等問題化焕,再采用shiro等方式進(jìn)行安全控制就顯得不是那么合理的。
可是铃剔,如何讓我們的API接口變得安全點(diǎn)撒桨?不至于當(dāng)其他人通過抓包的方式拿到你的userId或一些重要參數(shù)的時候,對你的數(shù)據(jù)進(jìn)行破壞番宁。
那么元莫,API接口的簽名校驗(yàn)赖阻,將會是你阻擋這些破壞的一堵墻蝶押。
下面,讓我們開始API簽名校驗(yàn)之旅吧火欧。
基礎(chǔ)準(zhǔn)備
首先棋电,我們先要了解一下普通的API接口是如何訪問的(以POSTMAN為例)。
下面貼一下代碼
@RestController
@RequestMapping(value = "/sign")
public class SignController {
/**
* 驗(yàn)簽測試
*
* @return
*/
@RequestMapping(value = "/test")
public String test(String name) {
return name;
}
}
代碼很簡潔苇侵,無需多言赶盔,我想,在沒進(jìn)行簽名校驗(yàn)的時候大多數(shù)人的代碼都是這樣的吧榆浓。
正式開始
實(shí)現(xiàn)效果
1于未、前端請求方式
我們需要用到的就是,http請求的 header陡鹃,將token(令牌) 和 timestamp(時間戳)作為參數(shù)烘浦,一起發(fā)送給我們的后端。然后后端對token和timestamp進(jìn)行校驗(yàn)萍鲸,校驗(yàn)通過后闷叉,才進(jìn)行的正式訪問。
2脊阴、后端處理方式
@RestController
@RequestMapping(value = "/sign")
public class SignController {
/**
* 驗(yàn)簽測試
*
* @return
*/
@SignatureValidation
@RequestMapping(value = "/test")
public String test(String name) {
return name;
}
}
是的握侧,你沒有看錯蚯瞧,就多了一個注解(自定義的,不用去百度什么意思了)品擎。
@SignatureValidation
加上后埋合,看效果
好了,下面去看怎么實(shí)現(xiàn)的吧萄传。
實(shí)現(xiàn)原理
1饥悴、簽名規(guī)則
(1)、前后端都要統(tǒng)一一個秘鑰(secret)盲再,這個秘鑰是自己定義的西设,盡可能復(fù)雜點(diǎn)。這個可別泄露哦答朋。
(2)贷揽、我們需要準(zhǔn)備一個當(dāng)前時間戳 timeStamp,這個很好獲取梦碗,要注意的是禽绪,這個時間戳最好要精確到毫秒。
(3)洪规、我們要確定自己的加密方式印屁。可以使用MD5進(jìn)行加密斩例,你想加密幾次看心情雄人,讓他們猜不出來就行了。
(4)念赶、將秘鑰和時間戳拼接字符串础钠,然后通過你們約定的加密方式進(jìn)行加密,得到TOKEN
偽代碼(一次加密為例)
token = MD5(secret+ timeStamp);
思考:如果一個人通過抓包的方式拿到了你的接口(header 中的token 和 timestamp)叉谜,他如何才能進(jìn)行破解旗吁?
第一:他要知道我們的秘鑰(secret),只要你設(shè)計(jì)的夠復(fù)雜,靠猜是猜不出來的停局。
第二:他要知道我們的加密方式很钓,我這里用MD5這種常規(guī)加密,你們可以換個加密方式董栽,最好是非對稱加密码倦。
第三:他要知道我們的加密次數(shù)。
第四:他要知道我們的加密規(guī)則
如果不是特別重要的接口或者專門要搞你裆泳,大部分會退縮的叹洲。當(dāng)然少部分就會通過反編譯你的源碼去拿到這些數(shù)據(jù),或者意外泄露工禾,那你只能自己去加強(qiáng)相關(guān)的防護(hù)去唄运提』热幔混淆文件啦,應(yīng)用加固啦民泵,用膠帶粘住嘴啦癣丧,方法很多,自己去慢慢研究栈妆。
2胁编、Springboot 實(shí)現(xiàn)(大家最喜歡的環(huán)節(jié),一步步去復(fù)制代碼到自己項(xiàng)目中去吧)
1鳞尔、新建一個文件嬉橙,定義注解接口
package com.xxx.aop;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author tangn
* 小程序請求認(rèn)證
*/
@Retention(value = RetentionPolicy.RUNTIME)
public @interface SignatureValidation {
}
其中,一定要注意包名(根據(jù)你放這個文件位置)寥假,這個下面會用到市框。
package com.xxx.aop;
2、使用aspect進(jìn)行切點(diǎn)攔截(不知道其實(shí)現(xiàn)原理糕韧,去百度吧)
@Aspect
@Component
public class SignatureValidation {
/**
* 時間戳請求最小限制(30s)
* 設(shè)置的越小枫振,安全系數(shù)越高,但是要注意一定的容錯性
*/
private static final long MAX_REQUEST = 30 * 1000L;
/**
* 秘鑰
*/
private static final long SECRET= "前后端約定的秘鑰";
/**
* 驗(yàn)簽切點(diǎn)(完整的找到設(shè)置的文件地址)
*/
@Pointcut("execution(@com.xxx.aop.SignatureValidation * *(..))")
private void verifyUserKey() {
}
/**
* 開始驗(yàn)簽
*/
@Before("verifyUserKey()")
public void doBasicProfiling() {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String token = request.getHeader("token");
String timestamp = request.getHeader("timestamp");
try {
Boolean check = checkToken(token, timestamp);
if (!check) {
// 自定義異常拋出(開發(fā)者自行換成自己的即可)
throw new MyException(ResultEnums.ERROR, "簽名驗(yàn)證錯誤");
}
} catch (Throwable throwable) {
// 自定義異常拋出(開發(fā)者自行換成自己的即可)
throw new PlbException(ResultEnums.ERROR, "簽名驗(yàn)證錯誤");
}
}
/**
* 校驗(yàn)token
*
* @param token 簽名
* @param timestamp 時間戳
* @return 校驗(yàn)結(jié)果
*/
private Boolean checkToken(String token, String timestamp) {
if (StringUtils.isAnyBlank(token, timestamp)) {
return false;
}
long now = System.currentTimeMillis();
long time = Long.parseLong(timestamp);
if (now - time > MAX_REQUEST) {
log.error("時間戳已過期[{}][{}][{}]", now, time, (now - time));
return false;
}
String crypt = MD5Utils.getMD5(SECRET+ timestamp);
return StringUtils.equals(crypt, token);
}
}
下面說一下用到的工具類:
MD5Utils
每個項(xiàng)目里面都有吧萤彩,沒有的話網(wǎng)上一搜就行了粪滤。
StringUtils
其實(shí)很簡單,自己寫也行雀扶,引入第三方的也行杖小,我是引用的apache的,你們也可試試怕吴。
pom.xml 文件
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
好了窍侧,你又沒有看錯县踢。這樣結(jié)束了转绷。這樣你就有了自己的簽名校驗(yàn)工具類,快拿到你的項(xiàng)目中試試去吧硼啤。
然后你以后的API接口议经,只需要加上這個注解就能進(jìn)行簽名驗(yàn)證了。
@SignatureValidation
總結(jié)
很多時候谴返,我們在開發(fā)中都只關(guān)心業(yè)務(wù)煞肾,對安全性的問題很可能就忽略了,其實(shí)我們只需要對自己的API接口稍微一處理嗓袱,有可能就會避免一些安全問題籍救。從而避免一些不必要的損失。好吧渠抹,就到這里蝙昙,安心睡覺去吧闪萄。
源碼
https://gitee.com/bean1995/signature_verification
在 tangn_init_20200314 分支
多說一句:看到很多給樓主評論的(可以去看評論區(qū)的討論),加簽是為了讓項(xiàng)目多一層防護(hù)奇颠,具體還要取決于項(xiàng)目的重要程度败去,如果涉及到資金、秘鑰等項(xiàng)目烈拒,可以采用更安全的加密方式圆裕,或者前端加強(qiáng)校驗(yàn)。android荆几、ios加殼吓妆,h5做混淆等。這篇文章主要還是想將非侵入的通過注解方式引入簽名吨铸,具體怎么個簽法耿战,要根據(jù)項(xiàng)目來定呦~