资讯详情

资讯详情

华为账号一键登录头像昵称缺失排查:Scope与授权码消费冲突解析

这个系列写到第四十一篇这周的问题来自一个已经上线的鸿蒙应用。现象很典型用户用华为账号一键登录成功服务端 session 里也拿到了匿名手机号但 App 个人中心的头像昵称一直是空白。运营盯了三天我一开始怀疑是前端缓存没落库可把授权回调的原始数据完整打出来之后才发现问题出在授权 Scope 和授权码的消费模型上——匿名手机号和头像昵称压根不是同一道授权链路里顺带返回的两者之间有冲突。这篇就把整个定位过程和解决方案完整写出来。内容包括华为账号一键登录的授权语义拆解、Authorization Code 的单次消费模型、三种可以落地的解决组合以及我在真机调试中踩过的几个坑。无论你是刚接华为账号服务还是已经上线但遇到类似数据缺失问题这篇都可以直接作为排查手册来用。1. 匿名手机号与头像昵称一个授权码背后的两种授权语义1.1 一键登录授权结果里到底有什么华为账号一键登录在鸿蒙端的交互很轻用户点击登录按钮系统拉起华为账号授权页页面上通常展示华为账号的头像和掩码手机号比如 138****8000用户确认后应用拿到一个授权结果。这个结果里核心内容分两块。一块是 Authorization Code授权码要传给应用服务端服务端拿它向华为账号服务交换 Access Token另一块是 AuthAccount 对象里面直接携带了这个用户在华为账号侧的公开信息包括 OpenID、UnionID以及授权范围内的字段比如 displayName昵称、avatarUri头像地址、phoneNumber手机号。很多开发者的第一反应是回调里既然有 AuthAccount那我直接从里面取头像昵称和手机号不就行了。实际没那么简单回调对象里带不带头像昵称完全取决于你发起授权时传进去的 Scope而手机号是不是能被服务端真正读取又取决于另一个维度的权限申请。这两个维度一旦没对齐就会出现数据字段为空的诡异现象。1.2 匿名手机号看起来像手机号实际是脱敏标识匿名手机号是华为账号服务里一个比较有特点的能力。业务场景通常是这样应用需要用一个手机号形态的标识来做账号维度关联但直接存储真实手机号会带来很高的隐私合规成本一旦数据库泄露真实号码就全暴露了。匿名手机号的方案是把真实手机号做固定脱敏变换返回一个仍然符合手机号格式的字符串。业务侧拿到它可以用它来做本应用内的账号识别、去重和状态标记但它不是用户的真实触达号码。匿名手机号有两条特性很关键一是同一用户对同一应用多次授权返回的匿名手机号是一致的可以稳定用于业务识别二是它不能用于发送短信、外呼或任何需要真实号码触达用户的场景隐私政策里也不能写用于营销联系。打个比方匿名手机号就像食堂饭卡上的卡号。你能拿卡号去窗口取餐、查消费记录但卡号本身不直接对应到身份证信息。它保护了真实身份同时保留了业务上的唯一性。1.3 头像昵称走的是另一条授权线头像昵称在华为账号服务里属于个人资料授权项对应 profile 这个 Scope。用户在华为账号授权页上会看到类似允许应用获取您的昵称、头像的提示文案只有这个授权项被用户同意AuthAccount 里才会返回 displayName 和 avatarUri。这里就是第一个冲突口一键登录授权页默认的 Scope 组合通常只包含 openid 和 phone并不会自动带上 profile。所以即便一键登录流程走得非常顺利匿名手机号也正常返回了displayName 和 avatarUri 依然是 null。这不是华为在故意刁难而是权限模型的设计——每一个敏感字段都要有清晰的授权记录不能因为一次登录就附带把所有个人资料都拿走。1.4 冲突的直观定义把前面两节拼在一起冲突的本质就很清楚了第一层冲突是 Scope 配置冲突。一键登录如果只配了 phone头像昵称必然为空把 profile 加进去授权页会展示额外的授权项部分团队怕影响登录转化率又不想加。第二层冲突是 Authorization Code 消费冲突。即便 Scope 都配了授权码也只能被消费一次。很多团队习惯把授权码传给服务端服务端拿它换 Access Token 后去取手机号另一套业务逻辑里又拿着同一个授权码去取用户资料第二个请求必然失败或者第一个请求就把授权码标记为已使用连带后面的所有请求全部报错。所以你在网上看到的手机号能拿到但头像昵称为空头像昵称能拿到但手机号为空两个都拿不到这类问题基本都是这两个冲突点在不同项目里的排列组合。2. 问题定位链路从用户反馈到 Authorization Code 日志2.1 先给线上问题分类排查这种问题第一步不是改代码而是先判断问题范围。我拿到运营反馈后第一件事是拉了一组用户日志按头像昵称为空和手机号为空两个维度做交叉统计。如果所有用户都拿不到头像昵称那基本可以断定是 Scope 配置或者授权请求参数的问题跟用户账号本身没关系。如果是部分用户拿不到就要怀疑授权行为本身——用户可能拒绝过个人资料授权或者授权页根本没有展示这个授权项。还有一种容易被忽略的情况头像和昵称都拿到了但是手机号为空。这类问题通常集中在服务端权限审核上比如应用在华为开发者平台没有通过手机号能力的审核授权页不会发起手机号授权。先分类再往对应分支排查能省掉大量无效动作。2.2 检查授权请求 Scope分类之后我直接打开了发起授权登录的那段代码。当时工程里的 Scope 组合大概长这样// 排查前的不完整写法 const scopes [openid, phone]看到这行基本就明白一半了。头像昵称没有是因为授权请求里压根没有 profile。正确的写法应该是// 排查后的完整写法 const scopes [openid, profile, phone]这里有一个很隐蔽的细节要知道Scope 修改之后不是立即生效到所有存量用户。用户手机上的华为账号授权记录是按旧 Scope 存的即使你把新 Scope 发到线上老用户再次点击一键登录授权页可能还是按旧授权项弹出来头像昵称依旧拿不到。必须让用户撤销授权后重新走一次完整授权流程新 Scope 才会生效。这也是为什么有些团队线上改了代码反馈却还是没修好的原因。2.3 打印授权回调的完整数据Scope 配置确认之后下一步是看回调数据。排查这类问题最忌讳只打印个别字段我建议把整个 authResult 原样打印出来关键字段尽量打成一行日志方便对比。Authorization Code: eyJhbGciOiJSUzI1NiIs... OpenID: xxxxx UnionID: yyyyy DisplayName: null AvatarUri: null PhoneNumber: 138****8000这份日志里有几个关键信息PhoneNumber 回来了说明手机号授权链路是通的UnionID 有值说明用户维度没问题DisplayName 和 AvatarUri 是 null进一步印证了授权请求没有带 profile或者用户没有同意个人资料授权。如果 DisplayName 和 AvatarUri 有值但 PhoneNumber 是空的那问题就从 Scope 转移到了服务端权限申请和授权码消费环节。两边的排查路径完全不同打印完整回调能帮你快速定位。2.4 顺着服务端日志找 Code 消费记录前端回调没问题之后如果数据还是缺就要检查服务端。我见过最典型的错误是服务端框架里封装了一个根据授权码刷新用户信息的工具函数这个函数内部拿到 Authorization Code 之后先换 Access Token再拿 Access Token 去取用户资料。问题在于如果业务代码里同时还有一个根据授权码获取手机号的函数两个函数都依赖前端传来的同一个 Authorization Code那么谁先执行谁就成功后执行的人拿到的就是code has been used之类的错误。去服务端日志里搜这个 Authorization Code你会发现它在短时间内被消费了两次甚至是三次。如果服务端确实有这个二次消费问题光改前端没有任何用处。正确姿势是约定清楚Authorization Code 进到服务端之后只允许在一个入口函数里被消费一次换到的 Access Token 统一放入上下文后续取手机号、取用户资料全部用 Access Token不要再碰原始授权码。3. 冲突根源Code 单次消费与 Scope 权限割裂3.1 授权码是一次性取货券理解 Authorization Code 的单次消费模型需要放下写业务代码的习惯。举个例子你在商场抽奖抽到一张取货券券上写明了能领取的商品。你拿着券到柜台店员扫码核销之后这张券就作废了哪怕你反悔了想换一个商品也不可能再用这张券。华为账号服务的 Authorization Code 就是这个逻辑。它从授权回调那一刻开始就只有一个使命提交给服务端换取 Access Token。不管换取成功还是失败这个 Code 基本就算是废了。后续所有业务数据请求全部应该走 Access Token。提示授权码一旦进入换取 Access Token的流程立即失效。后续任何业务接口的调用都应该基于 Access Token 完成不能继续拿原始授权码去调别的接口。很多团队习惯把 Authorization Code 和 Access Token 混在一起传甚至把它们一起打进日志排查问题的时候看到日志里有个 code 字段就直接复制出来又做一次请求这是很危险的调试行为。3.2 Scope 决定授权页长什么样Scope 是 OAuth 体系里控制权限范围的参数。对用户来说Scope 直接决定了他看到手机上弹出的那个授权页长什么样、有哪些勾选项对开发者来说Scope 决定了授权回调里能拿到哪些字段。华为账号服务的 Scope 主要有三类openid 是用户维度的标识profile 是个人资料phone 是手机号。你在发起授权登录时传的 Scope 列表就是告诉华为账号服务我这个应用需要哪些数据。Scope 列表越少授权页越简洁登录转化率越高Scope 越多能拿的数据越全但用户会看到更多的授权说明对应的合规责任也更重。一键登录的场景下很多人只关心能不能快速拿到手机号Scope 里就只带 openid 和 phone。这个选择本身没毛病但如果你同时还期望回调里出现头像昵称那就属于没买菜却想做饭——授权页根本没有向用户申请过个人资料权限数据自然不会出现。3.3 控制台权限与 Scope 是两套开关还有一个我必须单独拎出来说的点Scope 只是应用侧的请求参数华为开发者平台控制台是另一套开关。以手机号能力为例即使你的代码里正确传了 phone Scope也调起了授权页用户也点了同意但如果这个应用在华为开发者平台没有通过手机号能力的审核服务端用 Access Token 调用手机号接口时依然会被拒绝。这类错误返回的提示往往不是权限不足这种直白文案而是token invalid或者access denied很容易让开发者误以为问题出在授权码上。个人资料profile相对宽松一般不需要额外审核但手机号属于敏感能力提交审核时通常需要说明使用场景、隐私政策链接、是否存储真实号码等信息。如果你申请时填的用途是身份校验华为侧更倾向让你使用匿名手机号只有确需真实号码的业务比如物流、金融核身才有机会拿到真实号码权限。3.4 头像昵称建议直接吃回调别绕道 Code根据我接入多个账号服务项目的经验头像昵称这类个人资料能直接取授权回调 AuthAccount 里的值就优先直接取。原因很简单回调里的 displayName 和 avatarUri 是华为在授权通过后同步返回的时效性最好来源最直接也不额外消耗授权码。很多团队非要把 Authorization Code 传到服务端再从服务端调一遍获取用户信息接口绕了一大圈反而把授权码的消费次数浪费在了这里。如果服务端确实需要存头像昵称用于多端同步正确做法是前端从 AuthAccount 里把这些字段取出来作为普通业务参数 POST 给服务端而不是让服务端拿着授权码再去换一遍。这样既满足了后端存储需求也不影响手机号接口的授权码配额。4. 可落地的三种解决组合按业务场景选型4.1 方案A一次授权前端吃 Profile服务端消费 Code 拿匿名手机号这个方案适合业务强依赖手机号做身份识别同时需要展示用户头像昵称的场景。核心原则一次授权授权码只交给服务端消费一次且只用于换手机号头像昵称完全由前端从回调 AuthAccount 里取。前端授权请求的 Scope 必须带上 profile 和 phone// 授权参数示例以你接入的SDK版本为准 const scopes [openid, profile, phone] const authParam new AuthParam(AuthMode.DEFAULT_AUTH, scopes, antiPhishingKey) const authResult await getIdAuthHelper().login(authParam) // 回调里的 AuthAccount 直接带头像昵称 const authAccount authResult.authAccount const displayName authAccount.displayName const avatarUri authAccount.avatarUri const unionId authAccount.unionId // authorizationCode 只负责交给服务端换手机号前端不要再用 sendToServer(authResult.authorizationCode, displayName, avatarUri, unionId)服务端只消费一次授权码// 服务端伪代码核心是授权码只用于换Token const tokenData await huaweiAccount.getAccessToken(authCode) const accessToken tokenData.access_token // 换完Token之后后续请求都用accessTokentype设为anonymous表示要匿名手机号 const phoneData await huaweiAccount.getPhoneNumber(accessToken, { type: anonymous })这个方案在前端代码上是改动最小的后端只需要保证 Authorization Code 不在多个函数之间流通。注意一个细节如果服务端后续还需要更新头像昵称可以让 App 在每次启动或登录时把最新 displayName 和 avatarUri 签个名传给服务端服务端做覆盖更新完全不需要再依赖授权码。4.2 方案B两次授权先一键登录再补全资料有的团队反馈把 profile 加到首次授权 Scope 之后授权页从一键登录的简洁模式变成了完整授权模式用户会看到多项授权说明登录转化率明显降了。如果产品上特别在意首屏转化建议用方案B。第一次授权只带 openid 和 phone完成一键登录拿到匿名手机号先把账号建起来。用户进入个人中心后再提供一个绑定华为账号资料的入口这个入口单独发起一次带 profile 的授权把头像昵称补全。// 第一次登录只用手机号 const scopesLogin [openid, phone] // 用户主动补全资料时 const scopesProfile [openid, profile]两次授权之间要用 UnionID 做关联。第一次授权回调里已经能拿到 UnionID第二次授权回调里的 UnionID 和第一次一致就说明是同一个华为账号可以把头像昵称合并到老账号记录上。这个方案里有一个隐藏风险如果用户第一次登录时用的不是华为账号登录而是匿名登录或者纯手机号验证码登录那么第二次华为账号授权拿到的 UnionID 和已有账号没有任何关联。这时候必须先判断当前账号有没有绑定过 UnionID没有的话要先做一次绑定动作不能直接拿 UnionID 去覆盖。4.3 方案C纯前端字段不引入服务端换取方案C 适合对服务端复杂度极度敏感的小应用。如果你的业务不依赖服务端存储手机号进行二次识别只是希望在 App 内展示用户头像昵称那完全可以直接把 AuthAccount 里的 displayName 和 avatarUri 存到本地连授权码的传递都可以省略大半。这种情况下授权请求 Scope 带 openid 和 profile 就够了phone Scope 不传就不会触发手机号授权授权码由系统正常返回服务端如果不需要做业务关联甚至可以不接收授权码。方案C 的优点是链路最短、排查最容易缺点是你没有匿名手机号这个字段账号体系只能依赖 OpenID 或 UnionID。如果后续业务扩展需要手机号身份识别还是要回到方案A或方案B的改造路径上。4.4 三种方案对比与决策建议对比项方案A一次授权前后端分工方案B多次授权补全方案C纯前端字段授权次数1 次2 次1 次头像昵称来源授权回调 AuthAccount第二次授权回调授权回调 AuthAccount匿名手机号来源服务端用 Code 换取第一次授权后用 Code 换取不依赖适合场景业务强依赖手机号做身份识别一键登录转化优先资料后置纯展示无后端需求工程复杂度中高低风险点Code 只能消费一次需做好 UnionID 关联字段可能为空需兜底我个人的建议是绝大多数需要账号体系的应用优先走方案A。它既保留了一次授权的高转化率又不需要把头像昵称链路绕到服务端是工程复杂度和功能完整性之间最平衡的选择。5. 登录态合并与头像昵称兜底上线前后的工程化细节5.1 用户主键选 UnionID 而不是手机号拿不到数据的问题解决之后下一个陷阱是数据存下来之后怎么用。很多团队习惯直接拿手机号当用户主键这在接入华为账号一键登录之后会埋下隐患。匿名手机号虽然对同一用户是稳定的但从业务可靠性来说它本质上是脱敏变换后的一个中间值。用户换绑手机号、华为账号策略调整、应用申请权限类型变化都可能导致匿名手机号的规则变化。一旦手机号字段变了用手机号做主键的整个账号体系就会乱掉。华为账号服务里UnionID 是跨应用不变的稳定标识OpenID 是单应用内不变的标识。服务端用户表的主键建议用 UnionID手机号最多作为辅助索引字段。5.2 老账号绑定与安全校验老用户合并是另一个常见问题。很多 App 早期有手机号验证码登录用户后来改用华为账号一键登录这时候如果只按匿名手机号去做匹配风险很大——匿名手机号毕竟是脱敏过的值用脱敏值去绑定老账号万一撞号就会串账号。我的做法是先用 UnionID 查账号查不到再看用户是否愿意绑定老手机号。绑定流程要求用户输入完整的手机号并接收验证码验证通过后把老账号和新 UnionID 做关系绑定。整个过程完全不走匿名手机号匹配安全性高很多。5.3 头像昵称为空时的体验兜底头像昵称拿不到和没设置是两回事。用户自己在华为账号里就没设置过头像昵称这是最容易被忽略的一种情况。我排查第一轮的时候一直以为是 Scope 问题结果换了个测试账号头像昵称就有了原来问题是测试账号自己没填资料。产品层面无论如何都要做兜底。昵称为空时可以展示用户手机号后四位或者直接用华为用户头像为空时用昵称首字生成纯色占位图。头像 URL 加载失败也要有重试机制有些 URL 是带时效的过期之后要允许用户重新授权刷新。5.4 缓存、刷新与隐私声明头像昵称属于低频变化数据本地缓存完全够用。建议在每次启动时用缓存的展示后台静默刷新一次用户主动进入个人中心时再触发一次完整授权刷新拿到最新头像昵称后覆盖本地缓存。另外在隐私政策里要把匿名手机号的用途写清楚。我见过一些应用把匿名手机号当真实手机号用在用户协议里写将使用您的手机号进行营销联系这是不合规的。匿名手机号的合规口径是仅用于账号标识与安全验证千万别写错。6. 真机实测与高频报错速查表6.1 实测前的账号准备清单接华为账号服务实测阶段的准备工作比想象中重要。我建议准备至少两个测试华为账号一个有头像昵称一个没有用来验证兜底逻辑。测试手机上要确保已经登录了华为账号且系统版本支持对应的一键登录能力。开发者平台那边要确认手机号能力已经提交审核并通过Scope 权限配置正确。建议再准备一个能完整查看服务端日志的后台Authorization Code 的消费记录一定要能搜到否则排查第3章说的问题时会非常痛苦。6.2 高频报错速查表下面这张表覆盖了我接入华为账号一键登录以来碰到的大部分线上问题按现象、原因、处理动作三列组织排查时可以直接对照现象可能原因处理动作一键登录成功但 displayName 为 null授权请求缺少 profile Scope或用户未同意个人资料授权Scope 中加入 profile让用户重新走授权流程AuthAccount 中 phoneNumber 为空手机号能力未在控制台申请通过或 Scope 缺少 phone控制台提交手机号能力审核Scope 带 phone服务端第二次使用同一个 Code 报错Authorization Code 只能消费一次服务端统一管理 Code 消费入口后续请求全部使用 Access TokenToken 调手机号接口提示权限不足控制台手机号权限未审核通过或 Token 的 Scope 不够检查控制台权限状态重新授权获取新 Token头像 URL 加载失败或 403头像 URL 带时效或接口存在防盗链使用回调原 URL避免二次拼串失效后引导用户重新授权刷新授权页一直不出现头像昵称授权项用户设备上保留了旧 Scope 的授权记录让用户在华为账号设置中撤销授权后重新登录6.3 几个我花了很久才想通的坑第一个坑是 Scope 修改后的生效问题。我最早改完 Scope 之后在自己手机上反复测试授权页永远是旧的一度怀疑代码没生效。后来才意识到华为账号的授权记录是按 Scope 维度缓存的必须把 App 的授权记录清除重新发起授权新 Scope 才会生效。测试之前记得先做一次撤销授权操作。第二个坑是匿名手机号和真实手机号的混淆。有阵子服务端拿到的号码看起来非常正常我就默认它是真实手机号直接拿去接短信服务结果短信根本发不出去。回头看接口文档才发现拿到的是匿名手机号。这个教训也提醒我接任何账号服务第一步应该确认自己接入的是哪个字段而不是凭字段名猜。第三个坑是日志脱敏。匿名手机号虽然脱了敏但中间段经过变换后仍有部分规则可推导。排查问题时候把完整 Authorization Code、Access Token、手机号字段打在一个日志里如果日志平台被拖库等于把完整登录链路都暴露了。我现在对账号服务全链路的日志强制脱敏Authorization Code 和 Token 只打印前几位手机号打码后才允许落盘。关于华为账号一键登录和匿名手机号、头像昵称的组合问题工程上的核心就一句话授权码只消费一次Scope 按需申请头像昵称走回调手机号走服务端。把这几条理清楚这个系列下一期遇到类似问题应该不会再折腾一周了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →