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

如何為開放平臺設計一個安全好用的OpenApi

這篇具有很好參考價值的文章主要介紹了如何為開放平臺設計一個安全好用的OpenApi。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

為了確保軟件接口的標準化和規(guī)范化,實現(xiàn)業(yè)務模塊的重用性和靈活性,并提高接口的易用性和安全性,OpenAPI規(guī)范應運而生。這一規(guī)范通過制定統(tǒng)一的接口協(xié)議,規(guī)定了接口的格式、參數(shù)、響應和使用方法等內容,從而提高了接口的可維護性和可擴展性。同時,為了也需要考慮接口的安全性和穩(wěn)定性,本文將針對這些方面介紹一些具體的實踐方式。

1一、AppId和AppSecret

AppId的使用

AppId作為一種全局唯一的標識符,其作用主要在于方便用戶身份識別以及數(shù)據(jù)分析等方面。為了防止其他用戶通過惡意使用別人的AppId來發(fā)起請求,一般都會采用配對AppSecret的方式,類似于一種密碼。AppIdAppSecret通常會組合生成一套簽名,并按照一定規(guī)則進行加密處理。在請求方發(fā)起請求時,需要將這個簽名值一并提交給提供方進行驗證。如果簽名驗證通過,則可以進行數(shù)據(jù)交互,否則將被拒絕。這種機制能夠保證數(shù)據(jù)的安全性和準確性,提高系統(tǒng)的可靠性和可用性。

AppId的生成

正如前面所說,AppId就是有一個身份標識,生成時只要保證全局唯一即可。

AppSecret生成

AppSecret就是密碼,按照一般的的密碼安全性要求生成即可。

2二、sign簽名

RSASignature

首先,在介紹簽名方式之前,我們必須先了解2個概念,分別是:非對稱加密算法(比如:RSA)、摘要算法(比如:MD5)。

簡單來說,非對稱加密的應用場景一般有兩種,一種是公鑰加密,私鑰解密,可以應用在加解密場景中(不過由于非對稱加密的效率實在不高,用的比較少),還有一種就是結合摘要算法,把信息經(jīng)過摘要后,再用私鑰加密,公鑰用來解密,可以應用在簽名場景中,也是我們將要使用到的方式。

大致看看RSASignature簽名的方式,稍后用到SHA256withRSA底層就是使用的這個方法。

openapi,microsoft,服務器,php

摘要算法與非對稱算法的最大區(qū)別就在于,它是一種不需要密鑰的且不可逆的算法,也就是一旦明文數(shù)據(jù)經(jīng)過摘要算法計算后,得到的密文數(shù)據(jù)一定是不可反推回來的。

簽名的作用

好了,現(xiàn)在我們再來看看簽名,簽名主要可以用在兩個場景,一種是數(shù)據(jù)防篡改,一種是身份防冒充,實際上剛好可以對應上前面我們介紹的兩種算法。

數(shù)據(jù)防篡改

顧名思義,就是防止數(shù)據(jù)在網(wǎng)絡傳輸過程中被修改,摘要算法可以保證每次經(jīng)過摘要算法的原始數(shù)據(jù),計算出來的結果都一樣,所以一般接口提供方只要用同樣的原數(shù)據(jù)經(jīng)過同樣的摘要算法,然后與接口請求方生成的數(shù)據(jù)進行比較,如果一致則表示數(shù)據(jù)沒有被篡改過。

身份防冒充

這里身份防冒充,我們就要使用另一種方式,比如SHA256withRSA,其實現(xiàn)原理就是先用數(shù)據(jù)進行SHA256計算,然后再使用RSA私鑰加密,對方解的時候也一樣,先用RSA公鑰解密,然后再進行SHA256計算,最后看結果是否匹配。

3三、使用示例

前置準備

  1. 在沒有自動化開放平臺時,appId、appSecret可直接通過線下的方式給到接入方,appSecret需要接入方自行保存好,避免泄露。也可以自行

  2. 公私鑰可以由接口提供方來生成,同樣通過線下的方式,把私鑰交給對方,并要求對方需保密。

交互流程

openapi,microsoft,服務器,php

客戶端準備

  1. 接口請求方,首先把業(yè)務參數(shù),進行摘要算法計算,生成一個簽名(sign)

UserEntity?userEntity?=?new?UserEntity();
userEntity.setUserId("1");
userEntity.setPhone("13912345678");


String?sign?=?getSHA256Str(JSONObject.toJSONString(userEntity));

sign=c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5

  1. 然后繼續(xù)拼接header部的參數(shù),可以使用&符合連接,使用Set集合完成自然排序,并且過濾參數(shù)為空的key,最后使用私鑰加簽的方式,得到appSign。

Map<String,?String>?data?=?Maps.newHashMap();
data.put("appId",?appId);
data.put("nonce",?nonce);
data.put("sign",?sign);
data.put("timestamp",?timestamp);
Set<String>?keySet?=?data.keySet();
String[]?keyArray?=?keySet.toArray(new?String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder?sb?=?new?StringBuilder();
for?(String?k?:?keyArray)?{
????if?(data.get(k).trim().length()?>?0)?
????????sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("appSecret=").append(appSecret);
System.out.println("【請求方】拼接后的參數(shù):"?+?sb.toString());
System.out.println();

【請求方】拼接后的參數(shù):appId=123456&nonce=1234&sign=c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5&timestamp=1653057661381&appSecret=654321

【請求方】appSign:m/xk0fkDZlHEkbYSpCPdpbriG/EWG9gNZtInoYOu2RtrLMzHNM0iZe1iL4p/+IedAJN2jgG9pS5o5NZH1i55TVoTbZePdCbR9CEJoHq2TZLIiKPeoRgDimAl14V5jHZiMQCXS8RxWT63W8MKFyZQtB7xCtxVD7+IvLGQOAWn7QX+EmfAUvhgjkaVf2YLk9J9LqtyjfTYeloiP901ZsBZo5y9Gs5P73b+JoEcxmGZRv+Fkv3HnHWTQEpl7W6Lrmd0j44/XupwzHxaanRo5k0ALOVSFohdyMtHk3eOYx/bj+GeMKf8PN4J4tsPndnjyu4XUOnh74aaW9oC2DLiIzr4+Q==

  1. 最后把參數(shù)組裝,發(fā)送給接口提供方。

Header?header?=?Header.builder()
????????.appId(appId)
????????.nonce(nonce)
????????.sign(sign)
????????.timestamp(timestamp)
????????.appSign(appSign)
????????.build();
APIRequestEntity?apiRequestEntity?=?new?APIRequestEntity();
apiRequestEntity.setHeader(header);
apiRequestEntity.setBody(userEntity);
String?requestParam?=?JSONObject.toJSONString(apiRequestEntity);
System.out.println("【請求方】接口請求參數(shù):?"?+?requestParam);

【請求方】接口請求參數(shù):?{"body":{"phone":"13912345678","userId":"1"},"header":{"appId":"123456","appSign":"m/xk0fkDZlHEkbYSpCPdpbriG/EWG9gNZtInoYOu2RtrLMzHNM0iZe1iL4p/+IedAJN2jgG9pS5o5NZH1i55TVoTbZePdCbR9CEJoHq2TZLIiKPeoRgDimAl14V5jHZiMQCXS8RxWT63W8MKFyZQtB7xCtxVD7+IvLGQOAWn7QX+EmfAUvhgjkaVf2YLk9J9LqtyjfTYeloiP901ZsBZo5y9Gs5P73b+JoEcxmGZRv+Fkv3HnHWTQEpl7W6Lrmd0j44/XupwzHxaanRo5k0ALOVSFohdyMtHk3eOYx/bj+GeMKf8PN4J4tsPndnjyu4XUOnh74aaW9oC2DLiIzr4+Q==","nonce":"1234","sign":"c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5","timestamp":"1653057661381"}}

openapi,microsoft,服務器,php

服務端準備

  1. 從請求參數(shù)中,先獲取body的內容,然后簽名,完成對參數(shù)校驗

Header?header?=?apiRequestEntity.getHeader();
UserEntity?userEntity?=?JSONObject.parseObject(JSONObject.toJSONString(apiRequestEntity.getBody()),?UserEntity.class);

String?sign?=?getSHA256Str(JSONObject.toJSONString(userEntity));
if?(!sign.equals(header.getSign()))?{
????throw?new?Exception("數(shù)據(jù)簽名錯誤!");
}

  1. header中獲取相關信息,并使用公鑰進行驗簽,完成身份認證

String?appId?=?header.getAppId();
String?appSecret?=?getAppSecret(appId);
String?nonce?=?header.getNonce();
String?timestamp?=?header.getTimestamp();

Map<String,?String>?data?=?Maps.newHashMap();
data.put("appId",?appId);
data.put("nonce",?nonce);
data.put("sign",?sign);
data.put("timestamp",?timestamp);
Set<String>?keySet?=?data.keySet();
String[]?keyArray?=?keySet.toArray(new?String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder?sb?=?new?StringBuilder();
for?(String?k?:?keyArray)?{
????if?(data.get(k).trim().length()?>?0)?
????????sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("appSecret=").append(appSecret);
if?(!rsaVerifySignature(sb.toString(),?appKeyPair.get(appId).get("publicKey"),?header.getAppSign()))?{
????throw?new?Exception("公鑰驗簽錯誤!");
}
System.out.println();
System.out.println("【提供方】驗證通過!");

完整代碼示例

package?openApi;

import?com.alibaba.fastjson.JSONObject;
import?com.google.common.collect.Maps;
import?lombok.SneakyThrows;
import?org.apache.commons.codec.binary.Hex;

import?java.nio.charset.StandardCharsets;
import?java.security.*;
import?java.security.interfaces.RSAPrivateKey;
import?java.security.interfaces.RSAPublicKey;
import?java.security.spec.PKCS8EncodedKeySpec;
import?java.security.spec.X509EncodedKeySpec;
import?java.util.*;


public?class?AppUtils?{

????
?????*?key:appId、value:appSecret
?????*/
????static?Map<String,?String>?appMap?=?Maps.newConcurrentMap();

????
?????*?分別保存生成的公私鑰對
?????*?key:appId,value:公私鑰對
?????*/
????static?Map<String,?Map<String,?String>>?appKeyPair?=?Maps.newConcurrentMap();

????public?static?void?main(String[]?args)?throws?Exception?{
????????
????????String?appId?=?initAppInfo();

????????
????????initKeyPair(appId);

????????
????????String?requestParam?=?clientCall();

????????
????????serverVerify(requestParam);

????}

????private?static?String?initAppInfo()?{
????????
????????String?appId?=?"123456";
????????String?appSecret?=?"654321";
????????appMap.put(appId,?appSecret);
????????return?appId;
????}

????private?static?void?serverVerify(String?requestParam)?throws?Exception?{
????????APIRequestEntity?apiRequestEntity?=?JSONObject.parseObject(requestParam,?APIRequestEntity.class);
????????Header?header?=?apiRequestEntity.getHeader();
????????UserEntity?userEntity?=?JSONObject.parseObject(JSONObject.toJSONString(apiRequestEntity.getBody()),?UserEntity.class);

????????
????????String?sign?=?getSHA256Str(JSONObject.toJSONString(userEntity));
????????if?(!sign.equals(header.getSign()))?{
????????????throw?new?Exception("數(shù)據(jù)簽名錯誤!");
????????}

????????
????????String?appId?=?header.getAppId();
????????String?appSecret?=?getAppSecret(appId);
????????String?nonce?=?header.getNonce();
????????String?timestamp?=?header.getTimestamp();

????????
????????Map<String,?String>?data?=?Maps.newHashMap();
????????data.put("appId",?appId);
????????data.put("nonce",?nonce);
????????data.put("sign",?sign);
????????data.put("timestamp",?timestamp);
????????Set<String>?keySet?=?data.keySet();
????????String[]?keyArray?=?keySet.toArray(new?String[keySet.size()]);
????????Arrays.sort(keyArray);
????????StringBuilder?sb?=?new?StringBuilder();
????????for?(String?k?:?keyArray)?{
????????????if?(data.get(k).trim().length()?>?0)?
????????????????sb.append(k).append("=").append(data.get(k).trim()).append("&");
????????}
????????sb.append("appSecret=").append(appSecret);


????????if?(!rsaVerifySignature(sb.toString(),?appKeyPair.get(appId).get("publicKey"),?header.getAppSign()))?{
????????????throw?new?Exception("公鑰驗簽錯誤!");
????????}

????????System.out.println();
????????System.out.println("【提供方】驗證通過!");

????}

????public?static?String?clientCall()?{
????????
????????String?appId?=?"123456";
????????String?appSecret?=?"654321";
????????String?timestamp?=?String.valueOf(System.currentTimeMillis());
????????
????????String?nonce?=?"1234";

????????
????????UserEntity?userEntity?=?new?UserEntity();
????????userEntity.setUserId("1");
????????userEntity.setPhone("13912345678");

????????
????????String?sign?=?getSHA256Str(JSONObject.toJSONString(userEntity));

????????Map<String,?String>?data?=?Maps.newHashMap();
????????data.put("appId",?appId);
????????data.put("nonce",?nonce);
????????data.put("sign",?sign);
????????data.put("timestamp",?timestamp);
????????Set<String>?keySet?=?data.keySet();
????????String[]?keyArray?=?keySet.toArray(new?String[keySet.size()]);
????????Arrays.sort(keyArray);
????????StringBuilder?sb?=?new?StringBuilder();
????????for?(String?k?:?keyArray)?{
????????????if?(data.get(k).trim().length()?>?0)?
????????????????sb.append(k).append("=").append(data.get(k).trim()).append("&");
????????}
????????sb.append("appSecret=").append(appSecret);

????????System.out.println("【請求方】拼接后的參數(shù):"?+?sb.toString());
????????System.out.println();

????????
????????String?appSign?=?sha256withRSASignature(appKeyPair.get(appId).get("privateKey"),?sb.toString());
????????System.out.println("【請求方】appSign:"?+?appSign);
????????System.out.println();

????????
????????Header?header?=?Header.builder()
????????????????.appId(appId)
????????????????.nonce(nonce)
????????????????.sign(sign)
????????????????.timestamp(timestamp)
????????????????.appSign(appSign)
????????????????.build();
????????APIRequestEntity?apiRequestEntity?=?new?APIRequestEntity();
????????apiRequestEntity.setHeader(header);
????????apiRequestEntity.setBody(userEntity);

????????String?requestParam?=?JSONObject.toJSONString(apiRequestEntity);
????????System.out.println("【請求方】接口請求參數(shù):?"?+?requestParam);

????????return?requestParam;
????}


????
?????*?私鑰簽名
?????*
?????*?@param?privateKeyStr
?????*?@param?dataStr
?????*?@return
?????*/
????public?static?String?sha256withRSASignature(String?privateKeyStr,?String?dataStr)?{
????????try?{
????????????byte[]?key?=?Base64.getDecoder().decode(privateKeyStr);
????????????byte[]?data?=?dataStr.getBytes();
????????????PKCS8EncodedKeySpec?keySpec?=?new?PKCS8EncodedKeySpec(key);
????????????KeyFactory?keyFactory?=?KeyFactory.getInstance("RSA");
????????????PrivateKey?privateKey?=?keyFactory.generatePrivate(keySpec);
????????????Signature?signature?=?Signature.getInstance("SHA256withRSA");
????????????signature.initSign(privateKey);
????????????signature.update(data);
????????????return?new?String(Base64.getEncoder().encode(signature.sign()));
????????}?catch?(Exception?e)?{
????????????throw?new?RuntimeException("簽名計算出現(xiàn)異常",?e);
????????}
????}

????
?????*?公鑰驗簽
?????*
?????*?@param?dataStr
?????*?@param?publicKeyStr
?????*?@param?signStr
?????*?@return
?????*?@throws?Exception
?????*/
????public?static?boolean?rsaVerifySignature(String?dataStr,?String?publicKeyStr,?String?signStr)?throws?Exception?{
????????KeyFactory?keyFactory?=?KeyFactory.getInstance("RSA");
????????X509EncodedKeySpec?x509EncodedKeySpec?=?new?X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
????????PublicKey?publicKey?=?keyFactory.generatePublic(x509EncodedKeySpec);
????????Signature?signature?=?Signature.getInstance("SHA256withRSA");
????????signature.initVerify(publicKey);
????????signature.update(dataStr.getBytes());
????????return?signature.verify(Base64.getDecoder().decode(signStr));
????}

????
?????*?生成公私鑰對
?????*
?????*?@throws?Exception
?????*/
????public?static?void?initKeyPair(String?appId)?throws?Exception?{
????????KeyPairGenerator?keyPairGenerator?=?KeyPairGenerator.getInstance("RSA");
????????keyPairGenerator.initialize(2048);
????????KeyPair?keyPair?=?keyPairGenerator.generateKeyPair();
????????RSAPublicKey?publicKey?=?(RSAPublicKey)?keyPair.getPublic();
????????RSAPrivateKey?privateKey?=?(RSAPrivateKey)?keyPair.getPrivate();
????????Map<String,?String>?keyMap?=?Maps.newHashMap();
????????keyMap.put("publicKey",?new?String(Base64.getEncoder().encode(publicKey.getEncoded())));
????????keyMap.put("privateKey",?new?String(Base64.getEncoder().encode(privateKey.getEncoded())));
????????appKeyPair.put(appId,?keyMap);
????}

????private?static?String?getAppSecret(String?appId)?{
????????return?String.valueOf(appMap.get(appId));
????}


????@SneakyThrows
????public?static?String?getSHA256Str(String?str)?{
????????MessageDigest?messageDigest;
????????messageDigest?=?MessageDigest.getInstance("SHA-256");
????????byte[]?hash?=?messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
????????return?Hex.encodeHexString(hash);
????}

}


4四、常見防護手段

timestamp

前面在接口設計中,我們使用到了timestamp,這個參數(shù)主要可以用來防止同一個請求參數(shù)被無限期的使用。

稍微修改一下原服務端校驗邏輯,增加了5分鐘有效期的校驗邏輯。

private?static?void?serverVerify(String?requestParam)?throws?Exception?{
????APIRequestEntity?apiRequestEntity?=?JSONObject.parseObject(requestParam,?APIRequestEntity.class);
????Header?header?=?apiRequestEntity.getHeader();
????UserEntity?userEntity?=?JSONObject.parseObject(JSONObject.toJSONString(apiRequestEntity.getBody()),?UserEntity.class);
????
????String?sign?=?getSHA256Str(JSONObject.toJSONString(userEntity));
????if?(!sign.equals(header.getSign()))?{
????????throw?new?Exception("數(shù)據(jù)簽名錯誤!");
????}
????
????String?appId?=?header.getAppId();
????String?appSecret?=?getAppSecret(appId);
????String?nonce?=?header.getNonce();
????String?timestamp?=?header.getTimestamp();
????
????
????long?now?=?LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
????if?((now?-?Long.parseLong(timestamp))?/?1000?/?60?>=?5)?{
????????throw?new?Exception("請求過期!");
????}
????
????cache.put(appId?+?"_"?+?nonce,?"1");
????
????Map<String,?String>?data?=?Maps.newHashMap();
????data.put("appId",?appId);
????data.put("nonce",?nonce);
????data.put("sign",?sign);
????data.put("timestamp",?timestamp);
????Set<String>?keySet?=?data.keySet();
????String[]?keyArray?=?keySet.toArray(new?String[0]);
????Arrays.sort(keyArray);
????StringBuilder?sb?=?new?StringBuilder();
????for?(String?k?:?keyArray)?{
????????if?(data.get(k).trim().length()?>?0)?
????????????sb.append(k).append("=").append(data.get(k).trim()).append("&");
????}
????sb.append("appSecret=").append(appSecret);
????if?(!rsaVerifySignature(sb.toString(),?appKeyPair.get(appId).get("publicKey"),?header.getAppSign()))?{
????????throw?new?Exception("驗簽錯誤!");
????}
????System.out.println();
????System.out.println("【提供方】驗證通過!");
}

nonce

nonce值是一個由接口請求方生成的隨機數(shù),在有需要的場景中,可以用它來實現(xiàn)請求一次性有效,也就是說同樣的請求參數(shù)只能使用一次,這樣可以避免接口重放攻擊。

具體實現(xiàn)方式:接口請求方每次請求都會隨機生成一個不重復的nonce值,接口提供方可以使用一個存儲容器(為了方便演示,我使用的是guava提供的本地緩存,生產環(huán)境中可以使用redis這樣的分布式存儲方式),每次先在容器中看看是否存在接口請求方發(fā)來的nonce值,如果不存在則表明是第一次請求,則放行,并且把當前nonce值保存到容器中,這樣,如果下次再使用同樣的nonce來請求則容器中一定存在,那么就可以判定是無效請求了。

這里可以設置緩存的失效時間為5分鐘,因為前面有效期已經(jīng)做了5分鐘的控制。

static?Cache<String,?String>?cache?=?CacheBuilder.newBuilder()
????????.expireAfterWrite(5,?TimeUnit.MINUTES)
????????.build();

private?static?void?serverVerify(String?requestParam)?throws?Exception?{
????APIRequestEntity?apiRequestEntity?=?JSONObject.parseObject(requestParam,?APIRequestEntity.class);
????Header?header?=?apiRequestEntity.getHeader();
????UserEntity?userEntity?=?JSONObject.parseObject(JSONObject.toJSONString(apiRequestEntity.getBody()),?UserEntity.class);
????
????String?sign?=?getSHA256Str(JSONObject.toJSONString(userEntity));
????if?(!sign.equals(header.getSign()))?{
????????throw?new?Exception("數(shù)據(jù)簽名錯誤!");
????}
????
????String?appId?=?header.getAppId();
????String?appSecret?=?getAppSecret(appId);
????String?nonce?=?header.getNonce();
????String?timestamp?=?header.getTimestamp();
????
????long?now?=?LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
????if?((now?-?Long.parseLong(timestamp))?/?1000?/?60?>=?5)?{
????????throw?new?Exception("請求過期!");
????}
????
????String?str?=?cache.getIfPresent(appId?+?"_"?+?nonce);
????if?(Objects.nonNull(str))?{
????????throw?new?Exception("請求失效!");
????}
????cache.put(appId?+?"_"?+?nonce,?"1");
????
????Map<String,?String>?data?=?Maps.newHashMap();
????data.put("appId",?appId);
????data.put("nonce",?nonce);
????data.put("sign",?sign);
????data.put("timestamp",?timestamp);
????Set<String>?keySet?=?data.keySet();
????String[]?keyArray?=?keySet.toArray(new?String[0]);
????Arrays.sort(keyArray);
????StringBuilder?sb?=?new?StringBuilder();
????for?(String?k?:?keyArray)?{
????????if?(data.get(k).trim().length()?>?0)?
????????????sb.append(k).append("=").append(data.get(k).trim()).append("&");
????}
????sb.append("appSecret=").append(appSecret);
????if?(!rsaVerifySignature(sb.toString(),?appKeyPair.get(appId).get("publicKey"),?header.getAppSign()))?{
????????throw?new?Exception("驗簽錯誤!");
????}
????System.out.println();
????System.out.println("【提供方】驗證通過!");
}

訪問權限

數(shù)據(jù)訪問權限,一般可根據(jù)appId的身份來獲取開放給其的相應權限,要確保每個appId只能訪問其權限范圍內的數(shù)據(jù)。

參數(shù)合法性校驗

參數(shù)的合法性校驗應該是每個接口必備的,無論是前端發(fā)起的請求,還是后端的其他調用都必須對參數(shù)做校驗,比如:參數(shù)的長度、類型、格式,必傳參數(shù)是否有傳,是否符合約定的業(yè)務規(guī)則等等。

推薦使用SpringBoot Validation來快速實現(xiàn)一些基本的參數(shù)校驗。

參考如下示例:

@Data
@ToString
public?class?DemoEntity?{
?
?
????@NotBlank(message?=?"名稱不能為空")
????private?String?name;
?
?
????@DecimalMax(value?=?"10")
????@DecimalMin(value?=?"5")
????private?BigDecimal?amount;

?
????@Email
????private?String?email;
?
?
????@Size(max?=?10,?min?=?5)
????private?String?size;
?
?
????@Min(value?=?18)
????@Max(value?=?35)
????private?int?age;
?
?
????@NotNull
????private?User?user;
?
?
????@Digits(integer?=?2,?fraction?=?4)
????private?BigDecimal?digits;
?
?
????@Future
????private?Date?future;

?
????@Past
????private?Date?past;
?
?
????@FutureOrPresent
????private?Date?futureOrPast;
?
?
?@Pattern(regexp?=?"^\\d+$")
?private?String?digit;
}


@RestController
@Slf4j
@RequestMapping("/valid")
public?class?TestValidController?{

????@RequestMapping("/demo1")
????public?String?demo12(@Validated?@RequestBody?DemoEntity?demoEntity)?{
????????try?{
????????????return?"SUCCESS";
????????}?catch?(Exception?e)?{
????????????log.error(e.getMessage(),?e);
????????????return?"FAIL";
????????}
????}
}


限流保護

在設計接口時,我們應當對接口的負載能力做出評估,尤其是開放給外部使用時,這樣當實際請求流量超過預期流量時,我們便可采取相應的預防策略,以免服務器崩潰。

一般來說限流主要是為了防止惡意刷站請求,爬蟲等非正常的業(yè)務訪問,因此一般來說采取的方式都是直接丟棄超出閾值的部分。

限流的具體實現(xiàn)有多種,單機版可以使用Guava的RateLimiter,分布式可以使用Redis,想要更加完善的成套解決方案則可以使用阿里開源的Sentinel

敏感數(shù)據(jù)訪問

敏感信息一般包含,身份證、手機號、銀行卡號、車牌號、姓名等等,應該按照脫敏規(guī)則進行處理。

白名單機制

使用白名單機制可以進一步加強接口的安全性,一旦服務與服務交互可以使用,接口提供方可以限制只有白名單內的IP才能訪問,這樣接口請求方只要把其出口IP提供出來即可。

黑名單機制

與之對應的黑名單機制,則是應用在服務端與客戶端的交互,由于客戶端IP都是不固定的,所以無法使用白名單機制,不過我們依然可以使用黑名單攔截一些已經(jīng)被識別為非法請求的IP。

5五、其他考慮

  1. 名稱和描述:API 的名稱和描述應該簡潔明了,并清晰地表明其功能和用途。

  2. 請求和響應:API 應該支持標準的 HTTP 請求方法,如 GET、POST、PUT 和 DELETE,并定義這些方法的參數(shù)和響應格式。

  3. 錯誤處理:API 應該定義各種錯誤碼,并提供有關錯誤的詳細信息。

  4. 文檔和示例:API 應該提供文檔和示例,以幫助開發(fā)人員了解如何使用該 API,并提供示例數(shù)據(jù)以進行測試。

  5. 可擴展:API應當考慮未來的升級擴展不但能夠向下兼容(一般可以在接口參數(shù)中添加接口的版本號),還能方便添加新的能力。

6六、額外補充

1. 關于MD5應用的介紹

在提到對于開放接口的安全設計時,一定少不了對于摘要算法的應用(MD5算法是其實現(xiàn)方式之一),在接口設計方面它可以幫助我們完成數(shù)據(jù)簽名的功能,也就是說用來防止請求或者返回的數(shù)據(jù)被他人篡改。

本節(jié)我們單從安全的角度出發(fā),看看到底哪些場景下的需求可以借助MD5的方式來實現(xiàn)。

密碼存儲

在一開始的時候,大多數(shù)服務端對于用戶密碼的存儲肯定都是明文的,這就導致了一旦存儲密碼的地方被發(fā)現(xiàn),無論是黑客還是服務端維護人員自己,都可以輕松的得到用戶的賬號、密碼,并且其實很多用戶的賬號、密碼在各種網(wǎng)站上都是一樣的,也就是說一旦因為有一家網(wǎng)站數(shù)據(jù)保護的不好,導致信息被泄露,那可能對于用戶來說影響的則是他的所有賬號密碼的地方都被泄露了,想想看這是多少可怕的事情。

所以,那應該要如何存儲用戶的密碼呢?最安全的做法當然就是不存儲,這聽起來很奇怪,不存儲密碼那又如何能夠校驗密碼,實際上不存儲指的是不存儲用戶直接輸入的密碼。

如果用戶直接輸入的密碼不存儲,那應該存儲什么呢?到這里,MD5就派上用場了,經(jīng)過MD5計算后的數(shù)據(jù)有這么幾個特點:

  1. 其長度是固定的。

  2. 其數(shù)據(jù)是不可逆的。

  3. 一份原始數(shù)據(jù)每次MD5后產生的數(shù)據(jù)都是一樣的。

下面我們來實驗一下

public?static?void?main(String[]?args)?{
????String?pwd?=?"123456";
????String?s?=?DigestUtils.md5Hex(pwd);
????System.out.println("第一次MD5計算:"?+?s);
????String?s1?=?DigestUtils.md5Hex(pwd);
????System.out.println("第二次MD5計算:"?+?s1);
????pwd?=?"123456789";
????String?s3?=?DigestUtils.md5Hex(pwd);
????System.out.println("原數(shù)據(jù)長度變長,經(jīng)過MD5計算后長度固定:"?+?s3);
}

第一次MD5計算:e10adc3949ba59abbe56e057f20f883e
第二次MD5計算:e10adc3949ba59abbe56e057f20f883e
原數(shù)據(jù)長度變長,經(jīng)過MD5計算后長度固定:25f9e794323b453885f5181f1b624d0b

有了這樣的特性后,我們就可以用它來存儲用戶的密碼了。

????public?static?Map<String,?String>?pwdMap?=?Maps.newConcurrentMap();

????public?static?void?main(String[]?args)?{
????????
????????register("1",?DigestUtils.md5Hex("123456"));

????????
????????System.out.println(verifyPwd("1",?DigestUtils.md5Hex("123456")));
????????
????????System.out.println(verifyPwd("1",?DigestUtils.md5Hex("1234567")));
????}

????
????public?static?boolean?verifyPwd(String?account,?String?pwd)?{
????????String?md5Pwd?=?pwdMap.get(account);
????????return?Objects.equals(md5Pwd,?pwd);
????}

????public?static?void?register(String?account,?String?pwd)?{
????????pwdMap.put(account,?pwd);
????}

MD5后就安全了嗎?

目前為止,雖然我們已經(jīng)對原始數(shù)據(jù)進行了MD5計算,并且也得到了一串唯一且不可逆的密文,但實際上還遠遠不夠,不信,我們找一個破解MD5的網(wǎng)站試一下!

我們把前面經(jīng)過MD5計算后得到的密文查詢一下試試,結果居然被查詢出來了!

openapi,microsoft,服務器,php

之所以會這樣,其實恰好就是利用了MD5的特性之一:一份原始數(shù)據(jù)每次MD5后產生的數(shù)據(jù)都是一樣的。

試想一想,雖然我們不能通過密文反解出明文來,但是我們可以直接用明文去和猜,假設有人已經(jīng)把所有可能出現(xiàn)的明文組合,都經(jīng)過MD5計算后,并且保存了起來,那當拿到密文后,只需要去記錄庫里匹配一下密文就能得到明文了,正如上圖這個網(wǎng)站的做法一樣。

對于保存這樣數(shù)據(jù)的表,還有個專門的名詞:彩虹表,也就是說只要時間足夠、空間足夠,也一定能夠破解出來。

加鹽

正因為上述情況的存在,所以出現(xiàn)了加鹽的玩法,說白了就是在原始數(shù)據(jù)中,再摻雜一些別的數(shù)據(jù),這樣就不會那么容易破解了。

String?pwd?=?"123456";
String?salt?=?"wylsalt";
String?s?=?DigestUtils.md5Hex(salt?+?pwd);
System.out.println("第一次MD5計算:"?+?s);

第一次MD5計算:b9ff58406209d6c4f97e1a0d424a59ba

你看,簡單加一點內容,破解網(wǎng)站就查詢不到了吧!

攻防都是在不斷的博弈中進行升級,很遺憾,如果僅僅做成這樣,實際上還是不夠安全,比如攻擊者自己注冊一個賬號,密碼就設置成1。

openapi,microsoft,服務器,php

String?pwd?=?"1";
String?salt?=?"wylsalt";
String?s?=?DigestUtils.md5Hex(salt?+?pwd);
System.out.println("第一次MD5計算:"?+?s);

第一次MD5計算:4e7b25db2a0e933b27257f65b117582a

雖然要付費,但是明顯已經(jīng)是匹配到結果了。

openapi,microsoft,服務器,php

所以說,無論是密碼還是鹽值,其實都要求其本身要保證有足夠的長度和復雜度,這樣才能防止像彩虹表這樣被存儲下來,如果再能定期更換一個,那就更安全了,雖說無論再復雜,理論上都可以被窮舉到,但越長的數(shù)據(jù),想要被窮舉出來的時間則也就越長,所以相對來說也就是安全的。

數(shù)字簽名

摘要算法另一個常見的應用場景就是數(shù)字簽名了,前面章節(jié)也有介紹過了

大致流程,百度百科也有介紹

openapi,microsoft,服務器,php

2. 對稱加密算法

對稱加密算法是指通過密鑰對原始數(shù)據(jù)(明文),進行特殊的處理后,使其變成密文發(fā)送出去,數(shù)據(jù)接收方收到數(shù)據(jù)后,再使用同樣的密鑰進行特殊處理后,再使其還原為原始數(shù)據(jù)(明文),對稱加密算法中密鑰只有一個,數(shù)據(jù)加密與解密方都必須事先約定好。

對稱加密算法特點

  1. 只有一個密鑰,加密和解密都使用它。

  2. 加密、解密速度快、效率高。

  3. 由于數(shù)據(jù)加密方和數(shù)據(jù)解密方使用的是同一個密鑰,因此密鑰更容易被泄露。

常用的加密算法介紹
DES

其入口參數(shù)有三個:key、data、mode。key為加密解密使用的密鑰,data為加密解密的數(shù)據(jù),mode為其工作模式。當模式為加密模式時,明文按照64位進行分組,形成明文組,key用于對數(shù)據(jù)加密,當模式為解密模式時,key用于對數(shù)據(jù)解密。實際運用中,密鑰只用到了64位中的56位,這樣才具有高的安全性。

openapi,microsoft,服務器,php

算法特點

DES算法具有極高安全性,除了用窮舉搜索法對DES算法進行攻擊外,還沒有發(fā)現(xiàn)更有效的辦法。而56位長的密鑰的窮舉空間為2^56,這意味著如果一臺計算機的速度是每一秒鐘檢測一百萬個密鑰,則它搜索完全部密鑰就需要將近2285年的時間,可見,這是難以實現(xiàn)的。然而,這并不等于說DES是不可破解的。而實際上,隨著硬件技術和Internet的發(fā)展,其破解的可能性越來越大,而且,所需要的時間越來越少。使用經(jīng)過特殊設計的硬件并行處理要幾個小時。

為了克服DES密鑰空間小的缺陷,人們又提出了3DES的變形方式。

3DES

3DES相當于對每個數(shù)據(jù)塊進行三次DES加密算法,雖然解決了DES不夠安全的問題,但效率上也相對慢了許多。

AES

AES用來替代原先的DES算法,是當前對稱加密中最流行的算法之一。

ECB模式

AES加密算法中一個重要的機制就是分組加密,而ECB模式就是最簡單的一種分組加密模式,比如按照每128位數(shù)據(jù)塊大小將數(shù)據(jù)分成若干塊,之后再對每一塊數(shù)據(jù)使用相同的密鑰進行加密,最終生成若干塊加密后的數(shù)據(jù),這種算法由于每個數(shù)據(jù)塊可以進行獨立的加密、解密,因此可以進行并行計算,效率很高,但也因如此,則會很容易被猜測到密文的規(guī)律。

openapi,microsoft,服務器,php

private?static?final?String?AES_ALG?=?"AES";
private?static?final?String?AES_ECB_PCK_ALG?=?"AES/ECB/NoPadding";

public?static?void?main(String[]?args)?throws?Exception?{
????System.out.println("第一次加密:"?+?encryptWithECB("1234567812345678",?"50AHsYx7H3OHVMdF123456",?"UTF-8"));
?System.out.println("第二次加密:"?+?encryptWithECB("12345678123456781234567812345678",?"50AHsYx7H3OHVMdF123456",?"UTF-8"));
}

public?static?String?encryptWithECB(String?content,?String?aesKey,?String?charset)?throws?Exception?{
????Cipher?cipher?=?Cipher.getInstance(AES_ECB_PCK_ALG);
????cipher.init(Cipher.ENCRYPT_MODE,
????????????new?SecretKeySpec(Base64.decodeBase64(aesKey.getBytes()),?AES_ALG));
????byte[]?encryptBytes?=?cipher.doFinal(content.getBytes(charset));
????return?Hex.encodeHexString(encryptBytes);
}

第一次加密:87d2d15dbcb5747ed16cfe4c029e137c
第二次加密:87d2d15dbcb5747ed16cfe4c029e137c87d2d15dbcb5747ed16cfe4c029e137c

可以看出,加密后的密文明顯也是重復的,因此針對這一特性可進行分組重放攻擊。

CBC模式

CBC模式引入了初始化向量的概念(IV),第一組分組會使用向量值與第一塊明文進行異或運算,之后得到的結果既是密文塊,也是與第二塊明文進行異或的對象,以此類推,最終解決了ECB模式的安全問題。

openapi,microsoft,服務器,php

CBC模式的特點

CBC模式安全性比ECB模式要高,但由于每一塊數(shù)據(jù)之間有依賴性,所以無法進行并行計算,效率沒有ECB模式高。文章來源地址http://www.zghlxwxcb.cn/news/detail-775482.html

到了這里,關于如何為開放平臺設計一個安全好用的OpenApi的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

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

領支付寶紅包贊助服務器費用

相關文章

  • 如何設計安全可靠的開放接口---之簽名(sign)

    如何設計安全可靠的開放接口---之簽名(sign)

    1. 如何設計安全可靠的開放接口—之Token 2. 如何設計安全可靠的開放接口—之AppId、AppSecret 3. 如何設計安全可靠的開放接口—之簽名(sign) 4. 如何設計安全可靠的開放接口【番外篇】—關于MD5應用的介紹 5. 如何設計安全可靠的開放接口—還有哪些安全保護措施 6. 如何設計安全

    2024年02月10日
    瀏覽(27)
  • 開放平臺實現(xiàn)安全的身份認證與授權原理與實戰(zhàn):整理OAuth2.0各種開發(fā)指南

    OAuth 2.0 是一種基于標準 HTTP 的身份驗證和授權機制,它允許用戶授予第三方應用程序訪問他們在其他服務(如社交網(wǎng)絡、電子郵件服務器或云存儲服務)的數(shù)據(jù)。OAuth 2.0 的目標是提供一種簡化的方法,使得用戶可以安全地授予第三方應用程序訪問他們的數(shù)據(jù),而無需將他們的密

    2024年04月27日
    瀏覽(21)
  • C# 如何設計一個好用的日志庫?【架構篇】

    C# 如何設計一個好用的日志庫?【架構篇】

    相信你在實際工作期間經(jīng)常遇到或聽到這樣的說法: ??“我現(xiàn)在加一下日志,等會兒你再操作下。” ??“只有在程序出問題以后才會知道打一個好的日志有多么重要?!?可見日志的記錄是日常開發(fā)的必備技能。 記錄日志的必要性: ??當業(yè)務比較復雜時,在關鍵代碼

    2023年04月17日
    瀏覽(20)
  • 您距離一個成熟安全的 DevOps 平臺,只差一個遷移

    您距離一個成熟安全的 DevOps 平臺,只差一個遷移

    目錄 功能豐富,開箱即用 安全保障,質效并行 私有部署,自主可控 月度發(fā)版,持續(xù)迭代 本土化團隊,企業(yè)級支持 遷移指南 從 Gitee 遷移到極狐GitLab 從 SVN 遷移到極狐GitLab 從 GitHub 遷移到極狐GitLab 歷經(jīng) 14 年的發(fā)展后,DevOps 已經(jīng)不再是一個鮮為人知的術語,國內外眾多企業(yè)

    2024年02月03日
    瀏覽(17)
  • 如何為數(shù)據(jù)保護加上“安全鎖”?

    如何為數(shù)據(jù)保護加上“安全鎖”?

    伴隨著數(shù)字經(jīng)濟的日趨活躍,數(shù)據(jù)安全和隱私保護成為了各國政府和企業(yè)都十分重視的問題,紛紛加強了數(shù)據(jù)安全防護。但實際上,近幾年數(shù)據(jù)泄露問題接連不斷,雖然沒有造成嚴重的后果,但也足以證明目前數(shù)據(jù)安全防護的緊迫性。 2019年7月12日,美國媒體報道,F(xiàn)acebook將就

    2024年01月21日
    瀏覽(16)
  • idea如何為一個項目配置多個遠程 Git 倉庫

    idea如何為一個項目配置多個遠程 Git 倉庫

    有時候自己從開源項目中垃出來的項目需要同步推送到 github 和 gitlab 兩個倉庫地址,那么如何實現(xiàn)呢 添加多個遠程倉庫地址 然后在這里添加多個遠程倉庫地址 在提交代碼的地方想提交哪個遠程倉庫自己去選擇

    2024年02月12日
    瀏覽(20)
  • 全新加密敘事,以Solmash為代表的 LaunchPad 平臺如何為用戶賦能?

    全新加密敘事,以Solmash為代表的 LaunchPad 平臺如何為用戶賦能?

    銘文市場的火爆帶來“Fair Launch”這種全新的代幣啟動方式,F(xiàn)air Launch的特點在于其為所有人參與Launch帶來了公平的機會,所有鏈上玩家們都需要通過先到先得的方式Mint資產,VC在Fair Launch中幾乎沒有話語權,不同的投資者在Fair Launch中都被視為同一個個體。而在Fair Launch的推動

    2024年01月21日
    瀏覽(14)
  • 低代碼開發(fā)的一些見解:何為低代碼、優(yōu)缺點、如何入門及平臺介紹

    低代碼是一種軟件開發(fā)方法,它旨在通過最大程度地減少手動編碼來加快應用程序的開發(fā)速度和降低技能門檻。低代碼開發(fā)平臺提供了一系列工具和組件,使開發(fā)人員能夠使用圖形化界面、拖放式操作等方式來快速構建應用程序,而無需深入的編程知識。 低代碼開發(fā)平臺通常

    2024年02月04日
    瀏覽(24)
  • 如何為WPF應用程序制作一個虛擬鍵盤?這里有答案(Part 1)

    如何為WPF應用程序制作一個虛擬鍵盤?這里有答案(Part 1)

    Telerik UI for WPF擁有超過100個控件來創(chuàng)建美觀、高性能的桌面應用程序,同時還能快速構建企業(yè)級辦公WPF應用程序。UI for WPF支持MVVM、觸摸等,創(chuàng)建的應用程序可靠且結構良好,非常容易維護,其直觀的API將無縫地集成Visual Studio工具箱中。 點擊獲取Telerik UI for WPF最新版下載 T

    2024年02月09日
    瀏覽(23)
  • (附源碼)springboot網(wǎng)絡安全平臺設計  畢業(yè)設計042335

    (附源碼)springboot網(wǎng)絡安全平臺設計 畢業(yè)設計042335

    Springboot網(wǎng)絡安全考核平臺設計 摘要 隨著互聯(lián)網(wǎng)趨勢的到來,各行各業(yè)都在考慮利用互聯(lián)網(wǎng)將自己推廣出去,最好方式就是建立自己的互聯(lián)網(wǎng)系統(tǒng),并對其進行維護和管理。在現(xiàn)實運用中,應用軟件的工作規(guī)則和開發(fā)步驟,采用Java技術建設網(wǎng)絡安全考核平臺設計。 本設計主

    2024年02月06日
    瀏覽(19)

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

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

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

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

二維碼1

領取紅包

二維碼2

領紅包