【Cfeng Work】 Open API的intro和 梳理

news2024/12/26 23:37:42

OpenAPI 开放平台

内容管理

    • Open API intro
    • 腾讯云OpenAPI
      • OpenAPI请求
      • API密钥管理
      • 云API签名过程
        • 拼接规范请求串 CanonicalRequest
        • 拼接待签名字符串
        • 计算签名
        • 拼接到Authorization中
      • 云API签名失败规范
      • Token 和 长期密钥
      • 公共参数
    • OpenApi设计
      • AppId、AppSecret
      • sign签名
      • timestamp 时间戳保证有效期
      • nonce保证一次性,避免重放
      • 黑白名单
      • 限流、熔断、降级
      • 合法性校验 (参数)


对于OpenAPI开放平台的intro和 理解


之前对于OpenAPI的理解只是局限在调用Google的相关接口返回数据… 实际上OpenAPI的设计需要思考很多

Cfeng 最近接触到OpenAPI的内容,需要规范化设计OpenAPI,like 抖音开放平台、腾讯云的OpenAPI,这里分享一下自我见解

Open API intro

Open API 就是开放API,OpenAPI是服务型web项目的一种应用,网站(应用)服务商将自己网站的服务封装为一系列API 开放Open出去, 供第三方开发者使用, OpenAPI 也就是开放网站部分服务API

提供OpenAPI的平台(网站等)就是开放平台

开放平台: 第一种为技术型开放,比如baidu等将需求开放给第三方开发者, 第二种是软件系统(应用)公开 API或者 函数function,使外部程序可以 使用软件系统的资源,增加功能,而不需要更改该软件系统的代码

简单理解, 就是当前软件系统公开的API接口,第三方的开放者可以调用该接口获取当前软件系统的资源

这里就涉及到了鉴权、认证等具体操作流程

腾讯云OpenAPI

Cfeng注册了各个开放平台的账户,查看OpenAPI的设计, 这里就以腾讯云为例,各个平台的设计思路都是差不多的

OpenAPI请求

请求调用: 服务地址, 通信协议, 请求方法, 字符编码

调用腾讯云服务的OpenAPI需要提供服务地址,也就是开放OpenAPI的服务器地址,eg :cvm.tencentcloudapi.com;

通信协议 当然采用安全的HTTPS,高安全性

请求方法: 比如GET和POST【application/json; …】

字符编码: 采用UTF-8

API密钥管理

使用API密钥签名API请求, 腾讯云收到请求,比对签名串,验证通过才会给与资源【腾讯云控制台SecretKey无法直接查看,只能发送短信短暂查看】,密钥就和token一样,如果泄露别人会获得相同的权限造成损失;

给出的密钥保护方式: 将密钥隐藏在环境变量中, 存放在安全位置…

当需要发起请求时,通过变量方式调用,塞入请求头进行签名…

import os

from tencentcloud.common import credential
from tencentcloud.cvm.v20170312 import cvm_client, models

try:
    cred = credential.Credential(
        os.environ.get("TENCENTCLOUD_SECRET_ID"),
        os.environ.get("TENCENTCLOUD_SECRET_KEY"))
    client = cvm_client.CvmClient(cred, "ap-guangzhou")
    req = models.DescribeInstancesRequest()
    resp = client.DescribeInstances(req)
    print(resp)
except Exception as err:
    print(err)
    
# 像这里使用python, 这里也是包装过的了,像cred和client直接就会塞入请求头, req指定一个Action, 就可以发起请求获取资源

按照解释,API密钥代表的就是账号身份以及所拥有的权限,使用API 就可以直接操作用户名下的所有的资源【普通用户可能是图形界面的方式,开放者就可以直接调用API获取自己拥有的资源进行操作】

密钥可以直接生成,包含SecretId和SecretKey, 密钥是构建API的重要凭证, 用于调用时生成签名 【密钥可以手动禁用, 并且可以在控制台查看密钥最近使用的时间决定禁用与否】

腾讯云的API会对每一个调用请求进行身份验证,用户需要使用安全凭证,经过特定的步骤对请求进行签名Signature, 需要在请求的公共参数中指定签名结果并以指定的方式发送 request

签名Signature的目的就是验证请求者的身份(确保request是持有密钥的人发送的,防止劫持),以及保护传输数据,【加密】,发送方使用签名算法生成一个Hash值 连同请求一起发送, 服务器收到后以同样的过程计算,并验证hash值,如果被篡改,那么API拒绝此请求

腾讯云中使用的安全凭证就是密钥, 包括SecretId和SecretKey, 每个用户最多两对密钥

SecretId: 标识API调用者身份, 就像用户名

SecretKey: 验证API调用者身份,like 密码

用户需要严格保管好安全凭证,避免泄露

密钥管理中控制台可见的字段为: APPID(生成的两个密钥对的APPID相同)、 密钥(ID和Key)、创建时间,最近访问时间,状态(2: 有效, 3: 禁用 ,4: 已经删除)、操作

云API签名过程

按照OpenAPI业务接口文档,第三方用户可以直接直接访问相关的接口获取资源, 首先访问的就是服务提供方的服务器, 接口也就是服务器上Open的接口

比如tecent云的访问实例:

访问者访问需要提供公共参数和接口参数,访问者的SecretId 为 AKID…, SecretKey为GU5…

curl -X  POST https://cvm.tencentcloudapi.com \
-H "Authorization: TC3-HMAC-SHA256 Credential=AKID..../2023-01-01/cvm/tc3_request, SignedHeaders=content-type;host, Signature=22398u89hufahkgjaglkahhgaj
-H "ContentType: application/json; charset=utf-8"
-H "X-TC-Action: DescribeInstances" \
-H "X-TC-Timestamp: 1551113065" \
-H "X-TC-Version: 2017-03-12" \
-H "X-TC-Region: ap-guangzhou" \
-d '{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}'

按照这个请求,可以看到最重要的就是Authorization, 其中的Credential代表的就是SecretID,标识身份,后面加上请求的Date/service/云固定结尾tc3_request, 后面再跟上signheaders代表参与签名的头,就可以计算签名;

后面的signature就是签名串, 服务端按照同样的过程签名和该signature对比验证是否篡改

签名的过程为:

拼接规范请求串 CanonicalRequest

CanonicalRequest =
    HTTPRequestMethod + '\n' +   	【请求方法】
    CanonicalURI + '\n' +  			【URI参数 /】
    CanonicalQueryString + '\n' +	 【查询字符串,post为空,get为?后部分】
    CanonicalHeaders + '\n' +		【参与签名的头部信息,按照key:value\n】
    SignedHeaders + '\n' + 【参与签名的头部, 就是key1;key2】
    HashedRequestPayload  【请求正文body】比如对正文SHA256哈希,得到signature,这里假设为35e...,Get请求为空】

得到规范的请求串

POST
/

content-type:application/json; charset=utf-8
host:cvm.tencentcloudapi.com

content-type;host
35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6e89887f1398934f064

拼接待签名字符串

签名串格式

StringToSign =
    Algorithm + \n +    【签名算法: SHA256]
    RequestTimestamp + \n +	 	【请求时间戳】
    CredentialScope + \n + 		【凭证范围: Data/service/tc3_REQUEST,   eg: /2023-01-01/cvm/tc3_request,cvm就是具体的产品】
    HashedCanonicalRequest   请求串【规范请求串再次SHA256得到的签名串】

按照该格式,就可以得出一个实例的签名字符串

TC3-HMAC-SHA256
1551113065
2019-02-25/cvm/tc3_request
5ffe6a04c0664d6b969fab9a13bdab201d63ee709638e2749d62a09ca18d7031

计算签名

  • 签名密钥【计算得出】
SecretKey = "Gu5t9xGARNpq86cd98joQYCN3*******"
SecretDate = HMAC_SHA256("TC3" + SecretKey, Date)
SecretService = HMAC_SHA256(SecretDate, Service)
SecretSigning = HMAC_SHA256(SecretService, "tc3_request")

SecretKey就是用户自己持有的密钥Key,Date为访问的Date信息,Service就是请求的产品服务,比如cvm

这里的计算密钥过程,使用初始密钥Key 加入Date签名,再加入请求的服务再次签名,再将固定的结尾串tc3_request加入签名 得到一个 派生密钥

  • 计算签名

将上面的派生密钥加入到待签名字符串中进行签名

Signature = HexEncode(HMAC_SHA256(SecretSigning, StringToSign))

拼接到Authorization中

Authorization就是用户访问OpenAPI最关键的信息, 服务端不仅需要验证请求者身份和其对应的权限,还需要防止请求篡改

Authorization =
    Algorithm + ' ' +
    'Credential=' + SecretId + '/' + CredentialScope + ', ' +
    'SignedHeaders=' + SignedHeaders + ', ' +
    'Signature=' + Signature

Algorithm就是整个签名过程的签名算法,Credential 中包含的就是SecretId和CredentialScope 【date/service/ninetech_request】signedHeaders参与签名的头部, Signature就是签名的结果

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class TencentCloudAPITC3Demo {
    private final static Charset UTF8 = StandardCharsets.UTF_8;
    private final static String SECRET_ID = "AKIDz8krbsJ5yKBZQpn74WFkmLPx3*******";
    private final static String SECRET_KEY = "Gu5t9xGARNpq86cd98joQYCN3*******";
    private final static String CT_JSON = "application/json; charset=utf-8";

    public static byte[] hmac256(byte[] key, String msg) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        mac.init(secretKeySpec);
        return mac.doFinal(msg.getBytes(UTF8));
    }

    public static String sha256Hex(String s) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] d = md.digest(s.getBytes(UTF8));
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    public static void main(String[] args) throws Exception {
        String service = "cvm";
        String host = "cvm.tencentcloudapi.com";
        String region = "ap-guangzhou";
        String action = "DescribeInstances";
        String version = "2017-03-12";
        String algorithm = "TC3-HMAC-SHA256";
        String timestamp = "1551113065";
        //String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 注意时区,否则容易出错
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));

        // ************* 步骤 1:拼接规范请求串 *************
        String httpRequestMethod = "POST";
        String canonicalUri = "/";
        String canonicalQueryString = "";
        String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + "host:" + host + "\n";
        String signedHeaders = "content-type;host";

        String payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";
        String hashedRequestPayload = sha256Hex(payload);
        String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
                + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
        System.out.println(canonicalRequest);

        // ************* 步骤 2:拼接待签名字符串 *************
        String credentialScope = date + "/" + service + "/" + "tc3_request";
        String hashedCanonicalRequest = sha256Hex(canonicalRequest);
        String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
        System.out.println(stringToSign);

        // ************* 步骤 3:计算签名 *************
        byte[] secretDate = hmac256(("TC3" + SECRET_KEY).getBytes(UTF8), date);
        byte[] secretService = hmac256(secretDate, service);
        byte[] secretSigning = hmac256(secretService, "tc3_request");
        String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
        System.out.println(signature);

        // ************* 步骤 4:拼接 Authorization *************
        String authorization = algorithm + " " + "Credential=" + SECRET_ID + "/" + credentialScope + ", "
                + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
        System.out.println(authorization);

        TreeMap<String, String> headers = new TreeMap<String, String>();
        headers.put("Authorization", authorization);
        headers.put("Content-Type", CT_JSON);
        headers.put("Host", host);
        headers.put("X-TC-Action", action);
        headers.put("X-TC-Timestamp", timestamp);
        headers.put("X-TC-Version", version);
        headers.put("X-TC-Region", region);

        StringBuilder sb = new StringBuilder();
        sb.append("curl -X POST https://").append(host)
        .append(" -H \"Authorization: ").append(authorization).append("\"")
        .append(" -H \"Content-Type: application/json; charset=utf-8\"")
        .append(" -H \"Host: ").append(host).append("\"")
        .append(" -H \"X-TC-Action: ").append(action).append("\"")
        .append(" -H \"X-TC-Timestamp: ").append(timestamp).append("\"")
        .append(" -H \"X-TC-Version: ").append(version).append("\"")
        .append(" -H \"X-TC-Region: ").append(region).append("\"")
        .append(" -d '").append(payload).append("'");
        System.out.println(sb.toString());
    }
}

云API签名失败规范

签名是可能失败的,统一定义错误码类AuthFailure

SignatureExpire : 签名过期,timestamp 和接收到请求时间超过5min

SecretIdNotFound: 密钥不存在, 可能是密钥书写错误或者被禁用

SignatureFailure: 签名错误, 可能是SecretKey错误,就是内容不符合

TokenFailure: 临时证书Token错误

InvalidSecretId: 密钥非法, 类型错误...

Token 和 长期密钥

按照腾讯云的intro, Token是安全凭证服务申请的临时的凭证,而密钥是长期有效的

而凭证服务也是直接调用系统的API达到,such as:

  • 申请扮演角色: AssumeRole 【公共参数Action、Version、Region】,接口私有参数【RoleArn: 角色资源描述, 比如qcs::cam::uin/1234:roleName/testRoleName 、 RoleSessionName: 临时会话名称 、 DurationSeconds: 临时证书有效期,default 7200s 、 Policy 、 ExternId、 Tags.N 、 SourceIdentity】, 输出参数就包括Credentials 临时安全证书(Token: String、 TmpSecretId 临时证书密钥ID、 TmpSecretKey: key)、 ExpiredTime(时间戳过期时间)、Expiration(无效时间iso格式)、 RequestId(唯一请求ID,每次请求都有)
  • 申请OIDC角色临时密钥: AssumeRoleWithWebIdentity 【三个公共参数、ProviderId: 身份提供商名称、 webIdentityToken: IdP签发OIDC令牌、 RoleArn: 角色描述名称、 RoleSessionName: 会话名称、 DurationSeconds: 有效期】, 输出参数和上方一致
  • 获取当前调用者身份信息 : GetCallerIdentity : 支持主账号长期密钥、以及AssumeRole等临时密钥身份获取, 输入参数就3个公共参数; 输出参数包括 【Arn 、 AccountId: 主账号uin、 UserId: 身份标识、 PrincipalId: 密钥所属账号uin、 Type: 身份类型、 RequestID】
  • 获取联合身份临时凭证: GetFederationToken 【三个公共参数, Name: 调用方名称自定义、 Policy( 授予临时证书的CAM策略)、 DurationSeconds】; 输出参数就是👆,临时的一个证书和其失效时间

公共参数

公共参数是标识用户和接口签名的参数, 每一个接口访问都需要携带才能正常发起请求

公共参数需要统一放到HTTP Header请求头中

  • Action String X-TC-Action: 操作的接口名称,比如查询服务器DescribeInstances
  • Timestamp: Integer X-TC-Timestamp: 当前时间戳,如果与当前时间超过5min则过期
  • Version : String X-TC-Version 操作的API版本,比如2023-01
  • Authorization: String 标准身份认证字段,包含内容详见上面👆,比如签名算法、签名凭证、参与签名的头部、签名生成的摘要 【使用密钥Secret参与操作】
  • Token: String X-TC-Token, 安全凭证服务办法的临时安全凭证中的Token【注意是临时】,使用的时候也就是将SecretKey和ID替换为TmpSecretId和Key, 使用长期密钥Secret时不设置该Token字段
  • Language: String(可选),X-TC-Language,接口返回语言,比如zh-CN、en-US
  • Region : String (可选) 地域

按照腾讯云的示例,开发者用户查询云服务器示例列表前10个,偏移量Offset=0,Limit = 10

https://cvm.tencentcloudapi.com/?Limit=10&Offset=0

Authorization: TC3-HMAC-SHA256 Credential=AKID********EXAMPLE/2018-10-09/cvm/tc3_request, SignedHeaders=content-type;host, Signature=5da7a33f6993f0614b047e5df4582db9e9bf4672ba50567dba16c6ccf174c474
Content-Type: application/x-www-form-urlencoded
Host: cvm.tencentcloudapi.com
X-TC-Action: DescribeInstances
X-TC-Version: 2017-03-12
X-TC-Timestamp: 1539084154
X-TC-Region: ap-guangzhou

请求头中最主要的就是Authorization, 之后就是Content-type、host和公共参数(Action、version、timestamp、region)

除了公共参数,剩下的就是具体访问接口的具体的参数,类似上面的申请临时证书服务

OpenApi设计

Cfeng结合导师的讲解和腾讯云资料查看,对于OpenAPI的思考暂时如下:

首先需要的是统一、规范的接口标准, 快速接入,统一对外的接口, 最需要保证的就是接口安全性,利用密钥签名实现,同时不只是针对RPA项目,所以需要考虑复用性, 减少对于其他内容的调用,低耦合

AppId、AppSecret

或者是SecretId和SecretKey,id标识用户,key为密码, 接口请求会根据规则生成签名, 服务器会验证签名

ID的生成只需要保证全局唯一即可,对应用户; AppSeret生成也简单,只要和APPID能够关联即可

sign签名

非对称加密算法:例如RSA 私钥加密,公钥解密; 结合摘要算法,摘要后,私钥加密,公钥解密,可以用在签名中

img

摘要算法:例如MD5 不需要密钥不可逆,不能根据密文反推明文

签名: 数据防止篡改 , 防止身份冒充

  • 数据防止篡改: signature数据和计算数据相同,说明没有篡改,开放资源
  • 身份防冒充: 比如使用SHA256withRSA, 将数据先进行SHA256计算, 在使用RSA私钥加密,对方解密也是一样,先用RSA公钥解密,再计算

这里按照腾讯云的思路,用户可以申请密钥对,申请成功就拥有SecretId和SecretKey

公私钥 接口提供方生成, 把私钥交给请求方

img

这里参照腾讯云,同时参照的给出一个模拟代码,就是验证的过程

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 {
        // 模拟生成appId、appSecret
        String appId = initAppInfo();

        // 根据appId生成公私钥对
        initKeyPair(appId);

        // 模拟请求方
        String requestParam = clientCall();

        // 模拟提供方验证
        serverVerify(requestParam);

    }

    private static String initAppInfo() {
        // appId、appSecret生成规则,依据之前介绍过的方式,保证全局唯一即可
        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("数据签名错误!");
        }

        // 从header中获取相关信息,其中appSecret需要自己根据传过来的appId来获取
        String appId = header.getAppId();
        String appSecret = getAppSecret(appId);
        String nonce = header.getNonce();
        String timestamp = header.getTimestamp();

        // 按照同样的方式生成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);


        if (!rsaVerifySignature(sb.toString(), appKeyPair.get(appId).get("publicKey"), header.getAppSign())) {
            throw new Exception("公钥验签错误!");
        }

        System.out.println();
        System.out.println("【提供方】验证通过!");

    }

    public static String clientCall() {
        // 假设接口请求方与接口提供方,已经通过其他渠道,确认了双方交互的appId、appSecret
        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");

        // 使用sha256的方式生成签名
        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("【请求方】拼接后的参数:" + sb.toString());
        System.out.println();

        // 使用sha256withRSA的方式对header中的内容加签
        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("【请求方】接口请求参数: " + 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("签名计算出现异常", 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);
    }

}

使用算法就是SHA256

timestamp 时间戳保证有效期

时间戳是一个重要手段,可以防止同一个请求参数被无限使用,比如验证5分钟有效期

long now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    if ((now - Long.parseLong(timestamp)) / 1000 / 60 >= 5) {
        throw new Exception("请求过期!");
    }

nonce保证一次性,避免重放

接口请求方生成随机数,实现接口一次性有效,避免重放攻击

接口请求方每次请求都会随机生成一个不重复的nonce值,接口提供方可以使用存储容器来存储该nonce, 请求到来查看缓存中是否包含【可以放redis,有效期5min即可, 先校验是否过期,再校验是否重放】

String str = cache.getIfPresent(appId + "_" + nonce);
    if (Objects.nonNull(str)) {
        throw new Exception("请求失效!");
    }

黑白名单

这个针对的就是具体的运维过程了,可以对于某些IP进行黑白名单限制,降低风险

限流、熔断、降级

OpenAPI需要充分考虑限流、熔断、降级问题, 需要约定清楚,保证系统的功能,特别就是限流的大小

合法性校验 (参数)

像腾讯云一样,公共参数、接口参数,哪些参数必填,必填的业务参数、业务参数校验保证合法性

综合来看,OpenAPI起始就是以接口的方式公开系统资源, 需要注意的就是安全性 和 设计的复用性

当前cfeng考虑的就是参照腾讯的设计来设计OpenAPI,使用SHA256, 需要做密钥管理和一个临时的证书管理,目前需要考虑的问题: 发放长期密钥时,如何保证安全性? 长期密钥和token不同时使用,如何进一步保证密钥安全性?  密钥 和token 和refreshtoken的关系?
其实最核心的问题就是: 我给用户发放密钥时(通过HTTP),怎么保证不被其他人盗取

接下来就是开放平台的规范化设计了🌳

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/150345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

制造服务行业需要项目管理软件吗?

伴随着时代的快速发展&#xff0c;电子制造服务行业也正在飞速发展&#xff0c;在日新月异得时代步伐下&#xff0c;科学得管理方法和现代化得管理工具相结合&#xff0c;保证企业得进步与发展。项目管理工具通过构造高效统一的项目管理平台&#xff0c;优化企业管理中存在的问…

win10 conda安装labme安装和使用

1、安装conda 在WIN10中配置conda 1.1miniconda下载 https://docs.conda.io/en/latest/miniconda.html 可以任意选择对应的版本&#xff0c;安装时选择配置路径&#xff0c;以免后期重复配置环境。 1.2.2 在环境变量中添加路径 Win R&#xff0c;打开运行&#xff0c;输入…

在ubuntu系统上用pyinstaller加密打包yolov5项目代码的详细步骤

目录0. 背景1. 创建虚拟环境2. pyinstaller打包2.1. 生成并修改spec文件2.2. 重新生成二进制文件3. 测试4. 加密打包4.1. 创建入口函数main.py4.2. 修改detect.py4.3. 加密生成main.spec文件4.4. 修改main.spec文件4.5. 生成二进制文件4.6. 测试0. 背景 最近需要在ubuntu 18.0…

Payso×OceanBase:云上拓新,开启云数据库的智能托管

日前&#xff0c;聚合支付厂商 Payso&#xff08; 独角鲨北京科技有限公司&#xff09;的平台、交易系统引入 OceanBase 原生分布式数据库&#xff0c;实现显著降本增效—— 硬件成本降低 20&#xff5e;30%&#xff0c;查询效率提升 80%&#xff0c;执行效率提升了 30%&#x…

学习IB生物,我们需要知道什么知识点?

学习IB课程的很多同学应该都听说过一个说法&#xff1a;IB生物算是理科中的文科&#xff0c;没有公式推导&#xff0c;只有大量需要记忆的内容&#xff0c;不需要用学习理科的思维去学习&#xff0c;其实这种观点是有误区的。实际上&#xff0c;学习生物这门课将会面对的是一个…

01背包问题详解

目录 1.1二维dp数组 1.2一维dp数组改进 1.3相关例题 1.3.1分割等和子集 1.3.2一和零 1.1二维dp数组 概述&#xff1a;背包的最大重量是固定的&#xff0c;物品的数量&#xff0c;重量也是固定的&#xff0c;并且物品只能放一次&#xff0c;可以选择放或者不放&#xff0c…

Redis 核心原理串讲(中),架构演进之高可用

文章目录Redis 核心原理总览&#xff08;全局篇&#xff09;前言一、持久化1、RDB2、AOF3、AOF 重写4、混合持久化5、对比二、副本1、同步模式2、部分重同步三、哨兵1、核心能力2、节点通信总结Redis 核心原理总览&#xff08;全局篇&#xff09; 正文开始之前&#xff0c;我们…

【FPGA】Verilog:基本实验步骤演示 | 功能电路创建 | 添加仿真激励 | 观察记录仿真波形

前言&#xff1a; 本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载的完整过程、Verilog语言基本运用&#xff0c;电路设计和Test Bench程序的编写、以及实验开发板的使用&#xff0c;通过观察和数据记录理解仿真和FGPA实现的差异。 目录 Ⅰ. 基础知…

考研政治 马原 易混淆知识点

马哲 1. 哲学基本问题 从何者为第一性&#xff0c;分为唯物主义和唯心主义 从是否具有同一性&#xff0c;分为可知论&#xff08;有同一性&#xff09;和不可知论&#xff08;无同一性&#xff09; 辩证法&#xff1a;联系&#xff0c;发展的观点看世界&#xff0c;认为发展的…

python对接API二次开发高级实战案例解析:百度地图Web服务API封装函数(行政区划区域检索、地理编码、国内天气查询、IP定位、坐标转换)

文章目录前言一、IP定位1.请求URL2.获取IP定位封装函数3.输出结果二、国内天气查询1.请求url2.天气查询封装函数3.输出结果三、行政区划区域检索1.请求url2.区域检索封装函数3.输出结果四、地理编码1.请求url2.地理编码封装函数3.输出结果五、坐标转换1.请求url2.坐标转换封装函…

一文细说Linux虚拟文件系统原理

在 Unix 的世界里&#xff0c;有句很经典的话&#xff1a;一切对象皆是文件。这句话的意思是说&#xff0c;可以将 Unix 操作系统中所有的对象都当成文件&#xff0c;然后使用操作文件的接口来操作它们。Linux 作为一个类 Unix 操作系统&#xff0c;也努力实现这个目标。 虚拟…

CSS 这个就叫优雅 | 多行文本溢出省略

CSS 这个就叫优雅 | 多行文本溢出省略 文章目录CSS 这个就叫优雅 | 多行文本溢出省略一、文本溢出省略方式二、WebKit内核浏览器解决方法&#x1f959;三、通用解决方法四、CSS 预处理器封装&#x1f969;五、参考资料&#x1f498;六、推荐博文&#x1f357;一、文本溢出省略方…

小样本学习(Few-Shot Learning)训练参数意义

一、常规参数 1.1 epoch 是指所有的训练数据都要跑一遍。假设有6400个样本&#xff0c;在训练过程中&#xff0c;这6400个样本都跑完了才算一个epoch。一般实验需要训练很多个epoch&#xff0c;直到LOSS稳定后才停止。 1.2 batch_size 中文名称是批大小&#xff0c;之前的640…

【数据结构趣味多】二叉树概念及性质

1.树的定义 定义&#xff1a;树&#xff08;Tree&#xff09;是n&#xff08;n>0&#xff09;个结点的有限集。n0时称为空树。在任意一棵非空树种&#xff1b; 有且仅有一个根结点&#xff08;root&#xff09;。当n>1时&#xff0c;其余结点可分为m&#xff08;m>0&a…

H13-531云计算HCIE V2.0——400~600常错题和知识点总结

400~600 422、在 FusionCloud 6.x 中&#xff0c;以下关于备份的说法哪项是错误的&#xff1f; A&#xff0e;备份协议支持本地&#xff0c;通过 FTP/SFTP 到第三方服务器及 OBS B. 为了保证系统稳定运行&#xff0c;对管理数据进行备份恢复可以确保在异常时对业务的影响降到…

没有完美的项目,也轮不到你,找到适合自己的,先干起来再说

首先明确一点&#xff0c;没有百分百完美的项目&#xff0c;即使有&#xff0c;也轮不到你。不要认为你必须先找到一个完美的项目&#xff0c;然后再去工作。这个想法最后的结局就是项目一直在找&#xff0c;观望&#xff0c;迟迟不行动&#xff0c;不赚钱。如果你真的想找个项…

C++ 语法基础课 习题7 —— 类、结构体、指针、引用

文章目录例题1. 21.斐波那契数列2. 16.替换空格3. 84.123...n4. 28.O(1)时间删除链表结点5. 36.合并两个排序的链表例题 1. 21.斐波那契数列 Acwing 21.斐波那契数列 class Solution { public:int Fibonacci(int n) {if(n < 1) return n;return Fibonacci(n - 1) Fibon…

并发编程 - ThreadLocal

前言 ThreadLocal 用于解决多线程对于共享变量的访问带来的安全性问题。ThreadLocal 存储线程局部变量。每个线程内置 ThreadLocalMap&#xff0c;ThreadLocalMap 的 key 存储 ThreadLocal 实例&#xff0c;value 存储自定义的值。与同步机制相比&#xff0c;它是一种“空间换…

vue性能优化之预渲染prerender-spa-plugin+vue-meta-info解决seo问题

单页面应用中&#xff0c;web项目只有一个页面&#xff0c;前端根据路由不同进行组件之间的对应切换&#xff0c;动态的渲染页面内容。这就是客户端渲染&#xff0c;具有减少服务器端压力、响应速度快等优点。但是单页应用在优化用户体验的同时&#xff0c;也给我们带来了一些对…

阅读 | 001《人工智能导论》(三)知识应用篇1

文章目录知识应用第9章、专家系统9.1 专家系统概述9.2 推理方法9.3 一个简单的专家系统9.4 非确定性推理9.5 专家系统工具9.6 专家系统的应用9.7 专家系统的局限性9.8 本章小结第10章、计算机视觉10.1 计算机视觉概述10.2 数字图像的类型及机内表示10.3 常用计算机视觉模型和关…