mihoyo-api-collect/other/authentication.md
Kamisato-Ayaka-233 da0476968e 【P】增加对LToken和SToken的不同版本的说明
【A】“用户标识符”文档增加“通过SToken获取LToken(V1)”和“通过SToken获取LToken(V1)”
2023-09-10 17:06:49 +08:00

13 KiB
Raw Permalink Blame History

鉴权


大多数API需要验证请求头。

部分API例如点赞文章、米游社签到福利、米游币等需要使用Cookie鉴权。

请求头

米游社

大多数API需要验证的请求头x-rpc-app_versionx-rpc-client_typex-rpc-device_idX-Requested-WithOriginRefererHostDSUser-Agent

少数API才需要验证的额外的请求头x-rpc-device_fpx-rpc-challengex-rpc-app_idx-rpc-verify_key

可选请求头:x-rpc-device_namex-rpc-device_modelx-rpc-sys_versionx-rpc-channelx-rpc-game_biz

说明:

“需要验证请求头”意味着这个接口需要传递HostRefererOriginUser-Agent。此句之后才会标识需要传递的其它请求头。

出现DS类型时,需要传递DSx-rpc-app_versionx-rpc-client_typeX-Requested-With

x-rpc-app_version

米游社版本号,例如2.50.1

DS字段相关,DS生成需要salt,而每个米游社版本的salt都不同。

因此米游社版本只能使用对应的salt用于生成DS。

x-rpc-client_type

1245这些值。

注:以下列表只是说明该请求头的值通常在哪些平台出现,并不是平台一定只使用对应的值。具体的值请查看接口说明的标注

意义 备注
1 苹果端APP 一般不需要使用
2 安卓端APP
4 网页端
5 其它

根据请求的API不同而变化。

将会在需要验证请求头的API进行标注。

DS字段相关,DS生成需要salt,而不同的x-rpc-client_type对应该版本米游社的salt也不同。

x-rpc-sys_version

安卓系统或iOS大版本号例如Android 13则为13

x-rpc-channel

手机厂商,例如小米则为xiaomi

x-rpc-device_name

手机厂商和手机型号,例如Xiaomi M2101K9C

x-rpc-device_model

手机型号。

x-rpc-device_fp

发送POST请求至https://public-data-api.mihoyo.com/device-fp/api/getFp以获得。

x-rpc-app_id

为应用ID具体值需查看请求头标识与ID对照表

x-rpc-verify_key

一般为应用ID具体值需查看请求头标识与ID对照表

x-rpc-device_id

设备ID由使用的设备决定。

在安卓设备上,一般为一串由ANDROID_ID生成的UUIDKotlin Java同理中可以这样获取

import android.content.Context
import android.provider.Settings
import java.util.UUID

@SuppressLint("HardwareIds")
fun getDeviceId(context: Context): String {
    val androidId = Settings.Secure.getString(context.contentResolver, Settings.System.ANDROID_ID)
    val uuid = UUID.nameUUIDFromBytes(androidId.toByteArray())
    return uuid.toString()
}

X-Requested-With

国内版APP为com.mihoyo.hyperion

国际版APP为com.mihoyo.hoyolab

Origin

请求网站的协议与主机名。

例如请求https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/index通过原神UID获取角色信息则该字段为https://api-takumi-record.mihoyo.com

Host

请求的网站的主机名。

例如请求https://api-takumi.mihoyo.com/account/auth/api/webLoginByPassword(网页登录米游社)则该字段为api-takumi.mihoyo.com

Referer

在哪个网页发起的请求。

一般情况下国内版APP

x-rpc-client_type5,则为https://webstatic.mihoyo.com

x-rpc-client_type4,则为https://www.miyoushe.com

x-rpc-client_type2,则为https://app.mihoyo.com

国际版APP

x-rpc-client_type5,则为https://webstatic-sea.hoyolab.com

x-rpc-client_type4,则为https://www.hoyolab.com

x-rpc-client_type2,则为https://www.hoyolab.com

User-Agent

需要验证请求头的API的用户代理格式为Mozilla/5.0 (Linux; Android 安卓系统大版本号; 手机型号 Build/TKQ1.220829.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36 miHoYoBBS/米游社版本号

例如Mozilla/5.0 (Linux; Android 13; M2101K9C Build/TKQ1.220829.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36 miHoYoBBS/2.51.1

DS

动态签名Dynamic Sign别称“数据签名Data Sign”“动态密钥Dynamic Secret

大多数API需要验证请求头中的DS字段。

DS值通过一系列算法得出。

生成DS

DS的生成方式由以下因素影响:

  • 网页、中国版APP和国际版APP的生成算法不同。
  • 请求头的x-rpc-client_type字段,每个值都有其对应的生成算法。

生成DS需要salt为32个包含大写字母、小写字母、数字的字符串

salt在以下情况下不同:

  • 网页、中国版APP和国际版APP使用的salt不同。
  • 每个APP版本都存有其独有的salt
  • 请求头的x-rpc-client_type字段,每个值使用的salt不同。
  • 少数API有其单独的salt(例如米游社签到福利)。

请在这里获取salt

中国版APP

DS有多个生成算法,分别为DS1DS2

DS2

在请求头中的x-rpc-client_type5时使用。

整体思路:

  1. 获取当前的Unix时间戳整数
  2. 在100000到200000中选取随机整数但是如果随机到100000则加上542367得到642367。
  3. 若将发送POST请求则将发送的数据转为JSON字符串并使用对象的键进行英文字母顺序排序存储至变量下文称body。若将发送GET请求则将URL参数进行英文字母顺序排序后存储至变量下文称query例如URL参数为server=cn_gf01&role_id=114514191则结果为role_id=222681079&server=cn_gf01。若不需要传递数据或URL参数则为空字符串。
  4. 格式化字符串:salt={salt值}&t={第1步的结果}&r={第2步的结果}&b={第3步的body}&q={第3步的query}
  5. 将第4步的结果进行UTF-8编码再进行MD5编码。
  6. 格式化字符串:{第1步的结果},{第2步的结果},{第5步的结果}

Python

import time
import random
from hashlib import md5
# JSON模块用于处理POST请求的JSON数据
# import json

# 将要使用的salt此为4X salt
salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs"
# body和query一般来说不会同时存在
# 可以使用json库的dumps函数将对象转为JSON字符串
# body = json.dumps({"role": "123456789"}, sort_keys=True)
body = '{"role": "123456789"}'
# 可以使用urllib中的parse库的urlparse函数传入URL得到返回值中的query字段。
# 将其转为列表通过str.split("&")通过sorted函数来排序再用"&".join来将其转为最终值
query = "&".join(sorted("server=cn_gf01&role_id=123456789".split("&")))

t = int(time.time())
r = random.randint(100000, 200000)
if r == 100000:
  r = 642367
# 也可以直接用更简单粗暴的方法
# r = random.randint(100001, 200000)
main = f"salt={salt}&t={t}&r={r}&b={body}&q={query}"
ds = md5(main.encode(encoding='UTF-8')).hexdigest()

final = f"{t},{r},{ds}" # 最终结果

JavaScript

import md5 from 'md5'

const salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs"
// body和query一般来说不会同时存在
// 可以使用内置的JSON.stringify函数将对象或数组转换为JSON字符串
// const body = JSON.stringify({role: "123456789"})
const body = '{"role": "123456789"}'
// 需要对URL参数进行排序
const query = "server=cn_gf01&role_id=123456789".split('&').sort().join('&')

const t = Math.floor(Date.now() / 1000)
let r = Math.floor(Math.random() * 100001 + 100000)
if (r == 100000) {
  r = 642367
}
// const r = Math.floor(Math.random() * 100001 + 100001)

const main = `salt=${salt}&t=${t}&r=${r}&b=${body}&q=${query}`
const ds = md5(main)

const final = `${t},${r},${ds}` // 最终结果

DS1

在请求头中的x-rpc-client_type5时使用。

整体思路:

  1. 获取当前的Unix时间戳整数
  2. 在大写字母、小写字母、数字中随机抽出6个。
  3. 格式化字符串:salt={salt值}&t={第1步的结果}&r={第2步的结果}
  4. 将第3步的结果进行UTF-8编码再进行MD5编码。
  5. 格式化字符串:{第1步的结果},{第2步的结果},{第4步的结果}

Python

import time
import random
from hashlib import md5


lettersAndNumbers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'

# 将要使用的salt此为2.35.2版本的K2 salt。
salt = "ZSHlXeQUBis52qD1kEgKt5lUYed4b7Bb"

t = int(time.time())
r = "".join(random.choices(lettersAndNumbers, k=6))
main = f"salt={salt}&t={t}&r={r}"
ds = md5(main.encode(encoding='UTF-8')).hexdigest()

final = f"{t},{r},{ds}" # 最终结果。

JavaScript

import md5 from 'md5'

const salt = "ZSHlXeQUBis52qD1kEgKt5lUYed4b7Bb"
const lettersAndNumbers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'

const t = Math.floor(Date.now() / 1000)
let r = ""
for (let i; i < 6; i++) {
  r += lettersAndNumbers[Math.floor(Math.random() * lettersAndNumbers.length)]
}

const main = `salt=${salt}&t=${t}&r=${r}`
const ds = md5(main)

const final = `${t},${r},${ds}` // 最终结果

一些API例如文章点赞、签到和获取用户的游戏账号信息等API需要登录账号则表示为请求头Cookie的形式。

需要验证Cookie的API会进行标注。

若API无需登录账号就不需要设置Cookie。

API的Cookie标识格式

像SToken只有1种字段值或是像LToken2种字段值都被兼容则只标识Cookie名。

> _需要验证Cookie_
> 
> SToken

若像Account ID有多种字段值将会在其之后标识需要的字段名。若有多个兼容的字段或有多个值相同的字段使用“、”分隔。

> _需要验证Cookie_
> 
> Account ID`account_id`、`account_id_v2`

需要哪些Cookie取决于以下因素

  • API是否要求登录账号。
  • x-rpc-client_type的不同值。
  • 一些API要求特殊的Cookie。

LToken

ltoken_v2ltoken

ltoken_v2ltoken并不总是使用相同的值多用于查询用户的游戏账号信息。其被命名为“LTokenV1”与“LTokenV2”。

ltoken_v2必须与ltmid_v2一起使用,ltoken必须与ltuid一起使用。

ltoken_v2的开头通常带有“v2_”字样且长度较长ltoken的长度则较短。

在修改米游社账号的密码后将会发生变化。

SToken

stoken

stoken多用于在米游社内的操作。

必须与mid一起使用。

在修改米游社账号的密码后将会发生变化。

MiHoYo ID

分为与LToken一起使用的ltmid_v2,和与SToken一起使用的mid。其被命名为“STokenV1”与“STokenV2”。

ltmid_v2mid的值是相同,对应一个账号。

Account ID

account_id_v2account_idlogin_uidltuidltuid_v2stuid

UID即米游社UID。这个Cookie不是必须传递的。

Login Ticket

login_ticket

login_ticket是米游社的登录凭证,可用于获取STokenLToken

通常在米游社通行证中登录获得。

有效期为30分钟。

分为cookie_tokencookie_token_v2

cookie_tokencookie_token_v2的值不相同。

Action Ticket

Action Ticket通常不在Cookie中使用而是作为URL参数或请求体的一部分传递类似于凭证。

Action Ticket通常用于米游社内的部分网页操作。

Auth Key

Auth Key通常不在Cookie中使用而是作为URL参数或请求体的一部分传递类似于凭证。

Auth Key通常用于米游社的联系客服页面。

Game Token

game_token

game_token为游戏登录凭证通常用于获取其它Token。

Hk4e Token

e_hk4e_token

e_hk4e_token为米游社账号的《原神》账号标识,通常可以在《原神》的网页活动中见到。