精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Spring Boot如何優(yōu)雅提高接口數(shù)據(jù)安全性

開(kāi)發(fā) 前端
因?yàn)榱鲗?duì)應(yīng)的是數(shù)據(jù),數(shù)據(jù)放在內(nèi)存中,有的是部分放在內(nèi)存中。read 一次標(biāo)記一次當(dāng)前位置(mark position),第二次read就從標(biāo)記位置繼續(xù)讀(從內(nèi)存中copy)數(shù)據(jù)。

1.背景

最近我司業(yè)務(wù)上需要對(duì)接第三方各大銀行平臺(tái),調(diào)用第三方接口和提供接口供第三方調(diào)用,這時(shí)候的對(duì)外open接口安全性就得重視了,再有就是之前我在知乎上發(fā)布一篇《Spring Security實(shí)現(xiàn)后端接口權(quán)限驗(yàn)證》的總結(jié),有個(gè)兄弟提出一個(gè)問(wèn)題:只做接口功能菜單權(quán)限檢驗(yàn)還不夠,還得做數(shù)據(jù)權(quán)限檢驗(yàn)才行,舉個(gè)例子:用戶A有刪除某條數(shù)據(jù)的接口權(quán)限,這個(gè)接口的參數(shù)是傳記錄id來(lái)刪除的(ps:平時(shí)我們開(kāi)發(fā)接口也是這么做的),后端執(zhí)行的邏輯就是通過(guò)登錄信息通過(guò)用戶認(rèn)證,然后再判斷接口菜單權(quán)限,緊接著就執(zhí)行如下SQL邏輯:

delete from table where id=?

這里的id就是掉接口傳遞的參數(shù),這時(shí)候假如用戶B知道了怎么調(diào)接口,就根據(jù)id自增長(zhǎng)的特性隨意傳id,就會(huì)刪掉別人的數(shù)據(jù),所以這是一個(gè)嚴(yán)重的問(wèn)題,要解決這問(wèn)題可以像上面說(shuō)的一樣加上數(shù)據(jù)權(quán)限,執(zhí)行邏輯如下:

delete from table where id=? and user_id = userId

這樣就避免數(shù)據(jù)被別人操作了,也就是加上了數(shù)據(jù)權(quán)限判斷,但是卻給業(yè)務(wù)邏輯增加了復(fù)雜性同時(shí)老接口業(yè)務(wù)邏輯難以適配,本質(zhì)上來(lái)說(shuō)web頁(yè)面上看到的數(shù)據(jù)就是根據(jù)用戶角色做過(guò)數(shù)據(jù)隔離的,可以這么理解你能看到哪些數(shù)據(jù)和你有那些功能菜單操作權(quán)限就差不多避免上面所說(shuō)的情況了,但是保不準(zhǔn)懂代碼的人使用postman等工具惡意調(diào)接口而產(chǎn)生上面的情況,我們還是得正視這個(gè)問(wèn)題,既然通過(guò)數(shù)據(jù)權(quán)限解決該問(wèn)題不太友好,那么我們可以再思考下怎么避免這個(gè)問(wèn)題???這個(gè)問(wèn)題可以轉(zhuǎn)換為怎么避免別人輕易就能調(diào)通接口,解決辦法就是不能在外網(wǎng)暴露接口信息,拒絕接口裸奔,從而有效提高接口安全性,這也是今天我們這篇總結(jié)的核心主旨。當(dāng)然這里強(qiáng)調(diào)一下我這里說(shuō)的是有效提高,不是絕對(duì)保證安全,做不到...

項(xiàng)目推薦:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企業(yè)級(jí)系統(tǒng)架構(gòu)底層框架封裝,解決業(yè)務(wù)開(kāi)發(fā)時(shí)常見(jiàn)的非功能性需求,防止重復(fù)造輪子,方便業(yè)務(wù)快速開(kāi)發(fā)和企業(yè)技術(shù)??蚣芙y(tǒng)一管理。引入組件化的思想實(shí)現(xiàn)高內(nèi)聚低耦合并且高度可配置化,做到可插拔。嚴(yán)格控制包依賴和統(tǒng)一版本管理,做到最少化依賴。注重代碼規(guī)范和注釋?zhuān)浅_m合個(gè)人學(xué)習(xí)和企業(yè)使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

2.Spring Boot如何提高接口安全性

在Spring Boot項(xiàng)目中提高接口安全的核心所在:加密和加簽,加固接口參數(shù)、驗(yàn)證復(fù)雜度。

加密:對(duì)參數(shù)進(jìn)行加密傳輸,拒絕接口參數(shù)直接暴露,這樣就可以有效做到防止別人輕易準(zhǔn)確地獲取到接口參數(shù)定義和傳參格式要求了。

加簽:對(duì)接口參數(shù)進(jìn)行加簽,可以有效防止接口參數(shù)被篡改和接口參數(shù)被重放惡刷。

2.1 加密

現(xiàn)今有許許多多的加密算法,這里就不對(duì)算法進(jìn)行過(guò)度敘述,畢竟不是我們今天的主題,但是加密算法大體分為非對(duì)稱(chēng)加密和對(duì)稱(chēng)加密。

非對(duì)稱(chēng)加密

非對(duì)稱(chēng)加密算法是一種密鑰的保密方法。非對(duì)稱(chēng)加密算法需要兩個(gè)密鑰:公開(kāi)密鑰(publickey:簡(jiǎn)稱(chēng)公鑰)和私有密鑰(privatekey:簡(jiǎn)稱(chēng)私鑰)。公鑰與私鑰是一對(duì),如果用公鑰對(duì)數(shù)據(jù)進(jìn)行加密,只有用對(duì)應(yīng)的私鑰才能解密。因?yàn)榧用芎徒饷苁褂玫氖莾蓚€(gè)不同的密鑰,所以這種算法叫作非對(duì)稱(chēng)加密算法。

對(duì)稱(chēng)加密

加密秘鑰和解密秘鑰是一樣,當(dāng)你的密鑰被別人知道后,就沒(méi)有秘密可言了。

經(jīng)過(guò)需求分析和科學(xué)借鑒我們采用了非對(duì)稱(chēng)加密算法RSA和對(duì)稱(chēng)加密算法AES來(lái)完成接口加密。至于這兩種加密算法的原理與實(shí)現(xiàn)有興趣自己去查資料,我這里就說(shuō)一下選它們的原因:

AES 是對(duì)稱(chēng)加密算法,優(yōu)點(diǎn):加密速度快;缺點(diǎn):如果秘鑰丟失,就容易解密密文,安全性相對(duì)比較差

RSA 是非對(duì)稱(chēng)加密算法 , 優(yōu)點(diǎn):安全 ;缺點(diǎn):加密速度慢

接口參數(shù)加解密的流程大致如圖所示:

圖片圖片

具體步驟如下:

  1. 客戶端(調(diào)用接口方)隨機(jī)生成AES加解密的密鑰aes key,這里的AES密鑰每次調(diào)接口都需要隨機(jī)生成,可以有效提高安全性。
  2. 使用aes key對(duì)接口參數(shù)requestBody進(jìn)行加密,data=base64(AES(json參數(shù)))
  3. 通過(guò)RSA加密算法加密aes key,有效保證aes算法的密鑰的可靠安全性  key=base64(RSA(aes key))
  4. 經(jīng)過(guò)上面的步驟,得到了加密后的業(yè)務(wù)參數(shù)及密鑰,這時(shí)候就可以發(fā)送請(qǐng)求調(diào)用接口了
  5. 服務(wù)端接收到請(qǐng)求之后,先通過(guò)RSA算法對(duì)key進(jìn)行解密獲取到ase key, 再通過(guò)aes key解密data得到真正json參數(shù),最后映射到接口方法的參數(shù)對(duì)象上,供controller的業(yè)務(wù)方法邏輯使用。
  6. 業(yè)務(wù)方法執(zhí)行完成后,對(duì)響應(yīng)參數(shù)進(jìn)行加密,加密流程和上面的1、2、3一樣
  7. 客戶端收到響應(yīng)參數(shù)之后,和步驟5一樣解密響應(yīng)參數(shù),就拿到了真正的數(shù)據(jù)結(jié)果了。

2.2 加簽

簽名驗(yàn)證也是當(dāng)下提高接口安全性主要措施之一,核心就是客戶端在調(diào)用接口時(shí)按照一定規(guī)則生成簽名sign,服務(wù)端拿到簽名sign之后進(jìn)行驗(yàn)證操作,大致流程如下:

圖片圖片

具體步驟:

  1. 對(duì)請(qǐng)求參數(shù)對(duì)象bean轉(zhuǎn)sortMap保證參數(shù)拼接的有序性,如果接口沒(méi)有參數(shù)也沒(méi)有關(guān)系,這里轉(zhuǎn)成一個(gè)空的sortMap
  2. 按照約定拼接生成字符串content = sortMap + nonce + timestamp
  3. 使?SHA1WithRSA算法及私鑰對(duì)concent進(jìn)?簽名sign
  4. 服務(wù)端判斷timestamp是否超過(guò)簽名有效期和nonce是否重復(fù)使用
  5. 服務(wù)端和步驟2一樣規(guī)則生成字符串content
  6. 使?SHA1WithRSA算法及公鑰對(duì)concent和sign進(jìn)行驗(yàn)簽

3.優(yōu)雅實(shí)現(xiàn)接口加密、加簽

在實(shí)現(xiàn)這個(gè)需求時(shí),考慮到全公司的多個(gè)團(tuán)隊(duì)開(kāi)發(fā)使用的通用性和便捷性,所以我們對(duì)加密、加簽操作進(jìn)行了公共的抽取封裝,同時(shí)通過(guò)一個(gè)注解@ApiSecurity來(lái)標(biāo)識(shí)接口是否需要進(jìn)行加密、加簽操作,在業(yè)務(wù)側(cè)極大程度地降低了開(kāi)發(fā)使用成本,不用寫(xiě)冗余代碼,做到了真正的優(yōu)雅。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface ApiSecurity {

    @Alias("isSign")
    boolean value() default true;

    /**
     * 是否加簽驗(yàn)證,默認(rèn)開(kāi)啟
     * @return
     */
    @Alias("value")
    boolean isSign() default true;

    /**
     * 接口請(qǐng)求參數(shù)是否需要解密
     * @return
     */
    boolean decryptRequest() default false;

    /**
     * 接口響應(yīng)參數(shù)是否需要加密
     * @return
     */
    boolean encryptResponse() default false;
}

這里注解屬性可以看到簽名驗(yàn)證默認(rèn)是開(kāi)啟的,因?yàn)槲覀冋J(rèn)為接口安全性加簽是必須的,至于參數(shù)加解密可以視情況而定,通過(guò)屬性配置開(kāi)關(guān),做到了極致的靈活性,這也是優(yōu)雅呀。

使用案例:下面就是一個(gè)需要加密加簽的接口

@PostMapping("/security")
    @ApiSecurity(encryptResponse = true, decryptRequest = true)
    public User testApiSecurity(@RequestBody User user) {
        System.out.println(user);
        return user;
    }

可以看到我們?cè)陧?xiàng)目業(yè)務(wù)服務(wù)中只需要@ApiSecurity就可以了,就是這么簡(jiǎn)單,至于怎么實(shí)現(xiàn)的下面我們就來(lái)看看。

為了全公司對(duì)接口加密、加簽功能實(shí)現(xiàn)統(tǒng)一和規(guī)范,我們將實(shí)現(xiàn)抽取,封裝集成在公司自定義的web starter中,這樣只要項(xiàng)目服務(wù)引入這個(gè)starter依賴就可以使用該功能了

首先我們對(duì)加密傳輸?shù)膮?shù)bean進(jìn)行規(guī)定封裝如下:

@Data
public class ApiSecurityParam {

    /**
     * 應(yīng)用id
     */
    private String appId;

    /**
     * RSA加密后的aes秘鑰,需解密
     */
    private String key;

    /**
     * AES加密的json參數(shù)
     */
    private String data;

    /**
     * 簽名
     */
    private String sign;

    /**
     * 時(shí)間戳
     */
    private String timestamp;

    /**
     * 請(qǐng)求唯一標(biāo)識(shí)
     */
    private String nonce;

}

等于說(shuō)加密、加簽的參數(shù)格式,調(diào)用方需按照上面的對(duì)象傳參,當(dāng)然為了提高拓展性,簽名的相關(guān)信息sign、timestamp、nonce可以放到請(qǐng)求的header里面,也能獲取到。拿到apiSecurityParam我們就可以進(jìn)行請(qǐng)求參數(shù)解密、驗(yàn)簽了,需要通過(guò)判斷是否使用了注解@ApiSecuriy來(lái)決定是否執(zhí)行請(qǐng)求參數(shù)解密、驗(yàn)簽邏輯,這就正好可以使用基于注解的切面實(shí)現(xiàn)啦,在說(shuō)切面之前,先說(shuō)說(shuō)一次接口請(qǐng)求requestBody的輸入流InputStream只能讀取一次,就是說(shuō)request.getInputStream()只能使用一次,原因如下:

因?yàn)榱鲗?duì)應(yīng)的是數(shù)據(jù),數(shù)據(jù)放在內(nèi)存中,有的是部分放在內(nèi)存中。read 一次標(biāo)記一次當(dāng)前位置(mark position),第二次read就從標(biāo)記位置繼續(xù)讀(從內(nèi)存中copy)數(shù)據(jù)。 所以這就是為什么讀了一次第二次是空了。 怎么讓它不為空呢?只要inputstream 中的pos 變成0就可以重寫(xiě)讀取當(dāng)前內(nèi)存中的數(shù)據(jù)。javaAPI中有一個(gè)方法public void reset() 這個(gè)方法就是可以重置pos為起始位置,但是不是所有的IO讀取流都可以調(diào)用該方法!ServletInputStream是不能調(diào)用reset方法,這就導(dǎo)致了只能調(diào)用一次getInputStream()。

而我們需要先讀取出requestBody進(jìn)行解密,然后拿到解密之前的參數(shù)映射到真正的接口方法參數(shù)對(duì)象里,所以必須解決這個(gè)問(wèn)題。

解決方法就是原始的HttpServletRequest的InputStream只能讀取一下,那么我們就重新自定義封裝一個(gè)HttpServletRequest可以實(shí)現(xiàn)多次讀取。

public class RequestBodyWrapper extends HttpServletRequestWrapper {

    //用于將流保存下來(lái)
    private String body;

    public RequestBodyWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = new String(StreamUtils.copyToByteArray(request.getInputStream()), StandardCharsets.UTF_8);
    }

    /**
     * 重寫(xiě)getInputStream, 從body中獲取請(qǐng)求參數(shù)
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("UTF-8"));
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    public String getBody() {
        return this.body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

然后通過(guò)一個(gè)過(guò)濾器filter把自定義封裝的RqequestBodyWapper傳遞下去:

@Slf4j
public class BodyTransferFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        RequestBodyWrapper requestBodyWrapper = null;
        try {
            HttpServletRequest req = (HttpServletRequest)request;
            requestBodyWrapper = new RequestBodyWrapper(req);

        }catch (Exception e){
            log.warn("requestBodyWrapper Error:", e);
        }
        chain.doFilter((Objects.isNull(requestBodyWrapper) ? request : requestBodyWrapper), response);
    }
}

接下來(lái)就可以來(lái)看看切面了:這里是解析請(qǐng)求參數(shù)和驗(yàn)簽和邏輯所在:

@Aspect
@Slf4j
@Order(value = OrderConstant.AOP_API_DECRYPT)
public class ApiSecurityAspect {
    @Resource
    private ApiSecurityProperties apiSecurityProperties;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final String NONCE_KEY = "x-nonce-";

    @Pointcut("execution(* com.plasticene..controller..*(..)) && " +
            "(@annotation(com.plasticene.boot.web.core.anno.ApiSecurity) ||" +
            " @target(com.plasticene.boot.web.core.anno.ApiSecurity))")
    public void securityPointcut(){}

    @Around("securityPointcut()")
    public Object aroundApiSecurity(ProceedingJoinPoint joinPoint) throws Throwable {
        //=======AOP解密切面通知=======
        ApiSecurity apiSecurity = getApiSecurity(joinPoint);
        boolean isSign = apiSecurity.isSign();
        boolean decryptRequest = apiSecurity.decryptRequest();
        // 獲取request加密傳遞的參數(shù)
        HttpServletRequest request = getRequest();
        // 只能針對(duì)post接口的請(qǐng)求參數(shù)requestBody進(jìn)行統(tǒng)一加解密和加簽,這是規(guī)定
        if (!Objects.equals("POST", request.getMethod())) {
            throw new BizException("只能POST接口才能加密加簽操作");
        }
        // 獲取controller接口方法定義的參數(shù)
        Object[] args = joinPoint.getArgs();
        Object[] newArgs = args;
        ApiSecurityParam apiSecurityParam = new ApiSecurityParam();
        // 請(qǐng)求參數(shù)解密
        if (decryptRequest) {
            // 不支持多個(gè)請(qǐng)求,因?yàn)榻饷苷?qǐng)求參數(shù)之后會(huì)json字符串,再根據(jù)請(qǐng)求參數(shù)的類(lèi)型映射過(guò)去,如果有多個(gè)參數(shù)就不知道映射關(guān)系了
            if (args.length > 1) {
                throw new BizException("加密接口方法只支持一個(gè)參數(shù),請(qǐng)修改");
            }
            // args.length=0沒(méi)有請(qǐng)求參數(shù),就說(shuō)明沒(méi)必要解密,因?yàn)榻涌趬焊唤邮諈?shù),即使使用者無(wú)腦開(kāi)啟的該接口的參數(shù)加密,這里不做任何邏輯即可
            if (args.length == 1) {
                RequestBodyWrapper requestBodyWrapper;
                if (request instanceof RequestBodyWrapper) {
                    requestBodyWrapper = (RequestBodyWrapper) request;
                } else {
                    requestBodyWrapper = new RequestBodyWrapper(request);
                }
                String body = requestBodyWrapper.getBody();
                apiSecurityParam = JSONObject.parseObject(body, ApiSecurityParam.class);
                // 通過(guò)RSA私鑰解密獲取到aes秘鑰
                String aesKey = RSAUtil.decryptByPrivateKey(apiSecurityParam.getKey(), apiSecurityProperties.getRsaPrivateKey());
                // 通過(guò)aes秘鑰解密data參數(shù)數(shù)據(jù)
                String data = AESUtil.decrypt(apiSecurityParam.getData(), aesKey);
                //獲取接口入?yún)⒌念?lèi)
                Class<?> c = args[0].getClass();
                //將獲取解密后的真實(shí)參數(shù),封裝到接口入?yún)⒌念?lèi)中
                Object o = JSONObject.parseObject(data, c);
                newArgs = new Object[]{o};
            }
        }
        // 驗(yàn)簽
        if (isSign) {
            verifySign(request, newArgs.length == 0 ? null : newArgs[0], apiSecurityParam);
        }
        return joinPoint.proceed(newArgs);
    }

    void verifySign(HttpServletRequest request, Object o, ApiSecurityParam apiSecurityParam) {
        // 如果請(qǐng)求參數(shù)是加密傳輸?shù)模蔷拖葟腁piSecurityParam獲取簽名和時(shí)間戳等等。
        // 如果請(qǐng)求參數(shù)不是加密傳輸?shù)模敲碅piSecurityParam的字段取值都為null,這時(shí)候在請(qǐng)求的header里面獲取參數(shù)信息
        String sign = apiSecurityParam.getSign();
        if (StringUtils.isBlank(sign)) {
            sign = request.getHeader("X-Sign");
        }
        if (StringUtils.isBlank(sign)) {
            throw new BizException("簽名不能為空");
        }

        String nonce = apiSecurityParam.getNonce();
        if (StringUtils.isBlank(nonce)) {
            nonce = request.getHeader("X-Nonce");
        }
        if (StringUtils.isBlank(nonce)) {
            throw new BizException("唯一標(biāo)識(shí)不能為空");
        }

        String timestamp = apiSecurityParam.getTimestamp();
        Long t;
        if (StringUtils.isBlank(timestamp)) {
            timestamp = request.getHeader("X-Timestamp");
        }
        if (StringUtils.isBlank(timestamp)) {
            throw new BizException("時(shí)間戳不能為空");
        } else {
            try {
                t = Long.valueOf(timestamp);
            } catch (Exception e) {
                throw new BizException("非法的時(shí)間戳");
            }
        }

        // 判斷timestamp時(shí)間戳與當(dāng)前時(shí)間是否超過(guò)簽名有效時(shí)長(zhǎng)(過(guò)期時(shí)間根據(jù)業(yè)務(wù)情況進(jìn)行配置),如果超過(guò)了就提示簽名過(guò)期
        long now = System.currentTimeMillis() / 1000;
        if (now - t > apiSecurityProperties.getValidTime()) {
            throw new BizException("簽名已過(guò)期");
        }

        // 判斷nonce
        boolean nonceExists = stringRedisTemplate.hasKey(NONCE_KEY + nonce);
        if (nonceExists) {
            //請(qǐng)求重復(fù)
            throw new BizException("唯一標(biāo)識(shí)nonce已存在");
        }

        // 驗(yàn)簽
        SortedMap sortedMap = SignUtil.beanToMap(o);
        String content = SignUtil.getContent(sortedMap, nonce, timestamp);
        boolean flag = RSAUtil.verifySignByPublicKey(content, sign, apiSecurityProperties.getRsaPublicKey());
        if (!flag) {
            throw new BizException("簽名驗(yàn)證不通過(guò)");
        }

        stringRedisTemplate.opsForValue().set(NONCE_KEY+ nonce, "1", apiSecurityProperties.getValidTime(),
                TimeUnit.SECONDS);
    }


    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        return request;
    }


    private ApiSecurity getApiSecurity(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        ApiSecurity apiSecurity = method.getAnnotation(ApiSecurity.class);
        if (Objects.isNull(apiSecurity)) {
            apiSecurity = method.getDeclaringClass().getAnnotation(ApiSecurity.class);
        }
        return apiSecurity;
    }
}

這代碼沒(méi)什么好講的了,就按照上面的加密、加簽流程圖邏輯實(shí)現(xiàn)的,而且注釋也很清楚,可以自己慢慢消化,這里面涉及的工具類(lèi)如RSAUtil、AESUtil、SignUtil等,礙于文章代碼篇幅,我就這里就在一一展示,我會(huì)在文章后面放上全部代碼的項(xiàng)目github地址以供下載的。

上面的切面只完成了接口參數(shù)的解密和驗(yàn)簽,至于對(duì)響應(yīng)參數(shù)的加密返回我們放到了ResponseBodyAdvice中實(shí)現(xiàn)。

@RestControllerAdvice
@Slf4j
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private ApiSecurityProperties apiSecurityProperties;


    /**
     * 判斷類(lèi)或者方法是否使用了 @ResponseResultBody
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseResultBody.class)
                || returnType.hasMethodAnnotation(ResponseResultBody.class)
                || AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ApiSecurity.class)
                || returnType.hasMethodAnnotation(ApiSecurity.class);
    }

    /**
     * 當(dāng)類(lèi)或者方法使用了 @ResponseResultBody 就會(huì)調(diào)用這個(gè)方法
     * 如果返回類(lèi)型是string,那么springmvc是直接返回的,此時(shí)需要手動(dòng)轉(zhuǎn)化為json
     * 因?yàn)楫?dāng)body都為null時(shí),下面的非加密下的if判斷參數(shù)類(lèi)型的條件都不滿足,如果接口返回類(lèi)似為String,
     * 會(huì)報(bào)錯(cuò)com.shepherd.fast.global.ResponseVO cannot be cast to java.lang.String
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        Method method = returnType.getMethod();
        Class<?> returnClass = method.getReturnType();
        Boolean enable = apiSecurityProperties.getEnable();
        ApiSecurity apiSecurity = method.getAnnotation(ApiSecurity.class);
        if (Objects.isNull(apiSecurity)) {
            apiSecurity = method.getDeclaringClass().getAnnotation(ApiSecurity.class);
        }
        if (enable && Objects.nonNull(apiSecurity) && apiSecurity.encryptResponse() && Objects.nonNull(body)) {
            // 只需要加密返回data數(shù)據(jù)內(nèi)容
            if (body instanceof ResponseVO) {
                body = ((ResponseVO) body).getData();
            }
            JSONObject jsonObject = encryptResponse(body);
            body = jsonObject;
        } else {
            if (body instanceof String || Objects.equals(returnClass, String.class)) {
                String value = objectMapper.writeValueAsString(ResponseVO.success(body));
                return value;
            }
            // 防止重復(fù)包裹的問(wèn)題出現(xiàn)
            if (body instanceof ResponseVO) {
                return body;
            }
        }
        return ResponseVO.success(body);
    }

    JSONObject encryptResponse(Object result) {
        String aseKey = AESUtil.generateAESKey();
        String content = JSONObject.toJSONString(result);
        String data = AESUtil.encrypt(content, aseKey);
        String key = RSAUtil.encryptByPublicKey(aseKey, apiSecurityProperties.getRsaPublicKey());
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("key", key);
        jsonObject.put("data", data);
        return jsonObject;
    }

}

這里就是對(duì)接口返回參數(shù)格式進(jìn)行統(tǒng)一ResponseVO,同時(shí)判斷是否需要進(jìn)行返回參數(shù)加密執(zhí)行相應(yīng)邏輯即可。

4.總結(jié)

至此,對(duì)于Spring Boot如何提高接口安全性的思路與實(shí)現(xiàn)已講完,同時(shí)我們也盡量進(jìn)行了抽取封裝,做到了極致的優(yōu)雅實(shí)現(xiàn)。當(dāng)然這里還是要再次強(qiáng)調(diào)一下以上的思路實(shí)現(xiàn)是不能絕對(duì)保證接口安全性的,只能做到”防君子不妨小人“,可以這么說(shuō)假如不做加密加簽這些保護(hù)措施,黑客破解接口就會(huì)不費(fèi)吹灰之力,自己就經(jīng)歷過(guò)我在個(gè)人的阿里云服務(wù)器部署的MySQL服務(wù),為了訪問(wèn)簡(jiǎn)單省事,我直接在外網(wǎng)暴露了3306端口,同時(shí)用戶名密碼都是root,就被黑客黑了勒索比特幣。

圖片圖片

幸好里面數(shù)據(jù)不是很重要,我后面把暴露端口映射成其他的了,密碼也改了一個(gè)復(fù)雜的,我看你再給我破解了~~~。說(shuō)這些我就想表達(dá)這里的加密、加簽就是為了加大破解接口的復(fù)雜度,有了加密、加簽保障想破解不費(fèi)九牛二虎之力是不行的。

本文轉(zhuǎn)載自微信公眾號(hào)「Shepherd進(jìn)階筆記」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系公眾號(hào)。

責(zé)任編輯:武曉燕 來(lái)源: Shepherd進(jìn)階筆記
相關(guān)推薦

2022-03-10 14:17:11

區(qū)塊鏈數(shù)據(jù)安全技術(shù)

2022-07-13 16:39:54

數(shù)據(jù)中心數(shù)據(jù)安全

2022-08-03 14:33:21

數(shù)據(jù)安全數(shù)據(jù)泄露漏洞

2012-05-14 11:39:58

2012-08-22 10:27:16

2011-10-11 09:13:15

2012-07-30 10:07:01

2015-05-05 15:53:01

2011-03-11 14:05:41

2022-03-25 14:18:35

區(qū)塊鏈安全支付

2010-09-25 13:34:19

2015-04-23 11:38:00

2009-10-12 12:51:50

2021-10-12 16:25:35

物聯(lián)網(wǎng)物聯(lián)網(wǎng)安全IoT

2009-12-22 18:52:06

WCF安全性

2024-06-06 16:44:21

2018-02-27 14:50:16

數(shù)據(jù)庫(kù)MySQL安全性

2022-09-20 14:48:09

零信任安全隱私

2013-10-22 10:24:05

2023-07-11 10:38:24

區(qū)塊鏈文件驗(yàn)證安全
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

欧美激情护士| 91sao在线观看国产| 国产区亚洲区欧美区| 无人码人妻一区二区三区免费| 国产一级特黄aaa大片| 2019年精品视频自拍| 福利电影一区二区三区| 中文字幕av一区中文字幕天堂| 国产爆乳无码一区二区麻豆| 中文字幕人妻丝袜乱一区三区| 欧美午夜18电影| 一区二区高清视频在线观看| 国产日韩欧美日韩| 欧美丰满艳妇bbwbbw| 欧美男男gaygay1069| 久久综合国产精品| 久久久亚洲国产天美传媒修理工| 中文字幕第17页| sese一区| 另类尿喷潮videofree| 亚洲素人一区二区| 国产在线日韩在线| 日本一级淫片免费放| 99精品在线| 欧美日韩精品一区二区三区蜜桃 | 日韩黄色三级视频| 成人羞羞视频在线看网址| 一本一本久久a久久精品综合麻豆| 精品国产免费一区二区三区| 日本学生初尝黑人巨免费视频| 日韩.com| 911精品国产一区二区在线| 中文字幕中文字幕99| 国产精品久久久久久久免费 | 日本综合字幕| 中文在线免费| 欧美大片免费观看网址| 亚洲欧美视频一区| 视频在线精品一区| 中文字幕乱码中文字幕| 亚洲大胆av| 日韩av最新在线| 欧美 国产 综合| 色视频在线观看免费| 国产精品日本| 在线看日韩欧美| 日韩av卡一卡二| 黄色免费网站在线观看| 国产精品18久久久久| 久久久久久久影院| 中文字幕在线看高清电影| 裤袜国产欧美精品一区| 欧美激情一区在线观看| 成人免费视频a| 国产色无码精品视频国产| 欧美第一在线视频| 午夜视频一区二区三区| 日本一区二区免费看| 亚洲视频一区二区三区四区| 欧美日韩蜜桃| 国产亚洲精品91在线| 成年人网站av| 成人性生交大片免费观看网站| 亚洲国产欧美在线人成| 日韩欧美99| a级片在线视频| 一区二区三区福利| 中文字幕一精品亚洲无线一区| 欧洲女同同性吃奶| 国产日韩视频在线| 精品久久一区二区| 老头吃奶性行交视频| www视频在线免费观看 | www男人天堂| 亚洲欧洲美洲av| 亚洲欧美偷拍三级| 欧美在线观看黄| 国产精品秘入口| 成人性生交大片免费| 国产免费观看久久黄| 国产精品第108页| 91综合网人人| 亚洲免费中文字幕| 娇妻高潮浓精白浆xxⅹ| 成人性片免费| 欧美日韩在线视频一区二区| 国产又粗又长又爽视频| 国产视频网址在线| 亚洲欧美一区二区在线观看| 欧美日韩精品免费看| 亚洲国产欧美另类| 久久国产精品99久久人人澡| 国产91成人video| 欧产日产国产v| 日韩午夜av| 国产精品久久久久久影视 | 午夜精品久久久久久久蜜桃app| 欧美综合在线播放| av在线导航| 亚洲国产精品久久不卡毛片| 亚洲人成色77777| 国产99在线| 亚洲一区二区欧美| 女人床在线观看| 麻豆mv在线观看| 欧美三级日本三级少妇99| 免费观看日韩毛片| 韩日毛片在线观看| 欧美男女性生活在线直播观看| 国产精品久久久久9999小说| 国产一区二区三区亚洲综合| 欧美情侣在线播放| 中文字幕天堂网| 日韩精品免费一区二区在线观看| 国模极品一区二区三区| 中文字幕人妻色偷偷久久| 白白色 亚洲乱淫| 国产精品视频免费一区二区三区| 精品国产伦一区二区三| 国产一区二区视频在线播放| 成人午夜小视频| 日韩在线免费看| 亚洲精品国产精华液| 亚洲av首页在线| 在线观看h网| 欧美性大战xxxxx久久久| 欧美伦理片在线看| 北条麻妃一区二区三区在线| 精品粉嫩aⅴ一区二区三区四区| 日本女人黄色片| 精品国产一区二区三区噜噜噜| 亚洲人精品午夜在线观看| wwwwxxxx国产| 亚洲夜间福利| 99re在线| 无码国产色欲xxxx视频| 久久综合网色—综合色88| 欧美美女黄色网| 亚洲男人在线| 精品91自产拍在线观看一区| 亚洲熟女毛茸茸| 国内精品久久久久久久影视蜜臀 | 精品无码国产污污污免费网站 | 久久久蜜桃一区二区人| 国产精品va在线播放| 中文文字幕一区二区三三| 91在线精品一区二区| 亚洲精品乱码久久久久久蜜桃91 | 最近2019免费中文字幕视频三 | 欧美激情视频三区| 五月婷婷开心网| 日韩电影免费在线看| 成人性教育视频在线观看| 99re热久久这里只有精品34| 一区二区高清在线| 日本亚洲一区二区三区| 午夜影院欧美| 26uuu亚洲伊人春色| 午夜精品久久久久久久爽| 亚洲精品一二三| 18禁免费观看网站| 好吊妞视频这里有精品| 亚洲性线免费观看视频成熟| 999这里只有精品| 91视视频在线观看入口直接观看www | 日韩av中文字幕在线播放| 国产成人在线免费观看视频| 久久亚洲二区| 亚洲综合大片69999| 人妻一区二区三区免费| 国产午夜亚洲精品午夜鲁丝片| 国产一区一区三区| 精品国产第一福利网站| 亚洲欧美www| 国产精品无码粉嫩小泬| 国产 欧美在线| 久久久久久久久久网| 三级精品视频| 欧美二区乱c黑人| 亚洲精品国偷拍自产在线观看蜜桃| 亚洲综合免费观看高清完整版在线| 韩国三级在线看| 成人在线免费观看91| 国产在线视频欧美| 欧美性受ⅹ╳╳╳黑人a性爽| 欧美色视频在线| 午夜精品一区二区三级视频| 国产成人激情av| 欧美三级一级片| 第一sis亚洲原创| 91视频网页| 在线视频三区| 日韩欧美国产高清| 久草手机视频在线观看| 国产成人免费xxxxxxxx| 国产中文字幕在线免费观看| 成人毛片免费看| 99精品国产一区二区| 九色porny自拍视频在线播放| 亚洲性视频网站| www.爱爱.com| 欧美中文字幕一区二区三区| 国产精品久久久免费观看| 中文字幕一区二区三区在线视频 | 免费看的黄色欧美网站| 91精品久久久久久蜜桃| 中文字幕资源网在线观看免费 | 亚乱亚乱亚洲乱妇| 欧美日韩亚洲天堂| 中文字幕精品视频在线| 欧美视频日韩| 欧美18视频| 最新欧美色图| 久久亚洲影音av资源网| 一区二区 亚洲| 国产精品久久久一本精品| 午夜国产一区二区三区| 欧美精品一二| 国产精品久久久久久久久久久久午夜片| 625成人欧美午夜电影| 久久久av亚洲男天堂| 国产精品亚洲欧美在线播放| 精品久久久久久久久久| 极品白嫩丰满美女无套| 9久re热视频在线精品| 在线观看欧美亚洲| 综合色就爱涩涩涩综合婷婷| 国产不卡在线观看| 国产高清av在线| 精品福利av导航| 国产精品无码免费播放| 色屁屁一区二区| 大吊一区二区三区| 国产在线精品免费av| 欧美私人情侣网站| 夜久久久久久| 999一区二区三区| 一区二区三区网站| 亚洲三区在线| 亚洲va欧美va人人爽成人影院| 欧美肥臀大乳一区二区免费视频| 91短视频版在线观看www免费| 日韩黄在线观看| 少妇人妻一区二区| 在线免费亚洲电影| 日本熟女毛茸茸| 中文字幕一区二| 911亚洲精选| 国产成人综合在线观看| 久久久久久综合网| 亚洲精品四区| 欧美午夜视频在线| 首页亚洲中字| 久久国产精品99久久久久久丝袜| 欧美日韩精品一区二区三区视频| 欧美亚洲国产视频小说| 蜜桃视频www网站在线观看| 欧美激情一级欧美精品| 国产白丝在线观看| 在线精品播放av| 超碰免费在线| 最近2019中文字幕第三页视频| 番号集在线观看| 中国日韩欧美久久久久久久久| 国产精品ⅴa有声小说| 伊人久久精品视频| 在线免费看av| 久久色精品视频| 在线heyzo| 亚洲**2019国产| а√天堂在线官网| 欧美激情奇米色| 黄色激情在线播放| 国产成人97精品免费看片| 日本欧美一区| 91影视免费在线观看| 亚洲播播91| 国产欧美日韩亚洲精品| 国产欧美日韩电影| 国产精品yjizz| 四虎影视国产精品| 国产精彩精品视频| 123成人网| 成人免费视频视频在| 天堂网av成人| 亚洲精品成人久久久998| 亚洲有吗中文字幕| 国产精品无码一区二区在线| 日韩精品国产欧美| 激情久久综合网| 99国产精品久久久久久久久久 | 在线视频免费观看一区| 欧美一区二区三区的| 久久精品五月天| 亚洲精品国久久99热| 国产午夜福利精品| 欧美视频自拍偷拍| 丰满熟妇乱又伦| 日韩欧美一级二级三级久久久| 无码精品视频一区二区三区| 中文字幕无线精品亚洲乱码一区 | www.射射射| 夜间精品视频| 99爱视频在线| 国内久久精品视频| 欧美成人福利在线观看| 国产99久久久国产精品免费看| 一区二区三区免费在线观看视频| 亚洲伊人伊成久久人综合网| 在线精品高清中文字幕| 7777kkk亚洲综合欧美网站| 伦伦影院午夜日韩欧美限制| 欧美gv在线观看| 91午夜理伦私人影院| 欧美人与物videos另类xxxxx| 精品国产中文字幕| 偷拍欧美精品| 黄色a级片免费| 视频一区欧美日韩| 国产xxx在线观看| 国产精品三级av| 美日韩一二三区| 欧美成人伊人久久综合网| 97最新国自产拍视频在线完整在线看| 欧美激情久久久久久| 四虎精品永久免费| 亚洲v欧美v另类v综合v日韩v| 亚洲区国产区| 亚洲成人激情小说| 成人av电影免费在线播放| 黄色性生活一级片| 一区二区三区国产豹纹内裤在线| 中文字幕有码视频| 亚洲欧美国产日韩天堂区| av免费在线视| 国产福利久久精品| 亚洲精品网址| 小明看看成人免费视频| 日本一区二区三区高清不卡 | 一本大道av伊人久久综合| 黄色av一区二区三区| 欧美国产精品人人做人人爱| 国产亚洲观看| 强伦女教师2:伦理在线观看| 免费久久99精品国产| 亚洲黄色片免费看| 中文字幕国产一区| 日韩欧美国产成人精品免费| 欧美视频在线不卡| 国产高清视频免费最新在线| 日本不卡视频在线播放| 久久久久久一区二区三区四区别墅| 蜜桃欧美视频| 午夜宅男久久久| 成年人网站免费看| 亚洲免费观看高清完整版在线观看 | 欧美一级淫片videoshd| 红杏一区二区三区| 日韩中文字幕在线视频观看| 97久久精品人人爽人人爽蜜臀| 国产成人自拍视频在线| 精品一区电影国产| 黄视频网站在线看| 91久久国产综合久久91精品网站| 欧美疯狂party性派对| 天堂中文视频在线| 成人黄色av网站在线| 日本三级片在线观看| 亚洲精品日韩久久久| 欧美xnxx| 在线国产精品网| 国产麻豆一精品一av一免费| 久久精品国产亚洲AV熟女| 色综合色综合色综合色综合色综合 | 懂色av蜜臀av粉嫩av永久| 亚洲国产va精品久久久不卡综合| 亚洲精品成人电影| 97精品久久久| 国产精品三级| 久久久久久久久久一区二区| 亚洲三级电影全部在线观看高清| 精品人妻一区二区三区三区四区| 欧美黄色小视频| 香蕉久久精品日日躁夜夜躁| 岛国av在线免费| 一区二区三区四区在线| 国产精品国产高清国产| 国产精品久久久久影院日本| 亚洲一级毛片| 97超碰在线免费观看| 欧美日韩一区中文字幕| 综合久久2o19| 欧美日韩成人一区二区三区 | 国产精品91在线观看| 国产精品伦理久久久久久| 妖精视频一区二区| 欧美亚洲精品一区| 欧美1—12sexvideos| 欧美日韩在线观看一区| 国产伦精一区二区三区|