mirror of
https://github.com/SocialSisterYi/bilibili-API-collect.git
synced 2025-02-06 01:00:09 +08:00
添加文档【Wbi 接口签名】,修改目录结构
This commit is contained in:
parent
cefb7e7c40
commit
05ac3d5e2a
16
README.md
16
README.md
@ -58,10 +58,14 @@ B站 API 采用 C/S 结构,大多数接口为 REST API 和 gRPC,少部分接
|
||||
|
||||
计划整理分类 & 目录:(文档已完结请选中 checkbox)
|
||||
|
||||
- [x] [API 签名](docs/other/API_sign.md)
|
||||
- [x] [公共错误码](docs/other/errcode.md)
|
||||
- [x] [图片格式化](docs/other/picture.md)
|
||||
- [x] [bvid 说明](docs/other/bvid_desc.md)
|
||||
- [ ] [接口签名与验证](docs/misc/sign)
|
||||
- [x] [APP API 签名](docs/misc/sign/APP.md)(`appkey`与`sign`)
|
||||
- [x] [已知的 APPKey](docs/misc/sign/APPKey.md)
|
||||
- [x] [Wbi 签名](docs/misc/sign/wbi.md)(`wts`与`w_rid`)
|
||||
|
||||
- [x] [公共错误码](docs/misc/errcode.md)
|
||||
- [x] [图片格式化](docs/misc/picture.md)
|
||||
- [x] [bvid 说明](docs/misc/bvid_desc.md)
|
||||
- [ ] [gRPC API 接口定义](grpc_api)
|
||||
- [ ] [登录](docs/login)
|
||||
- [x] [登录操作 (人机认证)](docs/login/login_action)
|
||||
@ -234,8 +238,8 @@ B站 API 采用 C/S 结构,大多数接口为 REST API 和 gRPC,少部分接
|
||||
- [ ] [终端网络查询](docs/clientinfo)
|
||||
- [x] [基于ip的地理位置查询](docs/clientinfo/ip.md)
|
||||
- [x] [终端信息查询](docs/clientinfo/client_info.md)
|
||||
- [ ] [其他](docs/other)
|
||||
- [x] [获取当前时间戳](docs/other/time_stamp.md)
|
||||
- [ ] [其他](docs/misc)
|
||||
- [x] [获取当前时间戳](docs/misc/time_stamp.md)
|
||||
- [ ] [web端组件](docs/web_widget)
|
||||
- [x] [分区当日投稿数](docs/web_widget/zone_upload.md)
|
||||
- [x] [404 页漫画收集](docs/web_widget/404_manga.md)
|
||||
|
@ -27,30 +27,33 @@
|
||||
| -------------------- | ---- | ---------------- | ------------------------------------------------- |
|
||||
| isLogin | bool | 是否已登录 | false:未登录<br />true:已登录 |
|
||||
| email_verified | num | 是否验证邮箱地址 | 0:未验证<br />1:已验证 |
|
||||
| face | str | 用户头像url | |
|
||||
| face | str | 用户头像 url | |
|
||||
| level_info | obj | 等级信息 | |
|
||||
| mid | num | 用户mid | |
|
||||
| mid | num | 用户 mid | |
|
||||
| mobile_verified | num | 是否验证手机号 | 0:未验证<br />1:已验证 |
|
||||
| money | num | 拥有硬币数 | |
|
||||
| moral | num | 当前节操值 | 上限为70 |
|
||||
| official | obj | 认证信息 | |
|
||||
| officialVerify | obj | 认证信息2 | |
|
||||
| officialVerify | obj | 认证信息 2 | |
|
||||
| pendant | obj | 头像框信息 | |
|
||||
| scores | num | 0 | 作用尚不明确 |
|
||||
| scores | num | (?) | |
|
||||
| uname | str | 用户昵称 | |
|
||||
| vipDueDate | num | 会员到期时间 | 毫秒 时间戳 |
|
||||
| vipStatus | num | 会员开通状态 | 0:无<br />1:有 |
|
||||
| vipType | num | 会员类型 | 0:无<br />1:月度大会员<br />2:年度及以上大会员 |
|
||||
| vip_pay_type | num | 会员开通状态 | 0:无<br />1:有 |
|
||||
| vip_theme_type | num | 0 | 作用尚不明确 |
|
||||
| vip_theme_type | num | (?) | |
|
||||
| vip_label | obj | 会员标签 | |
|
||||
| vip_avatar_subscript | num | 是否显示会员图标 | 0:不显示<br />1:显示 |
|
||||
| vip_nickname_color | str | 会员昵称颜色 | 颜色码 |
|
||||
| wallet | obj | B币钱包信息 | |
|
||||
| has_shop | bool | 是否拥有推广商品 | false:无<br />true:有 |
|
||||
| shop_url | str | 商品推广页面url | |
|
||||
| allowance_count | num | 0 | 作用尚不明确 |
|
||||
| answer_status | num | 0 | 作用尚不明确 |
|
||||
| shop_url | str | 商品推广页面 url | |
|
||||
| allowance_count | num | (?) | |
|
||||
| answer_status | num | (?) | |
|
||||
| is_senior_member | num | 是否硬核会员 | 0:非硬核会员<br />1:硬核会员 |
|
||||
| wbi_img | obj | Wbi 签名实时口令 | 该字段即使用户未登录也存在 |
|
||||
| is_jury | bool | (?) | |
|
||||
|
||||
`data`中的`level_info`对象:
|
||||
|
||||
@ -79,35 +82,42 @@
|
||||
|
||||
`data`中的`pendant`对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ------ | ---- | ----------- | ------------ |
|
||||
| pid | num | 挂件id | |
|
||||
| name | str | 挂件名称 | |
|
||||
| image | str | 挂件图片url | |
|
||||
| expire | num | 0 | 作用尚不明确 |
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ------ | ---- | ----------- | ---- |
|
||||
| pid | num | 挂件id | |
|
||||
| name | str | 挂件名称 | |
|
||||
| image | str | 挂件图片url | |
|
||||
| expire | num | (?) | |
|
||||
|
||||
`data`中的`vip_label`对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ----------- | ---- | -------- | ------------------------------------------------------------ |
|
||||
| path | str | 空 | 作用尚不明确 |
|
||||
| path | str | (?) | |
|
||||
| text | str | 会员名称 | |
|
||||
| label_theme | str | 会员标签 | vip:大会员<br />annual_vip:年度大会员<br />ten_annual_vip:十年大会员<br />hundred_annual_vip:百年大会员 |
|
||||
|
||||
`data`中的`wallet`对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| --------------- | ---- | ------------- | ------------ |
|
||||
| mid | num | 登录用户mid | |
|
||||
| bcoin_balance | num | 拥有B币数 | |
|
||||
| coupon_balance | num | 每月奖励B币数 | |
|
||||
| coupon_due_time | num | 0 | 作用尚不明确 |
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| --------------- | ---- | ------------- | ---- |
|
||||
| mid | num | 登录用户mid | |
|
||||
| bcoin_balance | num | 拥有B币数 | |
|
||||
| coupon_balance | num | 每月奖励B币数 | |
|
||||
| coupon_due_time | num | (?) | |
|
||||
|
||||
`data`中的`wbi_img`对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ------- | ---- | ------------------------------- | ---------------------------------------- |
|
||||
| img_url | str | Wbi 签名参数 `imgKey`的伪装 url | 详见文档 [Wbi 签名](../misc/sign/wbi.md) |
|
||||
| sub_url | str | Wbi 签名参数 `subKey`的伪装 url | 详见文档 [Wbi 签名](../misc/sign/wbi.md) |
|
||||
|
||||
**示例:**
|
||||
|
||||
```shell
|
||||
curl 'https://api.bilibili.com/nav' \
|
||||
-b 'SESSDATA=xxx'
|
||||
-b 'SESSDATA=xxx'
|
||||
```
|
||||
|
||||
<details>
|
||||
@ -115,64 +125,110 @@ curl 'https://api.bilibili.com/nav' \
|
||||
|
||||
```json
|
||||
{
|
||||
"code":0,
|
||||
"message":"0",
|
||||
"ttl":1,
|
||||
"data":{
|
||||
"isLogin":true,
|
||||
"email_verified":1,
|
||||
"face":"http://i1.hdslb.com/bfs/face/aebb2639a0d47f2ce1fec0631f412eaf53d4a0be.jpg",
|
||||
"level_info":{
|
||||
"current_level":5,
|
||||
"current_min":10800,
|
||||
"current_exp":17065,
|
||||
"next_exp":28800
|
||||
"code": 0,
|
||||
"message": "0",
|
||||
"ttl": 1,
|
||||
"data": {
|
||||
"isLogin": true,
|
||||
"email_verified": 1,
|
||||
"face": "https://i0.hdslb.com/bfs/face/aebb2639a0d47f2ce1fec0631f412eaf53d4a0be.jpg",
|
||||
"face_nft": 0,
|
||||
"face_nft_type": 0,
|
||||
"level_info": {
|
||||
"current_level": 6,
|
||||
"current_min": 28800,
|
||||
"current_exp": 52689,
|
||||
"next_exp": "--"
|
||||
},
|
||||
"mid":293793435,
|
||||
"mobile_verified":1,
|
||||
"money":33.4,
|
||||
"moral":70,
|
||||
"official":{
|
||||
"role":0,
|
||||
"title":"",
|
||||
"desc":"",
|
||||
"type":-1
|
||||
"mid": 293793435,
|
||||
"mobile_verified": 1,
|
||||
"money": 172.4,
|
||||
"moral": 70,
|
||||
"official": {
|
||||
"role": 0,
|
||||
"title": "",
|
||||
"desc": "",
|
||||
"type": -1
|
||||
},
|
||||
"officialVerify":{
|
||||
"type":-1,
|
||||
"desc":""
|
||||
"officialVerify": {
|
||||
"type": -1,
|
||||
"desc": ""
|
||||
},
|
||||
"pendant":{
|
||||
"pid":0,
|
||||
"name":"",
|
||||
"image":"",
|
||||
"expire":0,
|
||||
"image_enhance":""
|
||||
"pendant": {
|
||||
"pid": 2511,
|
||||
"name": "初音未来13周年",
|
||||
"image": "https://i0.hdslb.com/bfs/garb/item/4f8f3f1f2d47f0dad84f66aa57acd4409ea46361.png",
|
||||
"expire": 0,
|
||||
"image_enhance": "https://i0.hdslb.com/bfs/garb/item/fe0b83b53e2342b16646f6e7a9370d8a867decdb.webp",
|
||||
"image_enhance_frame": "https://i0.hdslb.com/bfs/garb/item/127c507ec8448be30cf5f79500ecc6ef2fd32f2c.png"
|
||||
},
|
||||
"scores":0,
|
||||
"uname":"社会易姐QwQ",
|
||||
"vipDueDate":1612454400000,
|
||||
"vipStatus":1,
|
||||
"vipType":2,
|
||||
"vip_pay_type":1,
|
||||
"vip_theme_type":0,
|
||||
"vip_label":{
|
||||
"path":"",
|
||||
"text":"年度大会员",
|
||||
"label_theme":"annual_vip"
|
||||
"scores": 0,
|
||||
"uname": "社会易姐QwQ",
|
||||
"vipDueDate": 1707494400000,
|
||||
"vipStatus": 1,
|
||||
"vipType": 2,
|
||||
"vip_pay_type": 0,
|
||||
"vip_theme_type": 0,
|
||||
"vip_label": {
|
||||
"path": "",
|
||||
"text": "年度大会员",
|
||||
"label_theme": "annual_vip",
|
||||
"text_color": "#FFFFFF",
|
||||
"bg_style": 1,
|
||||
"bg_color": "#FB7299",
|
||||
"border_color": "",
|
||||
"use_img_label": true,
|
||||
"img_label_uri_hans": "",
|
||||
"img_label_uri_hant": "",
|
||||
"img_label_uri_hans_static": "https://i0.hdslb.com/bfs/vip/8d4f8bfc713826a5412a0a27eaaac4d6b9ede1d9.png",
|
||||
"img_label_uri_hant_static": "https://i0.hdslb.com/bfs/activity-plat/static/20220614/e369244d0b14644f5e1a06431e22a4d5/VEW8fCC0hg.png"
|
||||
},
|
||||
"vip_avatar_subscript":1,
|
||||
"vip_nickname_color":"#FB7299",
|
||||
"wallet":{
|
||||
"mid":293793435,
|
||||
"bcoin_balance":8,
|
||||
"coupon_balance":5,
|
||||
"coupon_due_time":0
|
||||
"vip_avatar_subscript": 1,
|
||||
"vip_nickname_color": "#FB7299",
|
||||
"vip": {
|
||||
"type": 2,
|
||||
"status": 1,
|
||||
"due_date": 1707494400000,
|
||||
"vip_pay_type": 0,
|
||||
"theme_type": 0,
|
||||
"label": {
|
||||
"path": "",
|
||||
"text": "年度大会员",
|
||||
"label_theme": "annual_vip",
|
||||
"text_color": "#FFFFFF",
|
||||
"bg_style": 1,
|
||||
"bg_color": "#FB7299",
|
||||
"border_color": "",
|
||||
"use_img_label": true,
|
||||
"img_label_uri_hans": "",
|
||||
"img_label_uri_hant": "",
|
||||
"img_label_uri_hans_static": "https://i0.hdslb.com/bfs/vip/8d4f8bfc713826a5412a0a27eaaac4d6b9ede1d9.png",
|
||||
"img_label_uri_hant_static": "https://i0.hdslb.com/bfs/activity-plat/static/20220614/e369244d0b14644f5e1a06431e22a4d5/VEW8fCC0hg.png"
|
||||
},
|
||||
"avatar_subscript": 1,
|
||||
"nickname_color": "#FB7299",
|
||||
"role": 3,
|
||||
"avatar_subscript_url": "",
|
||||
"tv_vip_status": 0,
|
||||
"tv_vip_pay_type": 0,
|
||||
"tv_due_date": 1640793600
|
||||
},
|
||||
"has_shop":false,
|
||||
"shop_url":"",
|
||||
"allowance_count":0,
|
||||
"answer_status":0
|
||||
"wallet": {
|
||||
"mid": 293793435,
|
||||
"bcoin_balance": 5,
|
||||
"coupon_balance": 5,
|
||||
"coupon_due_time": 0
|
||||
},
|
||||
"has_shop": true,
|
||||
"shop_url": "https://gf.bilibili.com?msource=main_station",
|
||||
"allowance_count": 0,
|
||||
"answer_status": 0,
|
||||
"is_senior_member": 1,
|
||||
"wbi_img": {
|
||||
"img_url": "https://i0.hdslb.com/bfs/wbi/653657f524a547ac981ded72ea172057.png",
|
||||
"sub_url": "https://i0.hdslb.com/bfs/wbi/6e4909c702f846728e64f6007736a338.png"
|
||||
},
|
||||
"is_jury": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
65
docs/misc/sign/APP.md
Normal file
65
docs/misc/sign/APP.md
Normal file
@ -0,0 +1,65 @@
|
||||
# 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
|
||||
|
||||
该 Demo 提供 [Python](#Python) 语言例程
|
||||
|
||||
使用 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
|
||||
```
|
54
docs/misc/sign/APPKey.md
Normal file
54
docs/misc/sign/APPKey.md
Normal file
@ -0,0 +1,54 @@
|
||||
# APIKey
|
||||
|
||||
以下为已知的 APPkey / APPSec,及部分使用场景参数信息,均来自抓包与逆向工程
|
||||
|
||||
| APPKEY | APPSEC | platform<sup>2</sup> | APP类型 | neuronAppId<sup>1</sup> | mobi_app<sup>2</sup> | 备注 |
|
||||
| :--------------: | :------------------------------: | :------------------: | :----------------: | :---------------------: | :------------------: | :----------------------------------------: |
|
||||
| 9d5889cf67e615cd | 8fd9bb32efea8cef801fd895bef2713d | `android` | Ai4cCreatorAndroid | | | |
|
||||
| 1d8b6e7d45233436 | 560c52ccd288fed045859ed18bffd973 | `android` | 粉版 | `1` | `android` | 获取资源通用 |
|
||||
| 783bbb7264451d82 | 2653583c8873dea268ab9386918b1d65 | `android` | 粉版 | `1` | `android` | 仅获取用户信息时使用(7.X及更新版本) |
|
||||
| 57263273bc6b67f6 | a0488e488d1567960d3a765e8d129f90 | `android` | 粉版 | `1` | `android` | 可能来自旧版 |
|
||||
| 07da50c9a0bf829f | 25bdede4e1581c836cab73a48790ca6e | `android` | 概念版 | `3` | `android_b` | |
|
||||
| 191c3b6b975af184 | | `android` | 概念版 | `3` | `android_b` | 新出现, 仅获取用户信息时使用. 暂未知appsec |
|
||||
| 178cf125136ca8ea | 34381a26236dd1171185c0beb042e1c6 | `android` | 概念版 | `3` | `android_b` | 可能来自旧版 |
|
||||
| 7d336ec01856996b | a1ce6983bc89e20a36c37f40c4f1a0dd | `android` | 概念版 | `3` | `android_b` | 可能来自旧版 |
|
||||
| dfca71928277209b | b5475a8825547a4fc26c7d518eaaa02e | `android` | HD 版 | `5` | `android_hd` | |
|
||||
| bb3101000e232e27 | 36efcfed79309338ced0380abd824ac1 | `android` | 白版 | `14` | `android_i` | |
|
||||
| ae57252b0c09105d | c75875c596a69eb55bd119e74b07cfe3 | `android` | 白版 | `14` | `android_i` | 仅获取用户信息时使用(7.X及更新版本) |
|
||||
| 8e16697a1b4f8121 | f5dd03b752426f2e623d7badb28d190a | `android` | 白版 | `14` | `android_i` | 可能来自旧版 |
|
||||
| 7d089525d3611b1c | acd495b248ec528c2eed1e862d393126 | `android` | 蓝版 | `30` | `bstar_a` | |
|
||||
| iVGUTjsxvpLeuDCf | aHRmhWMLkdeMuILqORnYZocwMBpMEOdt | `android` | - | - | - | 视频取流专用, 仅5.X旧版使用 |
|
||||
| YvirImLGlLANCLvM | JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM | `ios` | - | - | - | 视频取流专用 |
|
||||
| 27eb53fc9058f8c3 | c2ed53a74eeefe3cf99fbd01d8c9c375 | `web`/`ios`? | - | - | - | 第三方授权使用 |
|
||||
| 84956560bc028eb7 | 94aba54af9065f71de72f5508f1cd42e | ? | UWP 版 | - | - | 部分API不接受此appkey, 返回-663错误 |
|
||||
| 85eb6835b0a1034e | 2ad42749773c441109bdc0191257a664 | ? | UWP 版? | - | - | 部分API不接受此appkey, 返回-663错误 |
|
||||
| 4ebafd7c4951b366 | 8cb98205e9b2ad3669aad0fce12a4c13 | `ios` | iPhone 客户端? | `iphone` | ? | |
|
||||
| 8d23902c1688a798 | 710f0212e62bd499b8d3ac6e1db9302a | `android` | AndroidBiliThings | ? | ? | |
|
||||
| 4c6e1021617d40d9 | e559a59044eb2701b7a8628c86aa12ae | `android` | AndroidMallTicket | ? | ? | |
|
||||
| c034e8b74130a886 | e4e8966b1e71847dc4a3830f2d078523 | `android` | AndroidOttSdk | `7` | ? | |
|
||||
| 4409e2ce8ffd12b8 | 59b43e04ad6965f34319062b478f83dd | `android` | 云视听小电视(TV版) | `9`? | `android_tv_yst`? | |
|
||||
| 37207f2beaebf8d7 | e988e794d4d4b6dd43bc0e89d6e90c43 | `android` | BiliLink | ? | ? | |
|
||||
| 9a75abf7de2d8947 | 35ca1c82be6c2c242ecc04d88c735f31 | `android` | BiliScan | ? | ? | |
|
||||
| aae92bc66f3edfab | af125a0d5279fd576c1b4418a3e8276d | ? | PC 投稿工具 | - | ? | |
|
||||
| bca7e84c2d947ac6 | 60698ba2f68e01ce44738920a0ffe768 | ? | login | - | ? | |
|
||||
|
||||
注释:
|
||||
|
||||
<sup>1</sup> `neuronAppId`,产品编号,由数据平台分配,详情如下:
|
||||
|
||||
- 粉(国内版)=1
|
||||
- 白(GooglePlay 版)=2
|
||||
- 蓝(东南亚版)=3
|
||||
- 直播姬=4
|
||||
- HD=5
|
||||
- 海外=6
|
||||
- OTT=7
|
||||
- 漫画=8
|
||||
- TV野版=9
|
||||
- 小视频=10
|
||||
- 网易漫画=11
|
||||
- 网易漫画lite=12
|
||||
- 网易漫画HD=13,
|
||||
- 国际版=14
|
||||
|
||||
<sup>2</sup> `platform`, `mobi_app` 仅供参考, 具体值需要抓包确定.
|
250
docs/misc/sign/wbi.md
Normal file
250
docs/misc/sign/wbi.md
Normal file
@ -0,0 +1,250 @@
|
||||
# Wbi签名
|
||||
|
||||
自 2023 年三月起,B站 Web 端部分接口开始使用 Wbi 鉴权方式,即一种独立于 [APP 鉴权](APP.md) 与其他 Cookie 鉴权的方式,表现在 REST API 请求时在 query 中添加了`w_rid`和`wts`字段,为一种 Web 端的风控手段
|
||||
|
||||
这些接口涵盖”用户投稿视频“、”用户投稿专栏“、”首页推送“、”推广信息“、”热搜“、”视频信息“、”视频取流“、”搜索“等待主要查询性业务接口,如果请求这些 REST API 缺失`w_rid`和`wts`字段,则会在数次请求后返回`-403:非法访问`这样的风控错误
|
||||
|
||||
感谢 [#631](https://github.com/SocialSisterYi/bilibili-API-collect/issues/631) 的研究与逆向工程
|
||||
|
||||
## Wbi签名算法
|
||||
|
||||
1. 获取实时口令
|
||||
|
||||
从 [nav 接口](../../login/login_info.md#导航栏用户信息) 中获取`img_url`、`sub_url`两个字段的参数,并保存备用(如存入 localStorage),相关内容节选如下:
|
||||
|
||||
**注:`img_url`、`sub_url`两个字段的值看似为存于 BFS 中的 png 图片 url,实则只是经过伪装的实时 Token,故无需且不能试图访问这两个 url**
|
||||
|
||||
```json
|
||||
"wbi_img": {
|
||||
"img_url": "https://i0.hdslb.com/bfs/wbi/653657f524a547ac981ded72ea172057.png",
|
||||
"sub_url": "https://i0.hdslb.com/bfs/wbi/6e4909c702f846728e64f6007736a338.png"
|
||||
},
|
||||
```
|
||||
这两个 Key 均为 url 中末尾路径的无扩展名的文件名,即`img_key=653657f524a547ac981ded72ea172057`,`sub_key=6e4909c702f846728e64f6007736a338`
|
||||
|
||||
这两个 Key 的值无关登录 Session 与 IP,属于全站统一使用的,但**每日都会变化**,使用时应做好**缓存和刷新**处理
|
||||
|
||||
2. 打乱重排实时口令
|
||||
|
||||
把上一步获取到的`img_key`拼接在`sub_key`后面**(这里不是`img_url`和`sub_url`)**作为一个整体,将这个整体进行特定的顺序的字符打乱重排,再将重排后的字符串截取前 30 字符的切片,作为一个新的变量`mixin_key`,重排映射表长为 64,内容如下:
|
||||
|
||||
```javascript
|
||||
const mixinKeyEncTab = [
|
||||
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
|
||||
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
|
||||
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
|
||||
36, 20, 34, 44, 52
|
||||
]
|
||||
```
|
||||
|
||||
打乱重排内容如下(以上述第 1 步的参数作为输入)
|
||||
|
||||
```
|
||||
72136226c6a73669787ee4fd02a74c27
|
||||
```
|
||||
|
||||
3. 将欲签名的请求参数排序后编码
|
||||
|
||||
若下方内容为欲签名的请求参数(以 js obj 为例)
|
||||
|
||||
```javascript
|
||||
{
|
||||
foo: '114',
|
||||
bar: '514',
|
||||
baz: 1919810
|
||||
}
|
||||
```
|
||||
|
||||
那么按照 Key 排序并进行 url query 编码后的结果应为:
|
||||
|
||||
```
|
||||
bar=514&baz=1919810&foo=114
|
||||
```
|
||||
|
||||
4. 为参数中添加`wts`时间戳
|
||||
|
||||
`wts`字段的值应为以秒为单位的 Unix TimeStamp,如`1684746387`
|
||||
|
||||
将`wts`参数添加在参数列表最后,即:
|
||||
|
||||
```
|
||||
bar=514&baz=1919810&foo=114&wts=1684746387
|
||||
```
|
||||
|
||||
5. 计算`w_rid`并添加在其后
|
||||
|
||||
在上一步得出的 url query 字符串后拼接第 2 步计算得出的`mixin_key`(作为盐)
|
||||
|
||||
```
|
||||
bar=514&baz=1919810&foo=114&wts=168474638772136226c6a73669787ee4fd02a74c27
|
||||
```
|
||||
|
||||
对这个整体进行 **md5 Hash 运算**(32-bit 字符小写),得到的值便是 Wbi Sign,也就是参数`w_rid`
|
||||
|
||||
```
|
||||
d3cbd2a2316089117134038bf4caf442
|
||||
```
|
||||
|
||||
最后一步,把这个计算出的值作为参数`w_rid`添加在原始参数列表后,也就完成了一次 Wbi Sign,可以调用 REST API 进行请求了
|
||||
|
||||
```
|
||||
bar=514&baz=1919810&foo=114&wts=1684746387&w_rid=d3cbd2a2316089117134038bf4caf442
|
||||
```
|
||||
|
||||
## Wbi签名算法实现Demo
|
||||
|
||||
该 Demo 提供 [Python](#Python)、[JavaScript](#JavaScript) 语言
|
||||
|
||||
### Python
|
||||
|
||||
需要`requests`依赖
|
||||
|
||||
```python
|
||||
from functools import reduce
|
||||
from hashlib import md5
|
||||
import urllib.parse
|
||||
import time
|
||||
import requests
|
||||
|
||||
mixinKeyEncTab = [
|
||||
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
|
||||
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
|
||||
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
|
||||
36, 20, 34, 44, 52
|
||||
]
|
||||
|
||||
def getMixinKey(orig: str):
|
||||
'对 imgKey 和 subKey 进行字符顺序打乱编码'
|
||||
return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, '')[:32]
|
||||
|
||||
def encWbi(params: dict, img_key: str, sub_key: str):
|
||||
'为请求参数进行 wbi 签名'
|
||||
mixin_key = getMixinKey(img_key + sub_key)
|
||||
curr_time = round(time.time())
|
||||
params['wts'] = curr_time # 添加 wts 字段
|
||||
params = dict(sorted(params.items())) # 按照 key 重排参数
|
||||
# 过滤 value 中的 "!'()*" 字符
|
||||
params = {
|
||||
k : ''.join(filter(lambda chr: chr not in "!'()*", str(v)))
|
||||
for k, v
|
||||
in params.items()
|
||||
}
|
||||
query = urllib.parse.urlencode(params) # 序列化参数
|
||||
wbi_sign = md5((query + mixin_key).encode()).hexdigest() # 计算 w_rid
|
||||
params['w_rid'] = wbi_sign
|
||||
return params
|
||||
|
||||
def getWbiKeys() -> tuple[str, str]:
|
||||
'获取最新的 img_key 和 sub_key'
|
||||
resp = requests.get('https://api.bilibili.com/x/web-interface/nav')
|
||||
resp.raise_for_status()
|
||||
json_content = resp.json()
|
||||
img_url: str = json_content['data']['wbi_img']['img_url']
|
||||
sub_url: str = json_content['data']['wbi_img']['sub_url']
|
||||
img_key = img_url.rsplit('/', 1)[1].split('.')[0]
|
||||
sub_key = sub_url.rsplit('/', 1)[1].split('.')[0]
|
||||
return img_key, sub_key
|
||||
|
||||
img_key, sub_key = getWbiKeys()
|
||||
|
||||
signed_params = encWbi(
|
||||
params={
|
||||
'foo': '114',
|
||||
'bar': '514',
|
||||
'baz': 1919810
|
||||
},
|
||||
img_key=img_key,
|
||||
sub_key=sub_key
|
||||
)
|
||||
query = urllib.parse.urlencode(signed_params)
|
||||
print(signed_params)
|
||||
print(query)
|
||||
```
|
||||
|
||||
输出内容分别是进行 Wbi 签名的后参数的 key-Value 以及 url query 形式
|
||||
|
||||
```
|
||||
{'bar': '514', 'baz': '1919810', 'foo': '114', 'wts': '1684746387', 'w_rid': 'd3cbd2a2316089117134038bf4caf442'}
|
||||
bar=514&baz=1919810&foo=114&wts=1684746387&w_rid=d3cbd2a2316089117134038bf4caf442
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
需要`axios`、`md5`依赖
|
||||
|
||||
```javascript
|
||||
import md5 from 'md5'
|
||||
import axios from 'axios'
|
||||
|
||||
const mixinKeyEncTab = [
|
||||
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
|
||||
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
|
||||
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
|
||||
36, 20, 34, 44, 52
|
||||
]
|
||||
|
||||
// 对 imgKey 和 subKey 进行字符顺序打乱编码
|
||||
function getMixinKey(orig) {
|
||||
let temp = ''
|
||||
mixinKeyEncTab.forEach((n) => {
|
||||
temp += orig[n]
|
||||
})
|
||||
return temp.slice(0, 32)
|
||||
}
|
||||
|
||||
// 为请求参数进行 wbi 签名
|
||||
function encWbi(params, img_key, sub_key) {
|
||||
const mixin_key = getMixinKey(img_key + sub_key),
|
||||
curr_time = Math.round(Date.now() / 1000),
|
||||
chr_filter = /[!'\(\)*]/g
|
||||
let query = []
|
||||
params = Object.assign(params, {wts: curr_time}) // 添加 wts 字段
|
||||
// 按照 key 重排参数
|
||||
Object.keys(params).sort().forEach((key) => {
|
||||
query.push(
|
||||
encodeURIComponent(key) +
|
||||
'=' +
|
||||
// 过滤 value 中的 "!'()*" 字符
|
||||
encodeURIComponent(('' + params[key]).replace(chr_filter, ''))
|
||||
)
|
||||
})
|
||||
query = query.join('&')
|
||||
const wbi_sign = md5(query + mixin_key) // 计算 w_rid
|
||||
return query + '&w_rid=' + wbi_sign
|
||||
}
|
||||
|
||||
// 获取最新的 img_key 和 sub_key
|
||||
async function getWbiKeys() {
|
||||
const resp = await axios({
|
||||
url: 'https://api.bilibili.com/x/web-interface/nav',
|
||||
method: 'get',
|
||||
responseType: 'json'
|
||||
}),
|
||||
json_content = resp.data,
|
||||
img_url = json_content.data.wbi_img.img_url,
|
||||
sub_url = json_content.data.wbi_img.sub_url
|
||||
return {
|
||||
img_key: img_url.substring(img_url.lastIndexOf('/') + 1, img_url.length).split('.')[0],
|
||||
sub_key: sub_url.substring(sub_url.lastIndexOf('/') + 1, sub_url.length).split('.')[0]
|
||||
}
|
||||
}
|
||||
|
||||
const wbi_keys = await getWbiKeys()
|
||||
|
||||
const query = encWbi(
|
||||
{
|
||||
foo: '114',
|
||||
bar: '514',
|
||||
baz: 1919810
|
||||
},
|
||||
wbi_keys.img_key,
|
||||
wbi_keys.sub_key
|
||||
)
|
||||
console.log(query)
|
||||
```
|
||||
|
||||
输出内容为进行 Wbi 签名的后参数的 url query 形式
|
||||
|
||||
```
|
||||
bar=514&baz=1919810&foo=114&wts=1684805578&w_rid=bb97e15f28edf445a0e4420d36f0157e
|
||||
```
|
@ -1,93 +0,0 @@
|
||||
# API 签名与鉴权
|
||||
|
||||
部分客户端专用的 RESTful API 存在基于 sign 的鉴权,需要使用规定的`appkey`及其对应的`appsec`与原始请求参数进行签名计算
|
||||
|
||||
不同 `appkey` 对应不同的 app (如客户端、概念版、必剪、漫画、bililink等)
|
||||
|
||||
不同平台同 app 也会存在不同的 `appkey` (如安卓端、ios端、TV端等)
|
||||
|
||||
同平台同 app 下不同功能也会存在不同的 `appkey`(如登录专用、取流专用等)
|
||||
|
||||
不同版本的客户端的 `appkey` 也可能不同
|
||||
|
||||
**appkey与appsec一一对应**
|
||||
|
||||
## API签名的计算方式
|
||||
|
||||
首先为参数中添加`appkey`字段,然后按照参数的 key 重新排序,再将重排序后的参数使用 url query 格式序列化拼接与该 appkey 相对应的 appsec (盐值) 进行**md5 hash计算**(32位小写),该 hash 便是 API 签名
|
||||
|
||||
为参数尾部增添`sign`字段,它的值为上一步计算所得的 hash,一并作为表单提交
|
||||
|
||||
**实例:**
|
||||
|
||||
使用 appkey = `1d8b6e7d45233436`, appsec = `560c52ccd288fed045859ed18bffd973` 对如下 `params` 参数进行签名
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
import urllib.parse
|
||||
|
||||
def appsign(params, appkey, appsec):
|
||||
'为请求参数进行 api 签名'
|
||||
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)
|
||||
```
|
||||
|
||||
输出以下内容,分别是进行 api 签名后参数的 dict 以及 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
|
||||
```
|
||||
## 已知的APPKEY/APPSEC, 及部分参数信息
|
||||
|
||||
| APPKEY | APPSEC | platform<sup>2</sup> | APP类型 | neuronAppId<sup>1</sup> | mobi_app<sup>2</sup> | 备注 |
|
||||
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|
||||
| 9d5889cf67e615cd | 8fd9bb32efea8cef801fd895bef2713d | `android` | Ai4cCreatorAndroid |
|
||||
| 1d8b6e7d45233436 | 560c52ccd288fed045859ed18bffd973 | `android` | 普通版(粉版) | `1` |`android`| 获取资源通用 |
|
||||
| 783bbb7264451d82 | 2653583c8873dea268ab9386918b1d65 | `android` | 普通版(粉版) | `1` | `android` | 仅获取用户信息时使用(7.X及更新版本) |
|
||||
| 57263273bc6b67f6 | a0488e488d1567960d3a765e8d129f90 | `android` | 普通版(粉版) | `1` |`android`| 可能来自旧版 |
|
||||
| 07da50c9a0bf829f | 25bdede4e1581c836cab73a48790ca6e | `android` | 概念版(蓝版) | `3` | `android_b` |
|
||||
| 191c3b6b975af184 | ******************************** | `android` | 概念版(蓝版) | `3` | `android_b` | 新出现, 仅获取用户信息时使用. 暂未知appsec |
|
||||
| 178cf125136ca8ea | 34381a26236dd1171185c0beb042e1c6 | `android` | 概念版(蓝版) | `3` | `android_b` | 可能来自旧版 |
|
||||
| 7d336ec01856996b | a1ce6983bc89e20a36c37f40c4f1a0dd | `android` | 概念版(蓝版) | `3` | `android_b` | 可能来自旧版 |
|
||||
| dfca71928277209b | b5475a8825547a4fc26c7d518eaaa02e | `android` | HD版 | `5` | `android_hd` |
|
||||
| bb3101000e232e27 | 36efcfed79309338ced0380abd824ac1 | `android` | play版(国际版) | `14` | `android_i` |
|
||||
| ae57252b0c09105d | c75875c596a69eb55bd119e74b07cfe3 | `android` | play版(国际版) | `14` | `android_i` | 仅获取用户信息时使用(7.X及更新版本) |
|
||||
| 8e16697a1b4f8121 | f5dd03b752426f2e623d7badb28d190a | `android` | play版(国际版) | `14` | `android_i` | 可能来自旧版 |
|
||||
| 7d089525d3611b1c | acd495b248ec528c2eed1e862d393126 | `android` | 东南亚版 | `30` | `bstar_a` |
|
||||
| iVGUTjsxvpLeuDCf | aHRmhWMLkdeMuILqORnYZocwMBpMEOdt | `android` | - | - | - | 视频取流专用, 仅5.X旧版使用 |
|
||||
| YvirImLGlLANCLvM | JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM | `ios` | - | - | - | 视频取流专用 |
|
||||
| 27eb53fc9058f8c3 | c2ed53a74eeefe3cf99fbd01d8c9c375 | `web`/`ios`? | - | - | - | 第三方授权使用 |
|
||||
| 84956560bc028eb7 | 94aba54af9065f71de72f5508f1cd42e | ? | UWP版 | - | - | 部分API不接受此appkey, 返回-663错误 |
|
||||
| 85eb6835b0a1034e | 2ad42749773c441109bdc0191257a664 | ? | UWP版? | - | - | 部分API不接受此appkey, 返回-663错误 |
|
||||
| 4ebafd7c4951b366 | 8cb98205e9b2ad3669aad0fce12a4c13 | `ios` | iPhone客户端? | `iphone` | ? |
|
||||
| 8d23902c1688a798 | 710f0212e62bd499b8d3ac6e1db9302a | `android` | AndroidBiliThings | ? | ? |
|
||||
| 4c6e1021617d40d9 | e559a59044eb2701b7a8628c86aa12ae | `android` | AndroidMallTicket | ? | ? |
|
||||
| c034e8b74130a886 | e4e8966b1e71847dc4a3830f2d078523 | `android` | AndroidOttSdk | `7` | ? |
|
||||
| 4409e2ce8ffd12b8 | 59b43e04ad6965f34319062b478f83dd | `android` | 云视听小电视(TV版) | `9`? | `android_tv_yst`? |
|
||||
| 37207f2beaebf8d7 | e988e794d4d4b6dd43bc0e89d6e90c43 | `android` | BiliLink | ? | ? |
|
||||
| 9a75abf7de2d8947 | 35ca1c82be6c2c242ecc04d88c735f31 | `android` | BiliScan | ? | ? |
|
||||
| aae92bc66f3edfab | af125a0d5279fd576c1b4418a3e8276d | ? | PC 投稿工具 | - | ? |
|
||||
| bca7e84c2d947ac6 | 60698ba2f68e01ce44738920a0ffe768 | ? | login | - | ? |
|
||||
|
||||
注释:
|
||||
|
||||
<sup>1</sup> `neuronAppId`, 产品编号,由数据平台分配,粉=1,白=2,蓝=3,直播姬=4,HD=5,海外=6,OTT=7,漫画=8,TV野版=9,小视频=10,网易漫画=11,网易漫画lite=12,网易漫画HD=13, 国际版=14.
|
||||
|
||||
<sup>2</sup> `platform`, `mobi_app` 仅供参考, 具体值需要抓包确定.
|
Loading…
Reference in New Issue
Block a user