日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

問題的起源

在直播服務(wù)中,有一個(gè)敏感詞的檢測(cè)的需求:當(dāng)用戶發(fā)送聊天消息之前,調(diào)用接口驗(yàn)證消息是否包含敏感詞,我們使用了阿里云的文本安全服務(wù),這是一個(gè)按照次數(shù)收費(fèi)的服務(wù),所以接口要求防止參數(shù)篡改和重放攻擊

API重放攻擊: 就是把之前竊聽到的數(shù)據(jù)原封不動(dòng)的重新發(fā)送給接收方(測(cè)試大佬肯定知道)

常用的其他業(yè)務(wù)場(chǎng)景還有:

  • 發(fā)送短信接口
  • 支付接口

基于timestamp和nonce的方案

微信支付的接口就是這樣做的

timestamp的作用

每次HTTP請(qǐng)求,都需要加上timestamp參數(shù),然后把timestamp和其他參數(shù)一起進(jìn)行數(shù)字簽名。HTTP請(qǐng)求從發(fā)出到達(dá)服務(wù)器一般都不會(huì)超過60s,所以服務(wù)器收到HTTP請(qǐng)求之后,首先判斷時(shí)間戳參數(shù)與當(dāng)前時(shí)間相比較,是否超過了60s,如果超過了則認(rèn)為是非法的請(qǐng)求。

一般情況下,從抓包重放請(qǐng)求耗時(shí)遠(yuǎn)遠(yuǎn)超過了60s,所以此時(shí)請(qǐng)求中的timestamp參數(shù)已經(jīng)失效了,如果修改timestamp參數(shù)為當(dāng)前的時(shí)間戳,則signature參數(shù)對(duì)應(yīng)的數(shù)字簽名就會(huì)失效,因?yàn)椴恢篮灻罔€,沒有辦法生成新的數(shù)字簽名。

但這種方式的漏洞也是顯而易見的,如果在60s之后進(jìn)行重放攻擊,那就沒辦法了,所以這種方式不能保證請(qǐng)求僅一次有效

nonce的作用

nonce的意思是僅一次有效的隨機(jī)字符串,要求每次請(qǐng)求時(shí),該參數(shù)要保證不同。我們將每次請(qǐng)求的nonce參數(shù)存儲(chǔ)到一個(gè)“集合”中,每次處理HTTP請(qǐng)求時(shí),首先判斷該請(qǐng)求的nonce參數(shù)是否在該“集合”中,如果存在則認(rèn)為是非法請(qǐng)求。

nonce參數(shù)在首次請(qǐng)求時(shí),已經(jīng)被存儲(chǔ)到了服務(wù)器上的“集合”中,再次發(fā)送請(qǐng)求會(huì)被識(shí)別并拒絕。

nonce參數(shù)作為數(shù)字簽名的一部分,是無(wú)法篡改的,因?yàn)椴恢篮灻罔€,沒有辦法生成新的數(shù)字簽名。

這種方式也有很大的問題,那就是存儲(chǔ)nonce參數(shù)的“集合”會(huì)越來(lái)越大。

nonce的一次性可以解決timestamp參數(shù)60s(防止重放攻擊)的問題,timestamp可以解決nonce參數(shù)“集合”越來(lái)越大的問題。

防篡改、防重放攻擊 攔截器

@Slf4j
public class SignAuthInterceptor implements HandlerInterceptor {

    private redisTemplate<String, String> redisTemplate;

    private String key;

    public SignAuthInterceptor(RedisTemplate<String, String> redisTemplate, String key) {
        this.redisTemplate = redisTemplate;
        this.key = key;
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        // 獲取時(shí)間戳
        String timestamp = request.getHeader("timestamp");
        // 獲取隨機(jī)字符串
        String nonceStr = request.getHeader("nonceStr");
        // 獲取簽名
        String signature = request.getHeader("signature");

        // 判斷時(shí)間是否大于xx秒(防止重放攻擊)
        long NONCE_STR_TIMEOUT_SECONDS = 60L;
        if (StrUtil.isEmpty(timestamp) || DateUtil.between(DateUtil.date(Long.parseLong(timestamp) * 1000), DateUtil.date(), DateUnit.SECOND) > NONCE_STR_TIMEOUT_SECONDS) {
            throw new BusinessException("invalid  timestamp");
        }

        // 判斷該用戶的nonceStr參數(shù)是否已經(jīng)在redis中(防止短時(shí)間內(nèi)的重放攻擊)
        Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
        if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
            throw new BusinessException("invalid nonceStr");
        }

        // 對(duì)請(qǐng)求頭參數(shù)進(jìn)行簽名
        if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
            throw new BusinessException("invalid signature");
        }

        // 將本次用戶請(qǐng)求的nonceStr參數(shù)存到redis中設(shè)置xx秒后自動(dòng)刪除
        redisTemplate.opsForValue().set(nonceStr, nonceStr, NONCE_STR_TIMEOUT_SECONDS, TimeUnit.SECONDS);

        return true;
    }

    private String signature(String timestamp, String nonceStr, HttpServletRequest request) throws UnsupportedEncodingException {
        Map<String, Object> params = new HashMap<>(16);
        Enumeration<String> enumeration = request.getParameterNames();
        if (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getParameter(name);
            params.put(name, URLEncoder.encode(value, CommonConstants.UTF_8));
        }
        String qs = String.format("%s×tamp=%s&nonceStr=%s&key=%s", this.sortQueryParamString(params), timestamp, nonceStr, key);
        log.info("qs:{}", qs);
        String sign = SecureUtil.md5(qs).toLowerCase();
        log.info("sign:{}", sign);
        return sign;
    }

    /**
     * 按照字母順序進(jìn)行升序排序
     *
     * @param params 請(qǐng)求參數(shù) 。注意請(qǐng)求參數(shù)中不能包含key
     * @return 排序后結(jié)果
     */
    private String sortQueryParamString(Map<String, Object> params) {
        List<String> listKeys = Lists.newArrayList(params.keySet());
        Collections.sort(listKeys);
        StrBuilder content = StrBuilder.create();
        for (String param : listKeys) {
            content.Append(param).append("=").append(params.get(param).toString()).append("&");
        }
        if (content.length() > 0) {
            return content.subString(0, content.length() - 1);
        }
        return content.toString();
    }
}

配置攔截器

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Value("${security.api.key}")
    private String key;
    registry.addInterceptor(new SignAuthInterceptor(redisTemplate, key))
            .addPathPatterns("/live-text/check/**")

Postman接口測(cè)試

借助Postman的Pre-request Scritp可以實(shí)現(xiàn)自動(dòng)簽名功能,每次請(qǐng)求都會(huì)生成一個(gè)新的簽名

使用Pre-request Script腳本實(shí)現(xiàn)簽名功能

API接口防止參數(shù)篡改和重放攻擊

 

輸入Pre-request Script,請(qǐng)復(fù)制粘貼下面提供的JAVA Script代碼到文本框當(dāng)中

//設(shè)置當(dāng)前時(shí)間戳(毫秒)
var timestamp =  Math.round(new Date()/1000);
pm.globals.set("timestamp",timestamp);
var nonceStr = createUuid();
pm.globals.set("nonceStr",nonceStr);
var key =pm.environment.get("key"); 
console.log(key);

var qs = urlToSign();
qs += '×tamp='+timestamp+'&nonceStr='+nonceStr+'&key='+key;
console.log(qs);
var signature = CryptoJS.MD5(qs).toString();
console.log(signature);
pm.environment.set("signature", signature);


function urlToSign() {
    var params = new Map();
    var contentType = request.headers["content-type"];
    if (contentType && contentType.startsWith('application/x-www-form-urlencoded')) {
        const formParams = request.data.split("&");
        formParams.forEach((p) => {
            const ss = p.split('=');
            params.set(ss[0], ss[1]);
        })
    }
    
    const ss = request.url.split('?');
    if (ss.length > 1 && ss[1]) {
        const queryParams = ss[1].split('&');
        queryParams.forEach((p) => {
            const ss = p.split('=');
            params.set(ss[0], ss[1]);
        })
    }
    
    var sortedKeys = Array.from(params.keys())
    sortedKeys.sort();
    
    var l1 = ss[0].lastIndexOf('/');
    var first = true;
    var qs
    for (var k of sortedKeys) {
        var s = k + "=" + params.get(k);
        qs = qs ? qs + "&" + s : s;
        console.log("key=" + k + " value=" + params.get(k));
    }
    return qs;
}

function createUuid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
}

設(shè)置環(huán)境變量/全局變量

API接口防止參數(shù)篡改和重放攻擊

 

對(duì)中文參數(shù)進(jìn)行轉(zhuǎn)碼

選中需要進(jìn)行轉(zhuǎn)碼的參數(shù),然后點(diǎn)擊鼠標(biāo)右鍵選中 EncodeURLComponent

API接口防止參數(shù)篡改和重放攻擊

 

分享到:
標(biāo)簽:接口 API
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定