本文旨在深入探讨华为鸿蒙HarmonyOS Next系统(截止目前 API12)在开发多语言电商平台方面的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。

在当今数字化时代,用户在各类应用和网站中需要频繁地进行登录和注册操作,记忆和输入复杂的密码成为了一项繁琐且容易出错的任务。鸿蒙 Next 系统凭借其先进的密码自动填充服务,为用户提供了便捷、安全的密码管理解决方案。今天,我们就深入探讨这项服务的功能与应用场景,揭示其背后的技术奥秘。

一、密码自动填充服务概述

(一)功能简介

鸿蒙 Next 的密码保险箱作为系统原生安全功能,为用户带来了前所未有的便捷免密登录体验。在应用或浏览器进行注册/登录操作时,用户无需再为记住或手动输入繁琐的密码而烦恼。系统能够自动识别相关场景,一键完成强密码的自动生成、保存以及填充操作,实现了密码管理的自动化和智能化。

值得注意的是,用户查看密码或使用密码进行自动填充时,都需要经过严格的身份认证。这一过程通过输入锁屏密码或验证指纹/人脸等生物识别方式来实现,确保只有用户本人才能访问密码,有效保护了用户的隐私和账户安全。

(二)使用场景

  1. 登录场景
    当用户在密码保险箱已保存账号数据的情况下,打开应用或网站的登录界面,密码自动填充服务会立即发挥作用。例如,在常见的社交媒体应用登录时,系统会自动检测到输入框,并填充已保存的用户名和密码,用户只需点击登录按钮即可快速进入应用,大大节省了输入时间,提升了登录效率。
    若用户手动输入账号密码进行登录,密码保险箱会敏锐地捕捉到这一操作,并主动询问用户是否同意将本次输入的账号密码保存至密码保险箱。这一贴心的设计,方便用户下次登录时无需再次输入。
  2. 注册场景
    在用户注册新账号时,密码自动填充服务同样提供了强大的支持。当用户设置密码时,系统会自动为其推荐高强度密码建议,这些建议基于复杂的算法生成,包含大写字母、小写字母、数字和特殊字符的组合,有效提升了密码的安全性。用户可视自身需求决定是否采用系统推荐的密码。
    一旦用户成功完成注册账号,密码保险箱会再次主动询问用户是否进行保存,确保用户在注册过程中无需繁琐地手动管理密码,为后续登录提供便利。
  3. 查看账号场景
    用户若想查看已保存的账号信息,可通过系统设置菜单中的“密码保险箱”选项进行操作。具体路径为“设置 > 隐私和安全 > 密码保险箱”。在进入密码保险箱管理界面之前,用户需要验证锁屏密码、指纹或人脸等身份信息,确保只有授权用户才能查看敏感的账号密码数据。这一界面提供了本机全量账号查看能力,用户可以清晰地了解自己在各个应用和网站中保存的账号信息,并可对其进行管理,如修改备注、删除等操作。

(三)架构介绍

密码保险箱的架构设计精巧,基于关键资产存储能力,确保用户的账号密码得到安全可靠的保存和保护。其核心工作流程如下:首先,系统会根据用户在应用或网页中的操作自动识别使用账号密码的场景,如登录、注册、修改密码等。然后,基于识别出的场景,密码保险箱提供对应的免密登录服务。在填充密码的过程中,依托统一用户认证能力,若用户指定需要使用某条账号密码进行填充时,系统会立即进行严格的用户身份信息认证,通过人脸/指纹或锁屏密码等方式,保证正确的人访问正确的数据,从而为用户的密码安全提供全方位的保障。

二、强密码填充

(一)触发条件及注意事项

强密码填充功能的触发需要满足一定条件。用户必须已设置锁屏密码,并且开启密码保险箱自动保存和填入账号和密码开关。这两个前置条件确保了密码填充服务在安全可控的环境下运行。
在界面设计方面,要求必须同时存在 type 为 InputType.USER_NAME(表示用户名输入框)和 InputType.NEW_PASSWORD(表示新密码输入框)的 TextInput 输入框组件,且 TextInput 组件的 enableAutoFill 属性的值为 true(默认即为 true)。只有在满足这些界面布局和属性设置要求的情况下,强密码填充功能才能正常触发。

(二)示例代码(注册、修改密码场景)

  1. 注册场景示例代码
    以下是一个注册页面的示例代码,展示了如何在注册过程中实现强密码填充功能:

    import router from '@ohos.router';
    
    @Entry
    @Component
    struct RegisterPage {
     @State ReserveAccount: string = "";
     @State ReservePassword: string = "";
     @State enAbleAutoFill: boolean = true;
     private length: number = 0;
    
     onBackPress() {
         this.enAbleAutoFill = false;
         router.back();
         return true;
     }
    
     aboutToAppear() {
     }
    
     build() {
         Column() {
             Text('注册账号')
               .fontSize(24)
               .fontColor('#000000')
               .fontWeight(FontWeight.Medium)
               .textAlign(TextAlign.Center)
               .width('100%')
               .margin({ top: 18 })
             TextInput({ placeholder: '用户名' })
               .opacity(0.6)
               .type(InputType.USER_NAME)
               .placeholderColor(0x182431)
               .width('100%')
               .placeholderFont({ size: 16, weight: FontWeight.Regular })
               .margin({ top: 32, bottom: 8 })
               .onChange((value: string) => {
                     this.ReserveAccount = value;
                     this.length = value.length;
                 })
               .caretPosition(this.length)
             TextInput({ placeholder: '新密码' })
               .enableAutoFill(this.enAbleAutoFill)
               .type(InputType.NEW_PASSWORD)
               .passwordRules('begin:[upper],special:[yes],len:[maxlen:32,minlen:12]')
               .placeholderColor(0x182431)
               .width('100%')
               .opacity(0.6)
               .showPasswordIcon(true)
               .placeholderFont({ size: 16, weight: FontWeight.Regular })
               .onChange((value: string) => {
                     this.ReservePassword = value;
                 })
               .margin({ bottom: 36 })
             Button('页面跳转') { type: ButtonType.Capsule, stateEffect: false }
               .borderRadius(20)
               .width('80%')
               .height(40)
               .margin({ top: 24 })
               .enabled(this.ReserveAccount!== "" && this.ReservePassword!== "")
               .onClick(() => {
                     router.pushUrl({
                         url: 'pages/Index', // 此处 pages/Index 为跳转界面地址,请自行修改
                         params: {
                             src: '注册账号'
                         }
                     }, (err) => {
                         if (err) {
                             console.error('Invoke pushUrl failed, code is ${err.code}, message is ${err.message}');
                             return;
                         }
                         console.info('Invoke pushUrl succeeded.');
                     });
                 })
         }
           .width('100%')
           .height('100%')
     }
    }

    在上述代码中,用户名输入框的 type 属性设置为 InputType.USER_NAME,新密码输入框的 type 属性设置为 InputType.NEW_PASSWORD,并通过 passwordRules 属性设置了强密码规则。当用户点击新密码输入框时,如果满足触发条件,系统将自动生成符合规则的强密码并填充到输入框中。

  2. 修改密码场景示例代码
    修改密码场景的示例代码与注册场景类似,以下是一个简化的示例:

    import router from '@ohos.router';
    
    @Entry
    @Component
    struct ModifyPasswordPage {
     @State ReserveAccount: string = "";
     @State ReservePassword: string = "";
     @State enAbleAutoFill: boolean = true;
     private length: number = 0;
    
     onBackPress() {
         this.enAbleAutoFill = false;
         router.back();
         return true;
     }
    
     aboutToAppear() {
     }
    
     build() {
         Column() {
             Text('修改密码')
               .fontSize(24)
               .fontColor('#000000')
               .fontWeight(FontWeight.Medium)
               .textAlign(TextAlign.Center)
               .width('100%')
               .margin({ top: 18 })
             TextInput({ placeholder: '用户名' })
               .opacity(0.6)
               .type(InputType.USER_NAME)
               .placeholderColor(0x182431)
               .width('100%')
               .placeholderFont({ size: 16, weight: FontWeight.Regular })
               .margin({ top: 32, bottom: 8 })
               .onChange((value: string) => {
                     this.ReserveAccount = value;
                     this.length = value.length;
                 })
               .caretPosition(this.length)
             TextInput({ placeholder: '密码' })
               .type(InputType.Password)
               .placeholderColor(0x182431)
               .width('100%')
               .opacity(0.6)
               .showPasswordIcon(true)
               .placeholderFont({ size: 16, weight: FontWeight.Regular })
               .onChange((value: string) => {
                     this.ReservePassword = value;
                 })
               .margin({ bottom: 12 })
             TextInput({ placeholder: '新密码' })
               .enableAutoFill(this.enAbleAutoFill)
               .type(InputType.NEW_PASSWORD)
               .passwordRules('begin:[lower],special:[yes],len:[maxlen:32,minlen:12]')
               .placeholderColor(0x182431)
               .width('100%')
               .opacity(0.6)
               .showPasswordIcon(true)
               .placeholderFont({ size: 16, weight: FontWeight.Regular })
               .onChange((value: string) => {
                     this.ReservePassword = value;
                 })
               .margin({ bottom: 36 })
             Button('页面跳转') { type: ButtonType.Capsule, stateEffect: false }
               .borderRadius(20)
               .width('80%')
               .height(40)
               .margin({ top: 24 })
               .enabled(this.ReserveAccount!== "" && this.ReservePassword!== "")
               .onClick(() => {
                     router.pushUrl({
                         url: 'pages/Index', // 此处 pages/Index 为跳转界面地址,请自行修改
                         params: {
                             src: '修改密码'
                         }
                     }, (err) => {
                         if (err) {
                             console.error('Invoke pushUrl failed, code is ${err.code}, message is ${err.message}');
                             return;
                         }
                         console.info('Invoke pushUrl succeeded.');
                     });
                 })
         }
           .width('100%')
           .height('100%')
     }
    }

    在修改密码页面中,同样设置了用户名、旧密码和新密码输入框,并为新密码输入框配置了强密码规则。用户在修改密码时,可利用强密码填充功能获取安全可靠的新密码。

三、网页接入密码保险箱

(一)手机使用场景(以华为开发者网站为例)

在手机上使用网页进行登录操作时,鸿蒙 Next 的密码自动填充服务同样表现出色。以华为开发者网站(https://developer.huawei.com/)为例,当用户在网站中输入用户名和密码并成功登陆后,ArkWeb 会智能地提示用户是否将用户名和密码保存到密码保险箱中。
下次再次打开相同的网站时,点击用户名或者密码框,会弹出密码保险箱的填充提示。用户可以选择提示框中的用户名,通过认证(如输入锁屏密码、指纹或人脸验证)后,就能直接在网页中填入之前保存的用户名和密码,实现快速登录。此外,用户还可以点击“使用其他账号”,从密码保险箱中选择保存的其他账号进行登录,或者选择“手动输入”进行手动填写账号密码。这种便捷的操作方式,大大提升了用户在网页登录场景中的体验,同时也保障了账号密码的安全性。

(二)网页密码保存规格

ArkWeb 依赖密码表单提交成功后,触发页面跳转到其他页面,才能触发密码保存机制。这一设计确保了密码保存操作在登录成功且页面状态稳定后进行,避免了因登录失败或页面异常导致的密码保存错误。
需要注意的是,Native 应用通过 ArkWeb 实现 H5 登入时,登录成功后请勿立即销毁 ArkWeb 实例,否则将无法提示密码保存。这一点在开发过程中需要特别关注,以确保密码保存功能的正常运作。

(三)网页密码表单规格

  1. 推荐的密码登录表单
    为了确保密码自动填充服务能够准确识别和处理网页中的用户名和密码元素,推荐使用静态的登录页面或登录表单元素,避免通过 js 脚本在页面中动态插入<form><input>等表单元素。用户名和密码输入框均应使用<input>元素实现,并集成在同一个<form>内,且默认可编辑。在登录场景中,有且最多只能有一个 type="password"类型的<input>元素。
    点击按钮触发登录后,登录成功应当触发跳转到新的页面,这有助于系统判断登录操作的完成状态,从而正确执行密码保存等后续操作。用户名框应携带 autocomplete="username"属性,并携带 id 或 name 属性,且建议采用系统推荐的值,以便算法能够准确推断用户名元素。
  2. 不支持自动填充的密码登录表单类型

    • 初始页面内无用户名密码表单元素,点击登录跳转页面后,新增非<form>类型的用户名密码表单,这种情况不符合密码自动填充服务的要求,无法实现自动填充功能。
    • 密码输入框携带了 autocomplete="new-password"属性,这与系统预期的密码输入框属性不符,将导致无法自动填充密码。
    • 用户名输入框 type="number",验证码输入框 type="number",且无密码输入框的情况,也无法触发密码自动填充服务,因为系统无法识别这种不符合常规登录表单结构的布局。
    • 用户名和密码元素中间存在其他<input>元素,可能会导致算法推断出的用户名元素不符合用户预期,从而影响密码自动填充功能的正常使用。
    • 网页通过 javascript 脚本变更了<input>元素的焦点或者修改<input>元素的 value,这种动态操作可能会干扰密码自动填充服务对表单元素的识别和处理,导致无法正常填充密码。
    • 用户名<input>元素上 id、name、label 内容中匹配到特定的密码类型标识(如“pin”、“parola”等),或者其 autocomplete 属性为“one-time-code”或者“cc-”,或者能正则匹配到信用卡标识,这些情况都可能导致系统无法正确识别用户名元素,进而影响密码自动填充功能。
    • 页面加载完成时,<input>的 type 属性不是“password”,点击登录才变成“password”类型,这种动态变化的输入框类型也不符合密码自动填充服务的要求,可能导致无法正常填充密码。

通过对鸿蒙 Next 密码自动填充服务的详细解析,我们可以看到其在功能设计和应用场景方面的全面性和优越性。无论是在应用内还是网页端,该服务都为用户提供了便捷、安全的密码管理解决方案,有效提升了用户体验,降低了密码管理的复杂性和风险。希望本文能够帮助各位更好地理解和应用这一功能,为用户打造更加安全、便捷的应用环境。


SameX
1 声望2 粉丝