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

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

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

問題的起源

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

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

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

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

基于timestamp和nonce的方案

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

timestamp的作用

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

一般情況下,從抓包重放請求耗時遠(yuǎn)遠(yuǎn)超過了60s,所以此時請求中的timestamp參數(shù)已經(jīng)失效了,如果修改timestamp參數(shù)為當(dāng)前的時間戳,則signature參數(shù)對應(yīng)的數(shù)字簽名就會失效,因為不知道簽名秘鑰,沒有辦法生成新的數(shù)字簽名。

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

nonce的作用

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

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

nonce參數(shù)作為數(shù)字簽名的一部分,是無法篡改的,因為不知道簽名秘鑰,沒有辦法生成新的數(shù)字簽名。

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

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

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

@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 {
        // 獲取時間戳
        String timestamp = request.getHeader("timestamp");
        // 獲取隨機字符串
        String nonceStr = request.getHeader("nonceStr");
        // 獲取簽名
        String signature = request.getHeader("signature");

        // 判斷時間是否大于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中(防止短時間內(nèi)的重放攻擊)
        Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
        if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
            throw new BusinessException("invalid nonceStr");
        }

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

        // 將本次用戶請求的nonceStr參數(shù)存到redis中設(shè)置xx秒后自動刪除
        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;
    }

    /**
     * 按照字母順序進行升序排序
     *
     * @param params 請求參數(shù) 。注意請求參數(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接口測試

借助Postman的Pre-request Scritp可以實現(xiàn)自動簽名功能,每次請求都會生成一個新的簽名

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

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

 

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

//設(shè)置當(dāng)前時間戳(毫秒)
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ù)篡改和重放攻擊

 

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

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

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

 

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

網(wǎng)友整理

注冊時間:

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

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

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

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

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

答題星2018-06-03

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

全階人生考試2018-06-03

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

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

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

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

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

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定