国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

超詳細!完整版!基于spring對外開放接口的簽名認證方案(攔截器方式)

這篇具有很好參考價值的文章主要介紹了超詳細!完整版!基于spring對外開放接口的簽名認證方案(攔截器方式)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

1、場景

由于項目需要開發(fā)第三方接口給多個供應(yīng)商,為保證Api接口的安全性,遂采用Api接口簽名驗證。

2、接口防御措施

  1. 請求發(fā)起時間得在限制范圍內(nèi)
  2. 請求的用戶是否真實存在
  3. 是否存在重復(fù)請求
  4. 請求參數(shù)是否被篡改

3、簽名認證邏輯

1、服務(wù)端生成一對 accessKey/secretKey密鑰對,將 accessKey公開給客戶端,將 secretKey 保密。

2、客戶端使用 secretKey和一些請求參數(shù)(如時間戳、請求內(nèi)容等),使用 MD5 算法生成簽名。

3、客戶端將 accessKey、簽名和請求參數(shù)一起發(fā)送給服務(wù)端。

4、服務(wù)端使用 和收到的請求參數(shù),使用 MD5 算法生成簽名。

5、服務(wù)端比較客戶端發(fā)來的簽名和自己生成的簽名是否相同,如果相同,則認為請求是可信的,否則認為請求是不可信的。
secretKey不進行網(wǎng)絡(luò)傳輸,只用于本地MD5運算

4、簽名算法規(guī)則

計算步驟
用于計算簽名的參數(shù)在不同接口之間會有差異,但算法過程固定如下4個步驟。
將<key, value>請求參數(shù)對按key進行字典升序排序,得到有序的參數(shù)對列表N
將列表N中的參數(shù)對按URL鍵值對的格式拼接成字符串,得到字符串T(如:key1=value1&key2=value2),URL鍵值拼接過程value部分需要URL編碼,URL編碼算法用大寫字母,例如%E8,而不是小寫%e8
將應(yīng)用密鑰以app_key為鍵名,組成URL鍵值拼接到字符串T末尾,得到字符串S(如:key1=value1&key2=value2&app_key=密鑰)
對字符串S進行MD5運算,將得到的MD5值所有字符轉(zhuǎn)換成大寫,得到接口請求簽名

注意事項
不同接口要求的參數(shù)對不一樣,計算簽名使用的參數(shù)對也不一樣
參數(shù)名區(qū)分大小寫,參數(shù)值為空不參與簽名
URL鍵值拼接過程value部分需要URL編碼

5、代碼示例

1、sign工具類

public class SignUtil {

    /**
     * 簽名算法
     * 1. 計算步驟
     * 用于計算簽名的參數(shù)在不同接口之間會有差異,但算法過程固定如下4個步驟。
     * 將<key, value>請求參數(shù)對按key進行字典升序排序,得到有序的參數(shù)對列表N
     * 將列表N中的參數(shù)對按URL鍵值對的格式拼接成字符串,得到字符串T(如:key1=value1&key2=value2),URL鍵值拼接過程value部分需要URL編碼,URL編碼算法用大寫字母,例如%E8,而不是小寫%e8
     * 將應(yīng)用密鑰以app_key為鍵名,組成URL鍵值拼接到字符串T末尾,得到字符串S(如:key1=value1&key2=value2&app_key=密鑰)
     * 對字符串S進行MD5運算,將得到的MD5值所有字符轉(zhuǎn)換成大寫,得到接口請求簽名
     * 2. 注意事項
     * 不同接口要求的參數(shù)對不一樣,計算簽名使用的參數(shù)對也不一樣
     * 參數(shù)名區(qū)分大小寫,參數(shù)值為空不參與簽名
     * URL鍵值拼接過程value部分需要URL編碼
     * @return 簽名字符串
     */
    private static String getSign(Map<String, Object> map, String secretKey) {
        List<Map.Entry<String, Object>> infoIds = new ArrayList<>(map.entrySet());
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            public int compare(Map.Entry<String, Object> arg0, Map.Entry<String, Object> arg1) {
                return (arg0.getKey()).compareTo(arg1.getKey());
            }
        });
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> m : infoIds) {
            if(null == m.getValue() || StringUtils.isNotBlank(m.getValue().toString())){
                sb.append(m.getKey()).append("=").append(URLUtil.encodeAll(m.getValue().toString())).append("&");
            }
        }
        sb.append("secret-key=").append(secretKey);
        return MD5.create().digestHex(sb.toString()).toUpperCase();
    }


    //獲取隨機值
    private static String getNonceStr(int length){
        //生成隨機字符串
        String str="zxcvbnmlkjhgfdsaqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
        Random random=new Random();
        StringBuffer randomStr=new StringBuffer();
        // 設(shè)置生成字符串的長度,用于循環(huán)
        for(int i=0; i<length; ++i){
            //從62個的數(shù)字或字母中選擇
            int number=random.nextInt(62);
            //將產(chǎn)生的數(shù)字通過length次承載到sb中
            randomStr.append(str.charAt(number));
        }
        return randomStr.toString();
    }
    //簽名驗證方法
    public static boolean signValidate(Map<String, Object> map,String secretKey,String sign){
        String mySign = getSign(map,secretKey);
        return mySign.equals(sign);
    }
}

2、定義攔截器

@Configuration
public class SignInterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signInterceptor())
                .addPathPatterns("/openapi/**");//只攔截openapi前綴的接口
    }
	//交給spring管理 SignInterceptor bean 
	//不然下邊 private OpenApiApplyMapper applyMapper;注入為null
    @Bean
    public SignInterceptor signInterceptor(){
        return new SignInterceptor();
    }

}

3、生成accessKey、secretKey 工具類

public class KeyGenerator {
    private static final int KEY_LENGTH = 32; // 指定生成的key長度為32字節(jié)

    public static String generateAccessKey() {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[KEY_LENGTH / 2]; // 生成的字節(jié)數(shù)要除以2
        random.nextBytes(bytes);
        return Base64.getEncoder().encodeToString(bytes).replace("/", "").replace("+", "").substring(0, 20);
    }

    public static String generateSecretKey() {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[KEY_LENGTH];
        random.nextBytes(bytes);
        return Base64.getEncoder().encodeToString(bytes).replace("/", "").replace("+", "").substring(0, 40);
    }
}

4、signInterceptor類

public class SignInterceptor implements HandlerInterceptor {

    private static final String ACCESSKEY = "access-key";//調(diào)用者身份唯一標識
    private static final String TIMESTAMP = "time-stamp";//時間戳
    private static final String SIGN = "sign";//簽名
    private static final String NONCE = "nonce";//隨機值


    @Resource
    private OpenApiApplyMapper applyMapper;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(checkSign(request, response)){//簽名認證
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
        return false;
    }

    /**
     * 驗證簽名
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    private boolean checkSign(HttpServletRequest request,HttpServletResponse response)throws Exception {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf8");
        String ip = IPUtils.getIpAddr(request);
        FzyLogUtil.infoSafe("開放接口", "訪問時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL());
        String accessKey = request.getHeader(ACCESSKEY);
        String timestamp = request.getHeader(TIMESTAMP);
        String nonce = request.getHeader(NONCE);
        String sign = request.getHeader(SIGN);
        if (!StringUtils.isNotBlank(accessKey)) {
            response.getWriter().write(JSON.toJSONString(ResultUtil.fail("accessKey無效")));
            FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:accessKey無效");
            return false;
        }
        if (StringUtils.isBlank(sign)) {
            response.getWriter().write(JSON.toJSONString(ResultUtil.fail("簽名無效")));
            FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:簽名無效");
            return false;
        }
        OpenApiDetailDO openApiDetailDO = applyMapper.selectOneByAccessKey(accessKey);
        if (openApiDetailDO == null) {
            response.getWriter().write(JSON.toJSONString(ResultUtil.fail("accessKey不存在")));
            FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:accessKey不存在");
            return false;
        }
        if (StringUtils.isNotBlank(openApiDetailDO.getBlackList())) {
            for (String bIp : openApiDetailDO.getBlackList().split(",")) {
                if (bIp.equals(ip)) {//黑名單
                    response.getWriter().write(JSON.toJSONString(ResultUtil.fail("拒絕請求")));
                    FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:黑名單拒絕請求");
                    return false;
                }
            }
        }
        if (StringUtils.isNotBlank(openApiDetailDO.getWhiteList())) {
            boolean flag = false;
            for (String bIp : openApiDetailDO.getWhiteList().split(",")) {
                if (bIp.equals(ip)) {//白名單
                    flag = true;
                    break;
                }
            }
            if(!flag){
                response.getWriter().write(JSON.toJSONString(ResultUtil.fail("拒絕請求")));
                FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:白名單未符合拒絕請求");
                return false;
            }
        }

        if ("0".equals(openApiDetailDO.getInvokeStatus() + "")) {
            response.getWriter().write(JSON.toJSONString(ResultUtil.fail("訪問權(quán)限已被凍結(jié)")));
            FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:訪問權(quán)限已被凍結(jié)");
            return false;
        }
        if (!"1".equals(openApiDetailDO.getApiStatus() + "")) {
            response.getWriter().write(JSON.toJSONString(ResultUtil.fail("接口異常,暫停訪問")));
            FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:接口異常,暫停訪問");
            return false;
        }

        if (!StringUtils.isNotBlank(timestamp)) {
            response.getWriter().write(JSON.toJSONString(ResultUtil.fail("時間戳無效")));
            FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:時間戳無效");
            return false;
        } else if (openApiDetailDO.getTimeOut() != null) {
            if (System.currentTimeMillis() - Long.valueOf(timestamp) > openApiDetailDO.getTimeOut() * 1000) {
                response.getWriter().write(JSON.toJSONString(ResultUtil.fail("請求已過期")));
                FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:請求已過期");
                return false;
            }
            ;
        }
        Map<String, Object> hashMap = new HashMap<>();
        String queryStrings = request.getQueryString();//獲取url后邊拼接的參數(shù)
        if (queryStrings != null) {
            for (String queryString : queryStrings.split("&")) {
                String[] param = queryString.split("=");
                if (param.length == 2) {
                    hashMap.put(param[0], param[1]);
                }
            }
        }
        hashMap.put(ACCESSKEY, accessKey);
        hashMap.put(TIMESTAMP, timestamp);
        if (StringUtils.isNotBlank(nonce)) {
            hashMap.put(NONCE, nonce);
        }
        String secretKey = openApiDetailDO.getSecretKey();
        String body = new RequestWrapper(request).getBody();
        if (StringUtils.isNotBlank(body)) {
            Map<String, Object> map = JSON.parseObject(body);
            if (map != null) {
                hashMap.putAll(map);
            }
        }
        if (!SignUtil.signValidate(hashMap, secretKey, sign)) {//認證失敗
            response.getWriter().write(JSON.toJSONString(ResultUtil.fail("認證失敗")));
            FzyLogUtil.errorSafe("開放接口請求失敗", "時間:" + LocalDateTime.now() + ",IP:" + ip + ",訪問接口:" + request.getRequestURL() + "錯誤信息:認證失敗");
            return false;
        }
        return true;
    }
}

5、SignInterceptor 獲取body里參數(shù)后,接口的controller會獲取不到body的參數(shù)了,會報錯

通過過濾器解決文章來源地址http://www.zghlxwxcb.cn/news/detail-815826.html

@Component
@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "/")
@Order(10000)
public class HttpServletRequestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String contentType = request.getContentType();
        String method = "multipart/form-data";

        if (contentType != null && contentType.contains(method)) {
            // 將轉(zhuǎn)化后的 request 放入過濾鏈中
            request = new StandardServletMultipartResolver().resolveMultipart(request);
        }
        request = new RequestWrapper((HttpServletRequest) servletRequest);
        //獲取請求中的流如何,將取出來的字符串,再次轉(zhuǎn)換成流,然后把它放入到新request對象中
        // 在chain.doFiler方法中傳遞新的request對象
        if(null == request) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(request, servletResponse);
        }
    }

    @Override
    public void destroy() {

    }
}

到了這里,關(guān)于超詳細!完整版!基于spring對外開放接口的簽名認證方案(攔截器方式)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 如何實現(xiàn)服務(wù)器對外開放?路由器端口映射怎么設(shè)置?

    如何實現(xiàn)服務(wù)器對外開放?路由器端口映射怎么設(shè)置?

    使用路由器后,Internet用戶無法訪問到局域網(wǎng)內(nèi)的主機,因此不能訪問內(nèi)網(wǎng)搭建的Web、FTP、Mail等服務(wù)器。路由器端口映射功能可以實現(xiàn)將內(nèi)網(wǎng)的服務(wù)器映射到Internet,從而實現(xiàn)服務(wù)器對外開放。路由器端口映射怎么設(shè)置? 下面給大家介紹一下具體設(shè)置步驟。 第一步:設(shè)置虛擬

    2024年02月16日
    瀏覽(92)
  • 騰訊云向量數(shù)據(jù)庫正式對外全量開放公測

    11月1日,騰訊云對外宣布向量數(shù)據(jù)庫正式全量開放公測,同時性能層面帶來巨大提升。騰訊云數(shù)據(jù)庫副總經(jīng)理羅云表示,除了公測之外,騰訊云向量數(shù)據(jù)庫單索引已經(jīng)支持百億級向量規(guī)模,支持百萬級QPS毫秒級查詢延遲,領(lǐng)先行業(yè)平均水平1.5倍以上,計算成本低于行業(yè)水平

    2024年02月06日
    瀏覽(22)
  • 面向WEB3.0提升存儲性能,螞蟻鯨探底層區(qū)塊鏈存儲引擎正式對外開放

    面向WEB3.0提升存儲性能,螞蟻鯨探底層區(qū)塊鏈存儲引擎正式對外開放

    11月3日,螞蟻集團數(shù)字科技在云棲大會上宣布,其歷經(jīng)4年的關(guān)鍵技術(shù)攻關(guān)與測試驗證的區(qū)塊鏈存儲引擎LETUS(Log-structured Efficient Trusted Universal Storage),首次對外開放。該產(chǎn)品主要用于可信數(shù)據(jù)在區(qū)塊鏈上的存儲,LETUS今年在螞蟻數(shù)字藏品平臺“鯨探”成功應(yīng)用,真實業(yè)務(wù)環(huán)境

    2024年02月12日
    瀏覽(23)
  • Linux系統(tǒng)firewalld防火墻的應(yīng)用實操(對外端口開放使用,對內(nèi)端口限制ip地址使用,不使用端口默認關(guān)閉)

    Linux系統(tǒng)firewalld防火墻的應(yīng)用實操(對外端口開放使用,對內(nèi)端口限制ip地址使用,不使用端口默認關(guān)閉)

    本文直接進行Linux系統(tǒng)firewalld防火墻的應(yīng)用實操 對外端口開放使用 對內(nèi)端口限制ip地址使用 不使用端口默認關(guān)閉 基礎(chǔ)知識請查閱:Linux系統(tǒng)firewalld防火墻的基本操作 進階知識請查閱:Linux系統(tǒng)firewalld防火墻的進階操作(日志保存 IP網(wǎng)段 ssh服務(wù)) 應(yīng)用實操請查閱:Linux系統(tǒng)f

    2024年02月05日
    瀏覽(27)
  • Spring Boot入門(23):基于AOP實現(xiàn)自定義注解攔截接口日志并保存入庫 | 超級詳細,建議收藏

    Spring Boot入門(23):基于AOP實現(xiàn)自定義注解攔截接口日志并保存入庫 | 超級詳細,建議收藏

    ? ? ? ? 在上兩期中,我們著重介紹了如何集成使用 Logback?與?log4j2?日志框架的使用,今天我們講解的主題依舊跟日志有關(guān),不過不是使用何種開源框架,而是自己動手造。 ? ? ? ? Spring的核心之一AOP;AOP翻譯過來叫面向切面編程, 核心就是這個切面. 切面表示從業(yè)務(wù)邏輯中

    2024年02月11日
    瀏覽(29)
  • 如何保證對外接口的安全?

    如何保證對外接口的安全?

    1.什么是安全接口? 通常來說要將暴露在外網(wǎng)的 API 接口視為安全接口,需要實現(xiàn)防篡改和防重放的功能。 1.1 什么是篡改問題? 由于 HTTP 是一種無狀態(tài)協(xié)議,服務(wù)端無法確定客戶端發(fā)送的請求是否合法,也不了解請求中的參數(shù)是否正確。以一個充值接口為例: 如果非法用戶

    2024年03月08日
    瀏覽(27)
  • 如何設(shè)計一個安全的對外接口 ?

    最近有個項目需要對外提供一個接口,提供公網(wǎng)域名進行訪問,而且接口和交易訂單有關(guān),所以安全性很重要;這里整理了一下常用的一些安全措施以及具體如何去實現(xiàn)。 個人覺得安全措施大體來看主要在兩個方面,一方面就是如何保證數(shù)據(jù)在傳輸過程中的安全性,另一個方

    2024年02月08日
    瀏覽(20)
  • 如何設(shè)計一個安全的對外接口

    如何設(shè)計一個安全的對外接口

    非對稱加密:服務(wù)端會生成一對密鑰,私鑰存放在服務(wù)器端,公鑰可以發(fā)布給任何人使用;優(yōu)點就是比起對稱加密更加安全,但是加解密的速度比對稱加密慢太多了;廣泛使用的是RSA算法; 兩種方式各有優(yōu)缺點,而https的實現(xiàn)方式正好是結(jié)合了兩種加密方式,整合了雙方的優(yōu)

    2024年04月16日
    瀏覽(22)
  • docker+jenkins+gitee+shell 自動化部署微服務(wù)(基于若依)【詳細完整版】

    docker+jenkins+gitee+shell 自動化部署微服務(wù)(基于若依)【詳細完整版】

    提示:需基本熟悉docker命令、shell腳本、微服務(wù)部署、git命令、服務(wù)器基本命令、node、maven 服務(wù)器環(huán)境:Linux-EulerOS(華為自主研發(fā)服務(wù)器系統(tǒng),與CentOS基本類似) 安裝組件:docker20.10.19、openjdk1.8.0_191、maven3.8.6、git2.33.0、node16.17.1、npm9.1.2、jenkinsci/blueocean、docker-compose 遠程工

    2023年04月11日
    瀏覽(24)
  • 業(yè)務(wù)開發(fā)時,接口不能對外暴露怎么辦?

    業(yè)務(wù)開發(fā)時,接口不能對外暴露怎么辦?

    在業(yè)務(wù)開發(fā)的時候,經(jīng)常會遇到某一個接口不能對外暴露,只能內(nèi)網(wǎng)服務(wù)間調(diào)用的實際需求。面對這樣的情況,我們該如何實現(xiàn)呢? 今天,我們就來理一理這個問題,從幾個可行的方案中,挑選一個來實現(xiàn)。 推薦一個開源免費的 Spring Boot 實戰(zhàn)項目: https://github.com/javastacks

    2024年02月12日
    瀏覽(21)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包