由于各种原因,登录百度获取bduss的方式越来越少了,加上某段时间百度开始限制单一机器用账号密码登录的上限,导致博主的直链解析站所依赖的bduss获取站也挂了,博主一度陷入了烦恼。
百度坑死人
所以博主研究了扫码登录
关于扫码登录
这个功能很早就有了,只是博主一直没去研究,扫码登录能获取有效期长达十年的bduss,但是除了手机百度以外,其他百度系的应用都只能调用摄像头扫码,不能在图库选择图片,这意味着用户必须有两台或以上的设备才能够使用扫码登录。
获取二维码
简单的抓包就可以发现二维码获取的链接
curl "https://passport.baidu.com/v2/api/getqrcode?lp=pc"
然后会返回一串json,反序列化后会得到 imgurl
和 sign
{
"imgurl": "passport.baidu.com/v2/api/qrcode?sign=c2db7cc9133219a586606e9468decf3e&lp=pc",
"errno": ,
"sign": "c2db7cc9133219a586606e9468decf3e",
"prompt": "登录后威马将获得百度帐号的公开信息(用户名、头像)"
}
剩下那些就不用管了 imgurl就是获取扫码登录用的二维码,而sign是对应二维码的唯一id
不获取二维码直接使用网页授权
二维码只是一个链接,所以可以拼接链接并访问该链接授权
https://wappass.baidu.com/wp/?qrlogin=&sign=c2db7cc9133219a586606e9468decf3e
2020-03-30更新:百度不再允许pc的 User-Agent 访问此链接,会提示下载手机客户端,移动设备的 User-Agent 不受影响
获取临时bduss
获得二维码时,经过简单的筛选就可以发现后台在尝试连接一个url,但只要不扫码,一段时间以后会连接超时,然后再次进行请求,周而复始……
curl "https://passport.baidu.com/channel/unicast?channel_id=c2db7cc9133219a586606e9468decf3e&callback=this_is_callback"
这里的channel_id其实就是前面获取到的sign,扫了码再请求就会有结果了,query string 里面必须要有 callback
,callback
值可为空
然后又有了这货
this_is_callback({"errno":0,"channel_v":"{\"status\":0,\"v\":\"\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\",\"u\":null}"})
双重嵌套json,嗯…… 那个v对应的键值就是临时的bduss
登录
接上面
fetch("https://passport.baidu.com/v3/login/main/qrbdusslogin?bduss=" + JSON.parse(data.channel_v).v)
简单的GET请求,不是么? 然后就可以在返回头那的set-cookie拿你想拿的东西了
心血来潮去看了一下cookie以外返回了些啥
{
"errInfo": {
"no": "0",
"msg": ""
},
"code": "110000",
"message": "",
'data': {
"u": "https:\/\/passport.baidu.com\/",
"userName": "",
"hao123Param": "******FBQUFBQUFBQUFBQU******",
"bdusssign": "",
"authtoken": "",
"session": {
"version": "v3",
"actionType": "",
"canshare": "1",
"authsid": "******",
"needvcode": "0",
"bduss": "******AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA******",
"ptoken": "******",
"stoken": "******",
"ubi": "******",
"stokenList": "[\"tb#******\",\"pp#******\",\"bp#******\",\"netdisk#******\",\"cloudforbusiness#******\",\"bdwm#******\",\"waimai#******\",\"bdwalletsdk#******\",\"baiduwalletapp#******\",\"baidugushitong#******\",\"bdbus#******\",\"fund#******\",\"nuomi#******\",\"licai#******\",\"asset#******\",\"hao123#******\",\"pcs#******\",\"dev#******\",\"fbuym#******\",\"licaiapp#******\",\"mybox#******\",\"iitnightingale#******\",\"dianying#******\",\"mall#******\",\"lv#******\",\"cmovie#******\",\"mapsafe#******\",\"im_hi#******\","ppapp#******\",\"licaient#******\",\"album#******\",\"aduqa#******\"]"
},
"user": {
"username": "******",
"weakpass": "",
"userId": "1",
"livinguname": "",
"portraitUrl": "https:\/\/himg.bdimg.com\/sys\/portrait\/item\/pp.1.******.******.jpg",
"portraitSign": "pp.1.******.******",
"displayName": "******"
}
},
"traceid": ""
}
还挺详细的,这是一个 jsonp,使用了单引号 '
以及加多了一个反斜杠 \
, 所以直接使用 JSON.parse()
或 json_decode()
会报错,因此 js 可以直接 eval
,其他语言可以预处理一遍字符串 ("是双引号 "
)
示例代码只考虑理想情况,仅供参考
// regexp
str.replace(/'([^'']+)'/gm, `"$1"`).replace(/\\&/gm, "&")
// why not Function()?
function callbackfunc(callback_str) {
const roundIndex = callback_str.indexOf('(')
if (roundIndex === -1) {
return {}
}
const callback = callback_str.slice(0, roundIndex)
return Function(`${callback ? `const ${callback} = (obj) => obj; ` : ''}return ${callback_str}`)()
}
preg_replace("/'([^'']+)'/", '"$1"', str_replace("\\&", "&", $str))
由于百度每种服务的 stoken
都不同,有一个一次性列出所有 stoken
的 stokenList
还是挺方便的
后记
本来很简单的事情研究了一早上,因为curl忘记加上RETURNTRANSFER,被自己蠢哭(逃ε=ε=ε=┏(゜ロ゜;)┛
demo在此,你们要的蓝色的东西https://bduss.nest.moe/,源码位于 github:BANKA2017/get-bduss
2020-03-30追记:我以前都写了些啥啊
2024-04-01追记:感谢评论区网友 @alsotang 提供的信息,我也改用 Function()
来执行回调,再也不用头疼正则表达式了