2023-05-23 09:38:21 +08:00
|
|
|
|
# APP API 签名与鉴权
|
|
|
|
|
|
|
|
|
|
## APP API 签名特性
|
|
|
|
|
|
|
|
|
|
部分客户端专用的 REST API 存在基于参数签名的鉴权,需要使用规定的`appkey`及其对应的`appsec`与原始请求参数进行签名计算,部分`AppKey`及与之对应的`AppSec`已经被公开:见该文档 [APPKey](APPKey.md)
|
|
|
|
|
|
|
|
|
|
- 不同 `appkey` 对应不同的 app (如客户端、概念版、必剪、漫画、bililink等)
|
|
|
|
|
|
|
|
|
|
- 不同平台同 app 也会存在不同的 `appkey` (如安卓端、ios端、TV端等)
|
|
|
|
|
|
|
|
|
|
- 同平台同 app 下不同功能也会存在不同的 `appkey`(如登录专用、取流专用等)
|
|
|
|
|
|
|
|
|
|
- 不同版本的客户端的 `appkey` 也可能不同
|
|
|
|
|
|
|
|
|
|
- **appkey与appsec一一对应**
|
|
|
|
|
|
|
|
|
|
## APP API 签名算法
|
|
|
|
|
|
|
|
|
|
1. 首先为参数中添加`appkey`字段
|
|
|
|
|
2. 然后按照参数的 Key 重新排序
|
|
|
|
|
3. 再对这个 Key-Value 进行 url query 序列化,并拼接与之对应的`appsec` (盐) 进行 **md5 Hash 运算**(32-bit 字符小写),该 hash 便是 API 签名
|
|
|
|
|
4. 最后在参数尾部增添`sign`字段,它的 Value 为上一步计算所得的 hash,一并作为表单或 Query 提交
|
|
|
|
|
|
|
|
|
|
## Demo
|
|
|
|
|
|
2023-07-02 09:44:49 +08:00
|
|
|
|
该 Demo 提供 [Python](#Python) 和 [Java](#Java) 语言例程
|
2023-05-23 09:38:21 +08:00
|
|
|
|
|
|
|
|
|
使用 appkey = `1d8b6e7d45233436`, appsec = `560c52ccd288fed045859ed18bffd973` 对如下 `params` 参数进行签名
|
|
|
|
|
|
|
|
|
|
上述示例`appkey`、`AppSec`均来自文档 [APPKey](APPKey.md)
|
|
|
|
|
|
|
|
|
|
### Python
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
import hashlib
|
|
|
|
|
import urllib.parse
|
|
|
|
|
|
|
|
|
|
def appsign(params, appkey, appsec):
|
|
|
|
|
'为请求参数进行 APP 签名'
|
|
|
|
|
params.update({'appkey': appkey})
|
|
|
|
|
params = dict(sorted(params.items())) # 按照 key 重排参数
|
|
|
|
|
query = urllib.parse.urlencode(params) # 序列化参数
|
|
|
|
|
sign = hashlib.md5((query+appsec).encode()).hexdigest() # 计算 api 签名
|
|
|
|
|
params.update({'sign':sign})
|
|
|
|
|
return params
|
|
|
|
|
|
|
|
|
|
appkey = '1d8b6e7d45233436'
|
|
|
|
|
appsec = '560c52ccd288fed045859ed18bffd973'
|
|
|
|
|
params = {
|
|
|
|
|
'id':114514,
|
|
|
|
|
'str':'1919810',
|
|
|
|
|
'test':'いいよ,こいよ',
|
|
|
|
|
}
|
|
|
|
|
signed_params = appsign(params, appkey, appsec)
|
|
|
|
|
query = urllib.parse.urlencode(signed_params)
|
|
|
|
|
print(signed_params)
|
|
|
|
|
print(query)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
输出内容分别是进行 APP 签名的后参数的 key-Value 以及 url query 形式
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
{'appkey': '1d8b6e7d45233436', 'id': 114514, 'str': '1919810', 'test': 'いいよ,こいよ', 'sign': '01479cf20504d865519ac50f33ba3a7d'}
|
|
|
|
|
appkey=1d8b6e7d45233436&id=114514&str=1919810&test=%E3%81%84%E3%81%84%E3%82%88%EF%BC%8C%E3%81%93%E3%81%84%E3%82%88&sign=01479cf20504d865519ac50f33ba3a7d
|
|
|
|
|
```
|
2023-07-02 09:44:49 +08:00
|
|
|
|
|
|
|
|
|
### Java
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
package io.github.cctyl;
|
|
|
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.security.MessageDigest;
|
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
|
import java.net.URLEncoder;
|
|
|
|
|
import java.util.TreeMap;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @author cctyl
|
|
|
|
|
*/
|
|
|
|
|
public class AppSigner {
|
|
|
|
|
|
|
|
|
|
private static final String APP_KEY = "1d8b6e7d45233436";
|
|
|
|
|
private static final String APP_SEC = "560c52ccd288fed045859ed18bffd973";
|
|
|
|
|
|
|
|
|
|
public static String appSign(Map<String, String> params) {
|
|
|
|
|
// 为请求参数进行 APP 签名
|
|
|
|
|
params.put("appkey", APP_KEY);
|
|
|
|
|
// 按照 key 重排参数
|
|
|
|
|
Map<String, String> sortedParams = new TreeMap<>(params);
|
|
|
|
|
// 序列化参数
|
|
|
|
|
StringBuilder queryBuilder = new StringBuilder();
|
|
|
|
|
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
|
|
|
|
|
if (queryBuilder.length() > 0) {
|
|
|
|
|
queryBuilder.append('&');
|
|
|
|
|
}
|
|
|
|
|
queryBuilder
|
|
|
|
|
.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
|
|
|
|
|
.append('=')
|
|
|
|
|
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
|
|
|
|
|
}
|
|
|
|
|
return generateMD5(queryBuilder .append(APP_SEC).toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String generateMD5(String input) {
|
|
|
|
|
try {
|
|
|
|
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
|
|
|
byte[] digest = md.digest(input.getBytes());
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (byte b : digest) {
|
|
|
|
|
sb.append(String.format("%02x", b));
|
|
|
|
|
}
|
|
|
|
|
return sb.toString();
|
|
|
|
|
} catch (NoSuchAlgorithmException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
Map<String, String> params = new HashMap<>();
|
|
|
|
|
params.put("id", "114514");
|
|
|
|
|
params.put("str", "1919810");
|
|
|
|
|
params.put("test", "いいよ,こいよ");
|
|
|
|
|
System.out.println(appSign(params));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
输出结果为:01479cf20504d865519ac50f33ba3a7d
|