开发者工具箱-华为账号登录功能实现
<h1 id="华为账号登录功能实现">华为账号登录功能实现</h1><blockquote>
<p>最近项目要接入华为账号登录,SDK文档看着还行,真用起来发现坑还不少。这里把踩过的坑和实现细节都记下来,省得以后自己或者同事再掉坑里。</p>
</blockquote>
<h2 id="一基础知识">一、基础知识</h2>
<h3 id="11-什么是华为账号登录">1.1 什么是华为账号登录</h3>
<p>华为账号登录就是让用户用华为账号一键登录你App,省去注册、记密码的麻烦。用过微信/QQ登录的都懂,套路差不多。</p>
<h3 id="12-主要概念">1.2 主要概念</h3>
<ul>
<li>UnionID:全局唯一,跨App识别同一用户。别搞混了,和OpenID不是一回事。</li>
<li>OpenID:单App唯一,换个App就变了。</li>
<li>AuthorizationCode:临时凭证,后端用来换token。</li>
<li>AccessToken:拿这个去调接口,别暴露给前端。</li>
</ul>
<h2 id="二开发背景">二、开发背景</h2>
<p>我们项目用户大多用华为手机,老板一句"加个华为账号登录吧",于是就有了这篇笔记。SDK集成其实不难,难的是各种边界和异常场景。</p>
<h2 id="三实现步骤">三、实现步骤</h2>
<h3 id="31-基础配置">3.1 基础配置</h3>
<pre><code class="language-typescript">// 该引的都得引,不然编译直接报错
import { LoginWithHuaweiIDButton, loginComponentManager, authentication } from '@kit.AccountKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { util } from '@kit.ArkTS';
import { RDBUtils } from '../../utils/rdbutils';
import { UserInfo } from '../../utils/rdbutils';
import dataPreferences from '@ohos.data.preferences';
import common from '@ohos.app.ability.common';
import promptAction from '@ohos.promptAction';
import router from '@ohos.router';
</code></pre>
<h3 id="32-登录功能实现">3.2 登录功能实现</h3>
<pre><code class="language-typescript">@Entry
@Component
struct PreviewLoginButtonPage {
// 数据库工具实例
private rdbUtils: RDBUtils = RDBUtils.getInstance(getContext(this));
private context: common.Context = getContext(this);
// 状态变量
@State userInfo: UserInfo | null = null;
@State isLoggedIn: boolean = false;
@State showUserInfo: boolean = false;
@State loading: boolean = false;
// 登录按钮控制器
controller: loginComponentManager.LoginWithHuaweiIDButtonController =
new loginComponentManager.LoginWithHuaweiIDButtonController()
.onClickLoginWithHuaweiIDButton(async (error: BusinessError, response: loginComponentManager.HuaweiIDCredential) => {
if (error) {
hilog.error(0x0000, 'testTag',
`登录失败. 错误码: ${error.code}, 错误信息: ${error.message}`);
return;
}
if (response) {
this.loading = true;
try {
// 获取登录凭证
const authCode = response.authorizationCode;
const openID = response.openID;
const unionID = response.unionID;
const idToken = response.idToken;
// 获取用户详细信息
const authRequest = new authentication.HuaweiIDProvider()
.createAuthorizationWithHuaweiIDRequest();
authRequest.scopes = ['profile'];
authRequest.permissions = ['serviceauthcode'];
authRequest.forceAuthorization = true;
authRequest.state = util.generateRandomUUID();
const controller = new authentication.AuthenticationController(this.context);
const data = await controller.executeRequest(authRequest);
// 处理用户信息
const userInfo: UserInfo = {
avatarUri: data.data?.avatarUri || '',
nickName: data.data?.nickName || '',
unionID: data.data?.unionID || '',
openID: data.data?.openID || '',
authorizationCode: data.data?.authorizationCode || '',
createTime: Date.now(),
memo: '华为账号登录'
};
// 保存用户信息
await this.saveUserInfo(userInfo);
// 更新状态
this.isLoggedIn = true;
this.showUserInfo = true;
} catch (err) {
hilog.error(0x0000, 'testTag', `登录处理失败: ${err}`);
} finally {
this.loading = false;
}
}
});
}
</code></pre>
<h3 id="33-用户信息管理">3.3 用户信息管理</h3>
<pre><code class="language-typescript">private async saveUserInfo(userInfo: UserInfo) {
try {
// 查询是否已存在该用户
const existingUser = await this.rdbUtils.queryUserByUnionID(userInfo.unionID);
if (existingUser) {
// 更新现有用户信息
userInfo.id = existingUser.id;
await this.rdbUtils.updateUser(userInfo);
} else {
// 插入新用户信息
await this.rdbUtils.insertUser(userInfo);
}
// 保存当前用户的unionID
await this.saveCurrentUserUnionID(userInfo.unionID);
// 显示登录成功提示
promptAction.showToast({
message: '登录成功',
duration: 2000
});
// 延迟返回首页
setTimeout(() => {
router.back();
}, 2000);
} catch (error) {
hilog.error(0x0000, 'testTag', `保存用户信息失败: ${error}`);
promptAction.showToast({
message: '登录失败',
duration: 2000
});
}
}
</code></pre>
<h2 id="四踩坑记录">四、踩坑记录</h2>
<ul>
<li>登录状态没持久化,用户一重启App就得重新登录,被吐槽了好几次。</li>
<li>登录回调里没处理好异常,偶尔UI直接卡死。</li>
<li>多设备登录状态不同步,用户切换设备后信息乱套。</li>
<li>网络慢的时候,登录按钮没loading,用户以为点没反应。</li>
<li>错误码处理不全,有些报错用户根本看不懂。</li>
<li>图片没缓存,头像每次都要重新拉,体验很差。</li>
</ul>
<h2 id="五使用示例">五、使用示例</h2>
<pre><code class="language-typescript">// 1. 初始化登录页面
@Entry
@Component
struct LoginPage {
build() {
Column() {
// 登录按钮
LoginWithHuaweiIDButton({
params: {
style: loginComponentManager.Style.BUTTON_RED,
extraStyle: {
buttonStyle: new loginComponentManager.ButtonStyle()
.loadingStyle({
show: this.loading
})
},
borderRadius: 24,
loginType: loginComponentManager.LoginType.ID,
supportDarkMode: true,
verifyPhoneNumber: true
},
controller: this.controller
})
}
}
}
// 2. 检查登录状态
private async checkLoginStatus() {
try {
this.loading = true;
const currentUnionID = await this.getCurrentUserUnionID();
if (currentUnionID) {
const user = await this.rdbUtils.queryUserByUnionID(currentUnionID);
if (user) {
this.userInfo = user;
this.isLoggedIn = true;
this.showUserInfo = true;
}
}
} catch (error) {
hilog.error(0x0000, 'testTag', `检查登录状态失败: ${error}`);
} finally {
this.loading = false;
}
}
// 3. 退出登录
private async logout() {
try {
await this.clearCurrentUserUnionID();
this.isLoggedIn = false;
this.showUserInfo = false;
this.userInfo = null;
promptAction.showToast({
message: '已退出登录',
duration: 2000
});
} catch (error) {
hilog.error(0x0000, 'testTag', `退出登录失败: ${error}`);
}
}
</code></pre>
<h2 id="六注意事项">六、注意事项</h2>
<ul>
<li>华为开发者联盟的配置一定要对,包名、签名、权限,错一个都不行。</li>
<li>token和敏感信息一定要加密,别直接丢本地。</li>
<li>网络慢的时候记得加loading,别让用户等得心慌。</li>
<li>错误提示要友好,别只弹"登录失败",最好能带点原因。</li>
<li>多端同步要做好,别让用户切换设备后信息乱套。</li>
<li>代码里别写死任何ID和密钥,安全第一。</li>
</ul>
<h2 id="七后端实现说明">七、后端实现说明</h2>
<blockquote>
<p>这里只是前端实现,后端你得自己搞。比如token换取、用户信息存储、权限校验、接口安全,这些都得后端配合。别想着全靠前端糊弄过去,安全问题很严重。</p>
</blockquote>
<h3 id="71-后端服务需求">7.1 后端服务需求</h3>
<ul>
<li>用户认证服务:校验华为账号凭证、生成和管理会话、token刷新、登录状态同步。</li>
<li>用户信息服务:存储和管理用户信息、权限、同步、更新。</li>
<li>安全服务:数据加密、敏感信息保护、访问控制、防止数据泄露。</li>
</ul>
<h3 id="72-后端api接口">7.2 后端API接口</h3>
<pre><code class="language-typescript">// 1. 用户认证接口
POST /api/auth/login
Request: {
authorizationCode: string;// 华为账号授权码
openID: string; // 华为账号OpenID
unionID: string; // 华为账号UnionID
}
Response: {
token: string; // 用户访问令牌
refreshToken: string; // 刷新令牌
expiresIn: number; // 过期时间
}
// 2. 用户信息接口
GET /api/user/info
Request: {
token: string; // 用户访问令牌
}
Response: {
userInfo: {
avatarUri: string; // 头像
nickName: string; // 昵称
unionID: string; // UnionID
openID: string; // OpenID
// 其他用户信息
}
}
// 3. 刷新令牌接口
POST /api/auth/refresh
Request: {
refreshToken: string; // 刷新令牌
}
Response: {
token: string; // 新的访问令牌
expiresIn: number; // 过期时间
}
</code></pre>
<h3 id="73-安全考虑">7.3 安全考虑</h3>
<ul>
<li>所有接口必须HTTPS,别用明文。</li>
<li>token、敏感数据都要加密,数据库也别裸奔。</li>
<li>SQL注入、暴力破解这些老生常谈的安全问题,别掉以轻心。</li>
<li>登录设备管理、token过期、异常检测都要有。</li>
</ul>
<h3 id="74-部署建议">7.4 部署建议</h3>
<ul>
<li>服务器和数据库都要有备份,别等出事才想起来。</li>
<li>日志、监控、告警都要配好,出问题能第一时间发现。</li>
<li>缓存用Redis,接口别被刷爆。</li>
<li>防火墙、限流、黑名单这些都要有。</li>
</ul>
<h3 id="75-开发建议">7.5 开发建议</h3>
<ul>
<li>后端框架、ORM、API文档、日志系统都建议用主流方案,别造轮子。</li>
<li>统一错误处理,日志别省。</li>
<li>单元测试、压力测试、自动化测试都要有。</li>
<li>安全补丁要及时打,别等被黑了才补。</li>
</ul>
<h3 id="76-注意事项">7.6 注意事项</h3>
<ul>
<li>需要自行实现后端服务,前端只是"壳"。</li>
<li>华为开发者联盟账号、API权限都要提前申请。</li>
<li>数据安全、服务监控、应急预案都要有。</li>
<li>重要:别把密钥、token、用户数据写死在前端!</li>
</ul>
<hr>
<h2 id="有问题欢迎留言大家一起踩坑一起填方案适合大部分场景特殊需求记得多测几遍">有问题欢迎留言,大家一起踩坑一起填。方案适合大部分场景,特殊需求记得多测几遍。</h2>
<ul>
<li>鸿蒙应用开发指南</li>
</ul>
<h2 id="欢迎体验">欢迎体验</h2>
<p>这个已经集成到鸿蒙开发者工具箱里了,欢迎下载体验!</p>
<p>鸿蒙开发者工具箱</p>
<hr>
<blockquote>
<p>作者:在人间耕耘<br>
邮箱:1743914721@qq.com<br>
版权声明:本文为博主原创文章,转载请附上原文出处链接及本声明。<br>
本文由博客一文多发平台 OpenWrite 发布!</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/zhaoylhm/p/18939482
頁:
[1]