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

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

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

問題的起源

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

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

常用的其他業務場景還有:

  • 發送短信接口
  • 支付接口

基于timestamp和nonce的方案

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

timestamp的作用

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

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

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

nonce的作用

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

nonce參數在首次請求時,已經被存儲到了服務器上的“集合”中,再次發送請求會被識別并拒絕。

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

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

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

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

@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參數是否已經在redis中(防止短時間內的重放攻擊)
        Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
        if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
            throw new BusinessException("invalid nonceStr");
        }

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

        // 將本次用戶請求的nonceStr參數存到redis中設置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 請求參數 。注意請求參數中不能包含key
     * @return 排序后結果
     */
    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可以實現自動簽名功能,每次請求都會生成一個新的簽名

使用Pre-request Script腳本實現簽名功能

API接口防止參數篡改和重放攻擊

 

輸入Pre-request Script,請復制粘貼下面提供的JAVA Script代碼到文本框當中

//設置當前時間戳(毫秒)
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);
    });
}

設置環境變量/全局變量

API接口防止參數篡改和重放攻擊

 

對中文參數進行轉碼

選中需要進行轉碼的參數,然后點擊鼠標右鍵選中 EncodeURLComponent

API接口防止參數篡改和重放攻擊

 

分享到:
標簽:接口 API
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

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

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

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

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定