STUDIO · SPEC SHEET · UNIT S9
飞书 OAuth 接入:十次摔跤复盘
定位:内网工具最难的一关不是功能,是「让人进来」
给公司内网做 AI 工具,业务功能再顺,第一关永远是登录。这套品类管理平台深度集成了飞书生态:数据在多维表格、消息走飞书、身份也用飞书 OAuth。理想中「点一下就进来」的登录,实际落地摔了十跤。这一篇把它们全部记下来——每一跤单看都小,连起来就是一张「企业身份接入」的完整避坑图。
十跤清单
| # | 现象 | 根因 | 修法 |
|---|---|---|---|
| 1 | 登录页出现「用户名密码界面」,用户没有飞书账号可填 | 发起的 OAuth 授权 scope 缺失,静默降级到错误页 | 补齐 scope,callback 逐项容错 |
| 2 | 提示「app_id 请求不合法」 | 环境变量没进构建产物,构建期被内联成 undefined | 密钥改为函数取值 + 兜底,不依赖构建期内联 |
| 3 | 登录后卡死转圈 | 回调处理不容错,refresh 失败直接 500 | 失败统一 302 回登录页,不挂死 |
| 4 | 登录成功但页面不显示用户名 | 用户名 cookie 误加了 HttpOnly | 前端要读的字段单独放非 HttpOnly cookie |
| 5 | 会话偶尔 401 | 刷新令牌端点 404——飞书 v3 没有 PUT refresh,端点名用错 | 对齐官方 refresh_access_token,补回归测试 |
| 6 | 用到一半被踢出 | user_token 两小时过期,且无预判 | 回调记录过期时间戳,到期前主动刷新 |
| 7 | 登录死循环 | 无效 session 被送去 restore-session,restore 又失败再回登录 | 无效一律干净地重走 login,不留中间态 |
| 8 | 扫码后要授权两次 | 登录与业务各发起一次 OAuth,scope 不一致 | 统一入口、合并 scope,一次授权 |
| 9 | 用户被授权页吓到不敢点 | 授权列表一大串,没解释用途 | 授权页同步展示说明:哪些数据被用来做什么 |
| 10 | 文档所有者查不到 | 跨租户文档只有「互联网可访问」权限,常规接口读不到 owner | 换用文档元数据接口 + 权限降级展示「未知所有者」 |
三条主线教训
第一,错误要么修好要么优雅退出,绝不停在中间。第 3、7 跤同源:中间态(半登录、待恢复)是死循环的温床。最终约定是任何鉴权异常都清干净 cookie 重走 login,宁可让用户多点一次,不让页面挂死。
第二,令牌是有保质期的资产。过期时间在回调时就要记下来(第 6 跤),刷新接口要先用回归测试钉死(第 5 跤)。把「快过期」当成常态路径设计,过期就是一次静默续约,而不是一次事故。
第三,授权页是产品界面,不是技术手续。第 8、9 跤都在提醒:用户看到的是一串权限勾选,他没有义务理解 scope 这个词。把「为什么需要这些权限」写在同一屏,授权完成率和使用体验是直接挂钩的。
价值与可复用方法
回看这十跤,八跤与飞书本身无关:错误中间态、构建期内联、cookie 属性、令牌生命周期——全是通用的 Web 身份工程。换 AnyAuth、钉钉、企业微信,清单照样成立。
可复用的最小集:统一登录入口与 scope、回调逐项容错且失败必清态、过期时间落库主动续约、授权页写人话。四条做完,登录就从「最容易出事的一关」变成「没人再提起的一关」。