做鸿蒙应用开发一年多,最常被问到的就是“验证码登录怎么接”。手机号验证码虽然用户熟悉,但个人开发者要申请短信服务,资质审核、签名配置、计费问题一整套走下来非常折腾。后来我在某工具类应用里改用邮箱验证码登录,基于AGC(AppGallery Connect)的认证服务一条链路直接打通,注册、登录、账号状态管理全部复用。这篇就把完整实现过程、关键代码和我踩过的坑一起梳理出来,希望能帮你少走几周弯路。内容面向正在做HarmonyOS应用的开发者,也适合刚转鸿蒙、对AGC登录体系还不熟的同学参考。
1. 项目背景与整体设计思路
1.1 为什么选邮箱验证码而不是手机号验证码
先说我当时的实际处境:应用面向开发者工具场景,用户需要在电脑上配合使用,天然有稳定的邮箱使用习惯。产品要求登录必须低门槛,但又不能像纯匿名登录那样完全无法找回账号。对比下来,邮箱验证码登录是平衡成本和体验的最佳方案。
短信验证码的痛点很明显,个人开发者申请短信签名和模板需要企业资质或特殊报备,很多情况下根本走不通;企业开发者虽然可以申请,但每发一条短信都有成本,测试阶段尤其肉疼。邮箱验证码则由AGC云侧直接发送,不需要自建邮件服务,也基本没有额外费用,对于中小团队和个人开发者非常友好。
再从安全角度看,邮箱验证码通常配合邮件正文里的验证码数字使用,验证码短时有效,安全性完全可以接受。密码登录容易因为用户复用密码导致撞库风险,邮箱验证码是一次性凭证,即使验证码被截获,有效期也很短,风险可控。对于做工具类、内容类、企业协作类的应用,邮箱验证码是目前综合成本最低的登录方式之一。
1.2 整体登录链路与关键概念
在设计登录链路之前,必须先把AGC认证体系里的几个核心对象弄清楚,否则后面写代码会非常晕。
- 未登录用户:应用内的访客态,可以访问公开页面,但不能使用需要身份的功能。
- 当前用户:调用登录接口成功后,AGC返回的登录态对象,里面包含了UID、邮箱、头像、昵称等信息。
- ID Token:用户身份凭证,JWT格式,通常带签名,适合传给服务端做身份校验。
- Access Token:访问令牌,用于调用AGC服务端接口,有效期较短,过期后需要刷新。
- Refresh Token:刷新令牌,有效期较长,用于在Access Token过期后静默续期。
我建议你在项目里做一层登录状态的封装,而不是到处直接调用AGC接口。比如设计一个LoginManager单例,内部统一维护“当前用户”“登录状态变化回调”“Token刷新逻辑”。这样后续如果要增加手机号登录、第三方登录,都只需要替换凭证来源,上层业务不用改。
1.3 业务场景与架构约束
邮箱验证码登录不是所有场景都合适。如果你的应用面向大众消费者、用户年龄层偏年轻,很多人没有固定邮箱习惯,这时候邮箱登录的转化率可能会明显低于预期。我当时之所以坚决用这个方案,是因为目标用户本身就是开发者群体,邮箱既是登录凭证也是产品内的重要识别信息,用户没有额外记忆负担。
还有一类场景特别适合邮箱验证码登录,就是企业内部工具或测试环境。内部系统往往不需要实名手机号,但需要能追溯操作人,邮箱作为企业工号关联标识非常合适。同时,开发调试阶段用邮箱验证码可以大大减少测试账号的维护成本,不用频繁换手机号收短信。
架构上要注意一个关键点:AGC客户端的验证码登录只解决“认证”问题,不解决“业务”问题。如果你的应用有自己的服务端,必须用AGC返回的ID Token做二次校验,而不是轻信客户端传过来的用户信息。后面安全加固部分我会专门展开。
2. 控制台配置与工程接入:动手前必须先做的几件事
2.1 AGC控制台开通认证服务
在写代码之前,需要先到AGC控制台完成应用的创建和认证服务开通。步骤本身不难,但很多人会在配置文件环节出错。建议按下面顺序操作:
- 登录AGC控制台,创建一个应用,平台选择HarmonyOS。
- 在“认证服务”中确认服务状态为已开通。
- 配置邮件模板,这一步容易被忽略。AGC默认会有邮件模板,但发件人名称、邮件标题最好改成自己业务的风格,验证码邮件的送达率和打开率都跟模板质量有关系。
- 下载配置文件agconnect-services.json,后续放到工程里。
2.2 应用签名证书指纹配置
签名指纹是HarmonyOS应用接入AGC最容易踩坑的配置项。你在控制台填写的证书指纹必须和实际打包签名一致,否则客户端初始化会报签名错误,或者登录时返回鉴权失败。
这里有个细节很多人不知道:调试模式和发布模式的签名证书往往不同。如果你用的是自动签名,IDE生成的调试证书和正式上架证书指纹不一样,控制台里要分别添加。我在实际开发中会维护一个配置检查清单,记录Debug指纹和Release指纹,避免上线前临时换证书导致登录失效。
获取证书指纹的标准方式是在构建产物中查看签名信息,或者用签名工具的指纹信息。签名文件的位置通常在工程签名配置的storeFile路径下,密码信息不要提交到代码仓库。
注意:证书指纹错误最常见的报错是“应用签名校验失败”。排查时优先检查AGC控制台填写的指纹与实际打包证书是否完全一致,特别注意字母大小写和冒号分隔。
2.3 工程依赖与配置文件
依赖配置部分,不同SDK版本的写法略有差异。这里以我项目里使用的版本为例,最新版本API可能有调整,请以当前官方文档为准。
在工程的模块级build.gradle文件中配置依赖:
dependencies { implementation 'com.huawei.agconnect:agconnect-auth:1.9.1' implementation 'com.huawei.agconnect:agconnect-core:1.9.1' }同时在根目录build.gradle中配置AGC插件:
buildscript { dependencies { classpath 'com.huawei.agconnect:agcp:1.9.1' } }配置完成后,将agconnect-services.json文件拷贝到entry模块的src/main/resources/rawfile目录下,注意不要放错模块。然后重新同步Gradle项目。
这里特别想提醒:不要只把json放进去就完事,还要确认工程里正确应用了agcp插件。插件的作用是解析json配置并生成对应的初始化参数,如果没应用插件,即使json文件在工程里,初始化时也读不到配置,登录接口会一直报错。
2.4 初始化AGConnect
配置文件放好后,应用启动时需要初始化AGConnect。通常建议在Application的onCreate方法里做初始化。
import com.huawei.agconnect.AGConnectInstance; import com.huawei.agconnect.AGConnectOptionsBuilder; import com.huawei.agconnect.auth.AGConnectAuth; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); AGConnectInstance.initialize(this); AGConnectAuth.getInstance(); } }初始化完成后,可以通过AGConnectAuth.getInstance()获取认证服务单例。注意初始化不要放在子线程,否则可能出现偶发初始化失败的问题。
3. 邮箱验证码登录完整实现
3.1 发送邮箱验证码
发送验证码是整个链路的第一步,也是最需要做好用户提示的地方。AGC提供邮箱验证码发送接口,客户端传入邮箱地址,云侧负责生成验证码并发送邮件。发送频率是有限制的,控制台和SDK都会有节流策略,所以前端按钮的倒计时必须做,不要指望服务端兜底。
发送验证码的核心代码如下:
AGConnectAuth auth = AGConnectAuth.getInstance(); EmailUser emailUser = new EmailUser.Builder() .setEmail("user@example.com") .build(); EmailVerifier verifier = auth.getEmailVerifier(); verifier.sendVerifyCode(emailUser, 6, 900) .addOnSuccessListener(success -> { // 验证码发送成功,提示用户查看邮箱 }) .addOnFailureListener(e -> { // 发送失败,根据错误码提示 });这里的两个参数需要解释一下:6表示验证码数字位数,900表示验证码有效期,单位是秒。如果你不想用默认模板的6位数字,也可以自己生成业务验证码再通过邮件模板发送,但客户端直接调用SDK的发送接口更快,不用自己维护邮件发送服务。
3.2 校验验证码并完成登录
用户收到验证码后,在输入框里填入,我们需要用邮箱加验证码构造一个凭证,然后调用登录接口。这比传统的密码登录多了一步“即时凭证”的转换,逻辑上要清晰区分:验证码本身不是密码,它只是登录凭证的一次性原料。
登录代码如下:
AGConnectAuthCredential credential = EmailAuthProvider.credentialWithVerifyCode( "user@example.com", "123456" ); AGConnectAuth.getInstance().signIn(credential) .addOnSuccessListener(signInResult -> { AGConnectUser user = signInResult.getUser(); // 登录成功,可以获取用户UID、邮箱等 }) .addOnFailureListener(e -> { // 验证码错误、过期或网络异常 });如果验证码错误,SDK会返回具体的错误码。产品上要区分“验证码错误”和“验证码过期”两种提示,不要笼统提示“登录失败”,用户会非常困惑。我通常会在本地维护一个“验证码已过期”的倒计时状态,前端先拦截一部分过期场景,减少服务端错误请求。
3.3 登录态管理与会话刷新
登录成功后,AGC已经帮我们内部管理了登录态,但实际开发时不能只在内存里保存用户对象。应用被系统回收后,本地保存的登录信息需要持久化,否则每次冷启动都要重新登录。
我的做法是:登录成功后,把当前用户的关键信息(UID、邮箱、token过期时间)存入本地数据库或首选项。应用启动时先读取本地缓存,如果能读到用户信息,再调用AGC的接口恢复会话。这里要注意,恢复会话不等于登录,只是同步当前用户的登录状态。
AGConnectAuth.getInstance().getCurrentUser()如果返回的当前用户不为空,说明本地仍有登录缓存,可以继续使用。真正判断登录是否过期,要定时检查token的过期时间,并在需要访问业务接口前判断是否需要刷新。AGC的SDK会自动处理Access Token的刷新逻辑,但前提是你没有关闭自动刷新能力。我建议不要关闭,除非你有非常特殊的离线需求。
3.4 自定义登录页面与交互细节
很多应用不想用AGC默认的登录页面,需要自己做UI。这个阶段有几个交互细节值得打磨。
首先是邮箱输入框的格式校验。可以在用户输入时做基础校验,比如正则判断邮箱格式,但不要每个字符都校验,应该在用户点击发送验证码时才统一判断。格式错误的直接提示,不发请求。
其次是验证码按钮的倒计时。发送成功后,按钮进入60秒或120秒倒计时,倒计时结束后才能重新发送。倒计时状态建议放在ViewModel层,否则页面旋转或重建后状态容易丢失。我这里倒计时使用独立的状态对象,接管了页面的生命周期,效果比在Activity里临时存变量稳定得多。
还有一个小细节:用户在邮箱和验证码输入框之间切换时,体验要连贯。邮箱输入框失去焦点时,可以进行一次轻量级格式检查,但不要弹窗打断输入。验证码输入框建议设置数字键盘,并限制最大长度和输入类型,避免用户输入其他字符。
4. 踩坑记录与排查实录
4.1 验证码邮件收不到或进垃圾箱
这是最高频的问题,很多人配置好之后发现邮件迟迟不到,就开始怀疑代码画错了。我先说排查顺序:先看AGC控制台的发送记录,如果记录显示已发送,说明邮件发出去了,问题在收件方;如果记录里没有,说明接口没成功,要去看客户端错误码。
邮件进垃圾箱的常见原因有三个。一是发件人名称或邮件标题太像营销内容,被邮箱服务商拦截;二是邮件正文里的验证码图片或链接增加了垃圾邮件评分;三是发送频率过高触发风控。解决办法是把发件人名称改成产品名,邮件正文保持简洁,不要出现“中奖”“免费”等敏感词。如果用户使用企业邮箱或QQ邮箱,可以引导用户将发件地址加入白名单。
4.2 登录报用户名不存在或网络异常
有一次我遇到一个诡异问题:同一个邮箱,第一次发送验证码并登录成功了,退出后再登录,却一直报“用户名不存在”。一开始以为是AGC缓存数据出了问题,排查了半天发现,问题是发送验证码时用的邮箱和登录时用的邮箱不相同,一个末尾多了个空格,一个大小写不一致。
邮箱登录的核心是邮箱字符串完全匹配。SQLite索引、缓存、网络请求都可能保留原始字符串,你必须在业务层统一处理:发送验证码之前把邮箱trim,转成小写,然后所有后续请求都使用这一份标准化结果。这个坑看起来低级,但线上用户输入邮箱时自动补全、自动空格,很容易踩进去。
另外,AGC的登录接口对未注册邮箱的行为不是完全一致的。有时用户第一次用验证码登录会自动完成注册,有时会要求先走注册流程。我建议把注册和登录合并成一个流程,用户输入邮箱验证码成功后,如果系统判定是新用户,自动创建账号并进入应用,而不是让用户再经历一遍单独的注册页。
4.3 Token过期与会话丢失
AGC的Access Token有效期比较短,如果业务上用了独立的服务端,服务端必须自己处理Token校验和刷新。很多人在客户端登录成功后,把User对象直接序列化保存,下次启动反序列化发现字段缺失,或者token已经过期,就出现了“用户明明登录过却要重新登录”的体验问题。
我踩过最深的坑是:某个版本升级后,本地保存的用户对象缺少了某些新增字段,反序列化直接失败,导致会话丢失。解决办法是不直接序列化User对象,而是保存最小字段(UID、邮箱、token相关信息),然后启动时通过AGC接口重新获取完整的用户对象。
4.4 签名不匹配导致鉴权失败
前面提到过证书指纹配置,这个问题的排查往往最耗时。你甚至可能遇到Debug包正常,打Release包后登录就失败,第一反应是代码混淆导致SDK出现问题。实际上绝大多数情况是Release的证书指纹没有配置到AGC控制台。
签名日志有时会被混淆掩盖,建议在接入阶段先把Release标志关掉,用固定签名构建后再测试登录链路。另外,如果你的应用后续要支持多包名,比如免费版加专业版,每个包名都要在AGC控制台单独创建应用并配置指纹,同一个包名和指纹的组合必须和应用配置完全一致。
4.5 ArkTS类型系统带来的编译问题
HarmonyOS应用开发用的ArkTS语言,虽然语法上接近TypeScript,但在SDK接入时经常遇到类型定义不匹配的问题。比如AGC返回的User对象,某些字段可能定义为可空类型,你拿到后直接断言为字符串,编译就会报错,运行时可能崩溃。
我的建议是接入SDK时先定义好自己的接口类型,对AGC返回的数据做一层类型转换和兜底处理。
interface LoginUserInfo { uid: string; email: string; displayName?: string; }在回调里把AGC的User对象映射成自己的LoginUserInfo,不做复杂嵌套的类型断言,能省掉很多莫名其妙的编译问题。如果SDK版本升级导致某个属性被废弃,编译期就会暴露,比运行时崩溃好排查得多。
4.6 包名配置不一致问题
AGC控制台创建应用时填写的包名,必须和应用的包名完全一致。HarmonyOS应用的包名在构建配置里定义,很多人会忽略大小写,或者把开发期的测试包名和正式包名搞混。
包名不一致的报错通常比较直接,初始化时就会提示应用信息不匹配。但有一种隐蔽情况:配置了applicationId和实际模块的包名不同,虽然能编译通过,但初始化时读取到的包名是module的,而控制台填的是applicationId,导致鉴权失败。解决办法是打开build.gradle的defaultConfig,确认applicationId和namespace的关系,然后以applicationId为准在AGC控制台配置。
5. 安全加固与体验优化建议
5.1 验证码发送的安全策略
验证码接口如果被恶意刷,会带来两个问题:一是邮件服务器被当成垃圾邮件源,二是用户会收到大量骚扰邮件,导致投诉或拉黑。所以发送验证码的按钮必须做前端倒计时,这只是最低门槛。
更稳妥的做法是后端加一道人机校验。如果你的应用已经接入了行为验证码,在发送验证码时要求用户先完成一次验证操作,能挡住大部分脚本刷量。即使是轻量级的滑块验证,也能显著降低被刷概率。不要觉得这一步很麻烦,验证码接口被刷爆后的处理成本远高于接入成本。
另外,验证码有效期不要设置太长。900秒已经是极限,过长会增加验证码被恶意使用的风险。如果业务允许,建议设置到300秒到600秒之间,既给用户足够的输入时间,又保证安全边界。
5.2 服务端二次校验
如果你的应用有自己的后端服务,绝不能只依赖客户端传来的“我登录成功了”这样的消息。服务端应该用AGC返回的ID Token做签名校验,确认Token确实由AGC签发,再建立自己的会话。
标准的流程是:客户端登录成功后,把ID Token传给服务端,服务端从AGC服务端SDK验证Token的有效性并解析出UID,然后签发自己的业务Token。这样即使客户端的AGC登录流程被绕过,服务端也不会轻易被伪造请求穿透。
我有一个简单的客户端封装思路:登录成功后,不要把整个User对象交给服务端,而是只传idToken和uid。服务端校验通过后返回业务级token,后续所有接口都带这个业务token。同时服务端要缓存一份“用户UID到业务用户ID”的映射,避免每次请求都去查询用户表。
5.3 登录体验与账号找回的设计
邮箱验证码登录本身天然支持账号找回,不需要单独开发“忘记密码”流程。用户只要在登录页输入邮箱、获取验证码,通过验证即可登录,并自动恢复原账号。
不过要注意一个产品问题:如果用户之前用匿名或访客模式使用过应用,再通过邮箱验证码登录,需要决定是否合并匿名账号的历史数据。AGC的账号关联机制可以完成这个操作,但要在产品层明确规则,避免用户登录后发现历史记录丢失而困惑。
还有一个体验细节:登录成功后,建议把当前登录的邮箱显示在设置页或个人中心,方便用户确认自己登录的是哪个账号。如果同一用户多次使用不同邮箱登录,最好在设置里提供切换账号和绑定手机号的入口,完善账号体系。
5.4 本地日志与数据脱敏
最后说一个容易被忽略的点:不要把邮箱、验证码、Token以明文形式打印到日志里。验证码尤其敏感,即使只在调试阶段打印,一旦日志被抓取,等于把账号凭证拱手让人。
我在项目中专门封装了一个日志工具类,在Debug模式下可以输出必要信息,Release模式下自动过滤个人字段。邮箱只显示前三位和后两位,中间用星号替代。Token一律不打印完整内容。这个习惯建立起来之后,你会省掉非常多的安全评审问题。
最后再分享一个小技巧
邮箱验证码登录接完以后,我最大的感受是这个方案虽然看起来简单,但真正影响体验的全是细节:邮箱标准化、倒计时状态管理、Token刷新、账号关联。建议你按“发送验证码 → 登录 → 会话恢复 → 退出登录”四条链路分别做测试用例,尤其是断网、Token过期、重复点击发送这类边界场景,提前把异常状态处理了,线上问题会少很多。
如果后续要加手机号登录或第三方登录,把登录凭证的创建和登录接口拆开封装,上层页面只管调LoginManager,替换起来会非常舒服。目前这套方案在我的实际项目中运行了三个多月,邮件送达率稳定,登录成功率保持在98%以上,踩坑的经验基本都在上面,希望能帮到你。