├── .gitignore ├── .assetsignore ├── wrangler.jsonc ├── readme.md ├── api.php ├── workers.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .wrangler 2 | node_modules -------------------------------------------------------------------------------- /.assetsignore: -------------------------------------------------------------------------------- 1 | api.php 2 | readme.md 3 | workers.js 4 | wrangler.jsonc 5 | .wrangler/ 6 | node_modules/ 7 | .gitignore -------------------------------------------------------------------------------- /wrangler.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-bduss", 3 | "compatibility_date": "2025-04-01", 4 | "main": "./workers.js", 5 | "placement": { 6 | "mode": "smart" 7 | }, 8 | // "vars": { 9 | // "REFERER": "*", 10 | // "ROUTER": "" 11 | // }, 12 | "assets": { 13 | "directory": "./", 14 | "binding": "ASSETS", 15 | "html_handling": "drop-trailing-slash" 16 | } 17 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # BDUSS获取 2 | 3 | ## workers 后端 4 | 5 | 在 新建一个workers,将 `workers.js` 的内容复制粘贴就完成了 6 | 7 | ### 环境变量 8 | 9 | | 变量名 | 默认值 | 格式 | 备注 | 10 | | :-------- | :----- | :--------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | 11 | | `REFERER` | `*` | `http://example.com/\|https://example.com/` | 不需要路径 | 12 | | `ROUTER` | `/api` | `http://example.com/\|https://example.com/path/\|https://example.com/` | 即触发路由的部分(建议将 设置 -> 触发器 -> 路由 列表里面的都放进去,注意要去掉正则表达式)。默认值**没有**后面斜杠 | 13 | 14 | 可以取消掉 `wrangler.jsonc` 的键 `vars` 的注释来设置环境变量 15 | 16 | ### Wrangler 17 | 18 | ```sh 19 | # 本地调试 20 | npx wrangler dev -l 21 | 22 | # 发布 23 | npx wrangler publish 24 | ``` 25 | 26 | 更多配置请查看 Cloudflare 的相关文档 27 | 28 | ## ~~php 后端~~ 29 | 30 | - 可以设置 `$origin` 31 | 32 | 精力有限,不再维护 PHP 版后端,源码锁定 33 | 34 | ## 示例 35 | 36 | `/index.html` 是演示站 的源码,要直接使用需要修改或删除下述几项 37 | 38 | - api 地址,搜索 `!!! DEPLOY YOUR OWN API ENDPOINT !!!` 找到夹在中间的变量修改值即可,演示站后端已开访问校验,请尽量自行部署后端 39 | - 如果直接用 wrangler 或者通过 GitHub 部署到 workers 的可以无视 40 | 41 | ## 环境要求 42 | 43 | 仅限 PHP 版有要求,workers 版直接部署 44 | 45 | ```txt 46 | php 7.x 47 | php-curl 48 | ``` 49 | 50 | ## 关于回调与stoken 51 | 52 | - 请将回调链接进行`base64`/`base64url`编码后以`hash`形式添加到连接到网站的链接中 53 | - 默认不提供`stoken`,如果需要请在`query`/`hash`的`stoken_type`中体现,至于这个`stoken_type`的可用值请自行寻找 54 | - `query` 和 `hash` 同时存在时,将合并数据,返回模式取决于 `stoken_type` 所处位置,两边同时存在的数据将以 `query` 的为准 55 | 56 | 参考格式 57 | 58 | ```javascript 59 | // query 回调带 stoken 60 | // https://bduss.nest.moe/#/aHR0cHM6Ly9leGFtcGxlLmNvbS8/c3Rva2VuX3R5cGU9dGI= 61 | // https://bduss.nest.moe/#/aHR0cHM6Ly9leGFtcGxlLmNvbS8_c3Rva2VuX3R5cGU9dGI 62 | "https://bduss.nest.moe/#/" + btoa("https://example.com/?stoken_type=tb")/ 63 | // https://example.com/?stoken_type=tb&bduss=...&stoken=... 64 | 65 | // hash 回调带 stoken 66 | // https://bduss.nest.moe/#/aHR0cHM6Ly9leGFtcGxlLmNvbS8jL3N0b2tlbl90eXBlPXRi 67 | "https://bduss.nest.moe/#/" + btoa("https://example.com/#/stoken_type=tb") 68 | // https://example.com/#/stoken_type=tb&bduss=...&stoken=... 69 | 70 | // query 回调不带 stoken 71 | // https://bduss.nest.moe/#/aHR0cHM6Ly9leGFtcGxlLmNvbS8 72 | "https://bduss.nest.moe/#/" + btoa("https://example.com/") 73 | // https://example.com/?stoken_type=tb&bduss=... 74 | ``` 75 | 76 | ## 其他 77 | 78 | 关于本项目原理可以参考 [扫码登录百度获取BDUSS](https://blog.nest.moe/2018/07/17/scan-qrcode-to-fetch-bduss/) 79 | -------------------------------------------------------------------------------- /api.php: -------------------------------------------------------------------------------- 1 | -1, "msg" => "Forbidden", "data" => []]; 14 | if (($AllowedReferrer === "" || preg_match('/(^|.)(' . $AllowedReferrer . ')$/', $_SERVER["HTTP_HOST"])) && isset($get["m"])) { 15 | switch($get["m"]){ 16 | case "getqrcode": 17 | $r["data"] = getBDUSS::getqrcode(); 18 | if($r["data"]["sign"]){ 19 | $r["errno"] = 0; 20 | $r["msg"] = "Success"; 21 | } 22 | break; 23 | case "getbduss": 24 | if(isset($get["sign"]) && $get["sign"] != ""){ 25 | $r["data"] = getBDUSS::get_real_bduss($get["sign"], isset($get["full"])); 26 | if($r["data"]){ 27 | $r["errno"] = 0; 28 | $r["msg"] = "Success"; 29 | } 30 | } else { 31 | $r["msg"] = "Invalid QR Code or timeout"; 32 | } 33 | break; 34 | } 35 | } 36 | echo json_encode($r, JSON_UNESCAPED_UNICODE); 37 | 38 | class getBDUSS{ 39 | private static function scurl (string $url = "localhost", int $timeout = 60) :string { 40 | $ch = curl_init(); 41 | curl_setopt($ch,CURLOPT_URL, $url); 42 | curl_setopt($ch,CURLOPT_USERAGENT,'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'); 43 | curl_setopt($ch,CURLOPT_TIMEOUT, $timeout); 44 | curl_setopt($ch,CURLOPT_RETURNTRANSFER, true); 45 | return curl_exec($ch); 46 | } 47 | public static function getqrcode() :array { 48 | $resp = ["sign" => null, "imgurl" => null]; 49 | $get_qrcode = json_decode(self::scurl("https://passport.baidu.com/v2/api/getqrcode?lp=pc"), true); 50 | if(isset($get_qrcode["imgurl"]) && isset($get_qrcode["sign"])){ 51 | $resp = ["sign" => $get_qrcode["sign"], "imgurl" => $get_qrcode["imgurl"]]; 52 | } 53 | return $resp; 54 | } 55 | public static function get_real_bduss(string $sign, bool $full) :array{ 56 | //status code 57 | //errno不等于0或1时需要要求更换二维码及sign 58 | //-1 更换二维码 59 | //0 进入下一步 60 | //1 无需操作 61 | //2 已确认 62 | $r = ["status" => 1, "bduss" => "", "msg" => "", "fullmode" => false]; 63 | $response = self::scurl("https://passport.baidu.com/channel/unicast?channel_id={$sign}&callback=", 35); 64 | if ($response) { 65 | $responseParse = json_decode(str_replace(array("(",")"),'',$response),true); 66 | if(!$responseParse["errno"]){ 67 | $channel_v = json_decode($responseParse["channel_v"],true); 68 | if($channel_v["status"]){ 69 | $r["status"] = 0; 70 | $r["msg"] = "Continue"; 71 | }else{ 72 | $s_bduss = json_decode(preg_replace("/'([^'']+)'/", '"$1"', str_replace("\\&", "&", self::scurl('https://passport.baidu.com/v3/login/main/qrbdusslogin?bduss='.$channel_v["v"], 10))), true); 73 | if ($s_bduss && $s_bduss["code"] === "110000") { 74 | $r["status"] = 2; 75 | $r["msg"] = "Success"; 76 | $r["bduss"] = $s_bduss["data"]["session"]["bduss"]; 77 | if ($full) { 78 | $r["fullmode"] = true; 79 | $fullModeData = []; 80 | $fullModeData["ptoken"] = $s_bduss["data"]["session"]["ptoken"]; 81 | $fullModeData["stoken"] = $s_bduss["data"]["session"]["stoken"]; 82 | $fullModeData["ubi"] = $s_bduss["data"]["session"]["ubi"]; 83 | $fullModeData["hao123Param"] = $s_bduss["data"]["hao123Param"]; 84 | $fullModeData["username"] = $s_bduss["data"]["user"]["username"]; 85 | $fullModeData["userId"] = $s_bduss["data"]["user"]["userId"]; 86 | $fullModeData["portraitSign"] = $s_bduss["data"]["user"]["portraitSign"]; 87 | $fullModeData["displayName"] = $s_bduss["data"]["user"]["displayName"]; 88 | $fullModeData["stokenList"] = self::parseStoken($s_bduss["data"]["session"]["stokenList"]); 89 | $r["data"] = $fullModeData; 90 | } 91 | } 92 | } 93 | }else{ 94 | $r["status"] = $responseParse["errno"]; 95 | } 96 | }else{ 97 | $r["status"] = -1; 98 | $r["msg"] = "Invalid QR Code"; 99 | } 100 | return $r; 101 | } 102 | public static function parseStoken (string $stokenList) { 103 | $tmpStokenList = []; 104 | foreach (json_decode(str_replace(""", '"', $stokenList), true) as $stoken) { 105 | preg_match("/([\w]+)#(.*)/", $stoken, $tmpStoken); 106 | $tmpStokenList[$tmpStoken[1]] = $tmpStoken[2]; 107 | } 108 | return $tmpStokenList; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /workers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const responseInit = (referrer = '*') => ({ 3 | status: 403, 4 | headers: { 5 | 'content-type': 'application/json;charset=UTF-8', 6 | 'access-control-allow-origin': referrer, 7 | 'access-control-allow-methods': 'GET', 8 | 'X-XSS-Protection': '1; mode=block', 9 | 'X-Frame-Options': 'sameorigin', 10 | }, 11 | }) 12 | 13 | const requestHeaders = new Headers({ 14 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", 15 | }) 16 | 17 | // const callback = ''//"get_object_value_" + Date.now() 18 | 19 | async function switchRouter(request, env) { 20 | const parseUrl = new URL(request.url) 21 | const _searchParams = parseUrl.searchParams 22 | let resp = { 23 | "errno": -1, 24 | "msg": "Forbidden", 25 | "data": [] 26 | } 27 | 28 | // referrer 29 | let referrer = env.REFERER ? env.REFERER : '*' 30 | if (referrer !== '*') { 31 | referrer = referrer.split('|').find(x => x.trim() === request.headers.get('referer')) 32 | } 33 | 34 | const respInit = responseInit(referrer) 35 | if (referrer === '*' || referrer !== undefined) { 36 | respInit.status = 200 37 | switch (_searchParams.get("m")) { 38 | case "getqrcode": 39 | resp.data = await getqrcode() 40 | if (resp.data.sign) { 41 | resp.errno = 0 42 | resp.msg = "Success" 43 | } 44 | break 45 | case "getbduss": 46 | resp.data = await getBduss(_searchParams.get("sign"), (_searchParams.get("full") === null ? false : true)) 47 | if (resp.data.status >= 0 && resp.data.status <= 2) { 48 | resp.errno = 0 49 | resp.msg = "Success" 50 | } else { 51 | resp.msg = "Invalid QR Code or timeout" 52 | } 53 | break 54 | } 55 | } 56 | 57 | return new Response(JSON.stringify(resp), respInit) 58 | } 59 | 60 | async function getqrcode() { 61 | const response = await (await fetch("https://passport.baidu.com/v2/api/getqrcode?lp=pc", { headers: requestHeaders })).json() 62 | return { sign: response.sign, imgurl: response.imgurl } 63 | } 64 | 65 | async function getBduss(sign, full = false) { 66 | let resp = { status: 1, bduss: "", msg: "", fullmode: false } 67 | let response = await (await fetch("https://passport.baidu.com/channel/unicast?channel_id=" + sign + "&callback=a", { headers: requestHeaders })).text() 68 | if (response) { 69 | const errno = parseInt(/"errno":([\-0-9]+)(?:,|})/.exec(response)[1]) 70 | if (errno === 1) { 71 | resp.status = 1 72 | } else if (errno === 0) { 73 | const channel_v = JSON.parse(/"channel_v":"(.*)"}\)/.exec(response)[1].replace(/\\/gm, '')) 74 | if (channel_v.status) { 75 | resp.status = 0 76 | resp.msg = "Continue" 77 | } else { 78 | const userData = await JSON.parse(((await (await fetch('https://passport.baidu.com/v3/login/main/qrbdusslogin?bduss=' + channel_v.v, { headers: requestHeaders })).text()).replace(/'([^'']+)'/gm, `"$1"`)).replace(/\\&/gm, "&")) 79 | 80 | if (userData && userData.code === "110000") { 81 | resp.status = 2 82 | resp.msg = "Success" 83 | resp.bduss = userData.data.session.bduss 84 | if (full) { 85 | resp.fullmode = true 86 | resp.data = { 87 | ptoken: userData.data.session.ptoken, 88 | stoken: userData.data.session.stoken, 89 | ubi: userData.data.session.ubi, 90 | hao123Param: userData.data.hao123Param, 91 | username: userData.data.user.username, 92 | userId: userData.data.user.userId, 93 | portraitSign: userData.data.user.portraitSign, 94 | displayName: userData.data.user.displayName, 95 | stokenList: parseStoken(userData.data.session.stokenList) 96 | } 97 | } 98 | } 99 | } 100 | } else { 101 | resp.status = errno 102 | } 103 | } else { 104 | resp.status = -1 105 | resp.msg = "Invalid QR Code" 106 | } 107 | return resp 108 | } 109 | 110 | function parseStoken(stokenList) { 111 | return Object.fromEntries(JSON.parse(stokenList.replace(/"/gm, '"')).map(x => x.split('#'))) 112 | } 113 | 114 | // function callbackfunc(callback_str) { 115 | // return Function(`const ${callback} = (obj) => obj; return ${callback_str}`)() 116 | // } 117 | 118 | export default { 119 | async fetch(request, env) { 120 | const parseUrl = new URL(request.url) 121 | 122 | // router 123 | let router = env.ROUTER ? env.ROUTER : '' 124 | if (router !== '') { 125 | router = router.split('|').some(x => { 126 | try { 127 | const parsedRouter = new URL(x.trim()) 128 | return parseUrl.host === parsedRouter.host && parseUrl.pathname === parsedRouter.pathname 129 | } catch { 130 | return false 131 | } 132 | }) 133 | } else { 134 | router = parseUrl.pathname === '/api' 135 | } 136 | 137 | if (router) { 138 | // TODO: Add your custom /api/* logic here. 139 | return await switchRouter(request, env) 140 | } 141 | // Otherwise, serve the static assets. 142 | // Without this, the Worker will error and no assets will be served. 143 | return env.ASSETS.fetch(request) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 扫码登录获取BDUSS 13 | 44 | 115 | 116 | 117 | 118 |
119 |

120 | 警告: 将跳转到: localhost 122 |

123 |
124 |
125 | 127 | 128 | 130 | 131 | 132 |
133 | 134 |
135 | qrcode-success 138 |

扫描成功
请确认登录

139 |
140 | 141 |
142 | QRcode 143 |
144 | 145 |
146 | 148 | 149 | 150 |
151 | 152 |
153 |
154 |
155 |

5

156 |
157 |
158 |

BDUSS获取成功

159 |
160 |

161 |

162 |
163 |
164 |
165 | 166 |
167 |
168 | 169 |
170 |

BDUSS获取失败

171 |
172 | 173 |
174 |
175 |
176 |
177 |

提示

178 |
    179 |
  • 可直接点击 "网页授权" 并在新打开页授权,无需客户端扫码
  • 180 |
  • 打开百度页面时请使用移动设备或将浏览器 User-Agent 设置为移动设备的 User-Agent
  • 181 |
  • 点击 “更多” 将会获得更多信息,否则只会获取 BDUSS
  • 182 |
  • 前往本页 GitHub 仓库 获得更多信息
  • 183 |
184 |
185 |
Created by BANKA 186 | with ♥
187 |
188 | 433 | 434 | 435 | --------------------------------------------------------------------------------