目录
1 概述
1.1 功能简介
密码保险箱作为HarmonyOS系统安全功能,为用户提供了便捷的免密登录体验。
用户在应用或浏览器进行注册/登录操作时,可一键完成自动生成强密码、自动保存、自动填充,无需记住或手动输入繁琐的密码,由系统实现统一的安全管理密码能力。
用户查看密码或使用密码进行自动填充,都需要经过身份认证,通过输入锁屏密码或验证指纹/人脸,确保只有用户本人才能访问密码。
1.2 使用场景
面向用户,可分为三大场景:
- 登录:在密码保险箱已保存账号数据的情况下,提供自动填充服务,若用户为手动输入账号密码进行登录,密码保险箱会主动询问用户是否同意将本次输入的账号密码保存至密码保险箱。
- 注册:用户注册新用户时,当用户设置密码时,自动为用户推荐高强度密码建议,用户可视情况决定是否需要;同样,用户成功进行注册账号时,密码保险箱同样会主动询问用户是否进行保存。
- 查看账号:密码保险箱在系统设置菜单中,提供了本机全量账号查看能力,用户可通过“设置 > 隐私和安全 > 密码保险箱”查看本机保存的全量数据。
用户在注册/登录场景产生的密码数据,均可以在密码保险箱管理界面进行查看。进入密码保险箱管理界面的步骤:“设置 > 隐私和安全 > 密码保险箱”,用户验证锁屏密码/指纹/人脸后,可以查看已保存的所有账号密码数据。
1.3 架构介绍
如下图所示,密码保险箱基于关键资产存储能力,保存并保护用户的账号密码。
根据用户操作自动识别使用账号密码的场景(如登录、注册、修改密码),基于识别的场景,提供对应的免密登录服务。
在填充过程中,依托统一用户认证能力,若用户指定需要使用某条账号密码进行填充时,会进行用户身份信息认证(人脸/指纹或锁屏密码),用于保证正确的人访问了正确的数据。
图1 密码保险箱功能架构图
2 应用接入密码保险箱
2.1 快速适配
密码自动填充服务依托ArkUI TextInput 组件
2.1.1 约束与限制
应用需要使用ArkUI组件TextInput作为输入框,并指定输入框类型(InputType),才能使用系统密码自动填充服务。不同输入框类型与使用场景的对应关系如下表所示。
InputType名称 | 描述 | 使用场景 |
---|---|---|
USER_NAME | 用户名输入模式。 | 用于登录、注册等场景的用户名输入。 |
Password | 密码输入模式。支持输入数字、字母、下划线、空格、特殊字符。 密码显示小眼睛图标并且默认会将文字变成圆点。 | 用于登录、注册等场景的密码输入。 |
NUMBER_PASSWORD | 数字密码输入模式。仅支持输入数字。 密码显示小眼睛图标并且默认会将文字变成圆点。 | 用于登录、注册等场景的密码输入。 |
NEW_PASSWORD | 新密码输入模式。 | 用于注册、修改密码等场景的新密码输入。 |
2.1.2 快速适配指导
当应用具备账号密码登录的场景时,只需要将充当用户名的TextInput输入框的type属性设置为InputType.USER_NAME,将密码TextInput输入框的type属性设置为InputType.Password,即可使用密码保险箱的填充和保存功能。
在应用的账号密码注册页除设置用户名输入框外,将新密码TextInput输入框的type属性设置为InputType.NEW_PASSWORD,即可使用强密码填充功能。如果应用对密码强度有特殊要求,根据为应用添加自动生成高强度密码的建议适配即可。
代码示例:
TextInput({ placeholder: '用户名' })
.opacity(0.6)
.type(InputType.USER_NAME)
.placeholderColor(0x182431)
.width('100%')
.placeholderFont({ size: 16, weight: FontWeight.Regular })
.margin({ top: 32, bottom: 8 })
2.2 账号密码保存
密码保险箱在应用的登录、注册、修改密码等场景具备自动保存用户名和密码的能力。
保存后的用户名和密码可以在下次登录、修改密码时中自动填充到界面上的对应输入框,用户可以在密码保险箱内对已保存的用户名和密码进行查看,修改,添加备注,删除。
应用界面触发账号密码自动保存时,若密码保险箱中不存在同应用下的相同账号,则弹出账号密码保存提示框,用户点击“保存密码”按钮,即可将本次使用的账号和密码保存至密码保险箱。
应用触发账号登录或注册时,均可触发保存功能,下面分别介绍两种布局的标准适配场景。
触发条件及注意事项:
- 已设置锁屏密码并且开启密码保险箱自动保存和填入账号和密码开关。
- 界面中TextInput输入框组件的enableAutoFill属性的值应为true(默认为true)。
- 密码保险箱的自动保存功能只适用用户名和密码保存场景,在界面中必须同时存在用户名和密码的TextInput输入框组件。具体类型请参考输入框类型说明。
用户名输入框应设置type属性为InputType.USER_NAME。
密码输入框应设置type属性为InputType.Password或InputType.NEW_PASSWORD。
其中,InputType.Password表示普通密码输入框,适用于登录界面的密码和修改密码界面的旧密码,
InputType.NEW_PASSWORD表示新密码输入框,适用于注册界面和修改密码界面的新密码。
- 用户名和密码输入框中需要输入内容,不能为空也不能超长。用户名长度不能超过128字符,密码长度不能超过256字符。
- 页面跳转时触发保存功能。
- 在只有type为InputType.USER_NAME和InputType.Password的两个TextInput组件时,如果使用账号密码填充-修改密码自动填充了用户名和密码并没有修改,则不会触发保存和更新功能。
2.2.1 账号密码登录
示例代码如下:
@Entry
@Component
struct LoginExample {
pathInfos: NavPathStack = new NavPathStack();
@State ReserveAccount: string = '';
@State ReservePassword: string = '';
@Builder
PageMap(name: string) {
if (name === 'home_page') {
HomePage()
}
}
build() {
Navigation(this.pathInfos) {
Column({ space: 16 }) {
Text("账户登录").commonTitleStyles()
TextInput({ placeholder: '用户名' })
.commonInputStyles()
.type(InputType.USER_NAME) // 账号框使用USER_NAME属性
.onChange((value: string) => {
this.ReserveAccount = value;
})
TextInput({ placeholder: '密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.Password) // 密码框使用Password属性
.onChange((value: string) => {
this.ReservePassword = value;
})
Button('登录')
.width('100%')
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.pathInfos.pushPathByName('home_page', null)
})
}
.padding(16)
}
.navDestination(this.PageMap)
.height('100%')
.width('100%')
}
}
@Component
struct HomePage {
pathInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
Text("Home Page").commonTitleStyles()
}.width('100%').height('100%')
}.title("Home Page")
.onReady((context: NavDestinationContext) => {
this.pathInfos = context.pathStack;
})
}
}
@Extend(Text)
function commonTitleStyles() {
.fontSize(24)
.fontColor('#000000')
.fontWeight(FontWeight.Medium)
.margin({ top: 24, bottom: 16 })
}
@Extend(TextInput)
function commonInputStyles() {
.placeholderColor(0x182431)
.width('100%')
.opacity(0.6)
.placeholderFont({ size: 16, weight: FontWeight.Regular })
.margin({ top: 16 })
}
@Extend(Button)
function commonButtonStyles() {
.width('100%')
.height(40)
.borderRadius(20)
.margin({ top: 24 })
}
2.2.2 账号密码注册
示例代码如下:
@Entry
@Component
struct RegisterExample {
pathInfos: NavPathStack = new NavPathStack();
@State ReserveAccount: string = '';
@State ReservePassword: string = '';
@State enAbleAutoFill: boolean = true;
onBackPress() {
// 当非成功登录、返回等页面跳转时将enAbleAutoFill设置为false,密码保险箱不使能
this.enAbleAutoFill = false;
return false;
}
@Builder
PageMap(name: string) {
if (name === 'register_result_page') {
RegisterResultPage()
}
}
build() {
Navigation(this.pathInfos) {
Column() {
Text("注册账号")
.commonTitleStyles()
TextInput({ placeholder: '用户名' })
.commonInputStyles()
.type(InputType.USER_NAME) // 账号框使用USER_NAME属性
.onChange((value: string) => {
this.ReserveAccount = value;
})
TextInput({ placeholder: '新密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.NEW_PASSWORD) // 密码框使用 new Password 属性,可以触发生成强密码
.enableAutoFill(this.enAbleAutoFill)
.passwordRules('begin:[upper],special:[yes],len:[maxlen:32,minlen:12]')
.onChange((value: string) => {
this.ReservePassword = value;
})
Button('页面跳转')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.pathInfos.pushPathByName('register_result_page', null)
})
Button('页面跳转(跳转前关闭autofill)')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.enAbleAutoFill = false;
this.pathInfos.pushPathByName('register_result_page', null)
})
}
}
.navDestination(this.PageMap)
.height('100%')
.width('100%')
}
}
@Component
struct RegisterResultPage {
pathInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
Text("Result Page").commonTitleStyles()
}.width('100%').height('100%')
}.title("Result Page")
.onReady((context: NavDestinationContext) => {
this.pathInfos = context.pathStack;
})
}
}
@Extend(Text)
function commonTitleStyles() {
.fontSize(24)
.fontColor('#000000')
.fontWeight(FontWeight.Medium)
.margin({ top: 24, bottom: 16 })
}
@Extend(TextInput)
function commonInputStyles() {
.placeholderColor(0x182431)
.width('100%')
.opacity(0.6)
.placeholderFont({ size: 16, weight: FontWeight.Regular })
.margin({ top: 16 })
}
@Extend(Button)
function commonButtonStyles() {
.width('100%')
.height(40)
.borderRadius(20)
.margin({ top: 24 })
}
2.3 账号密码更新
应用界面触发账号密码自动保存时,若密码保险箱中已存在同应用下与本次使用账号相同的账号,则弹出密码更新提示框,用户点击更新按钮,即可更新密码保险箱内对应账号的密码。
应用触发修改密码或使用已经保存过的账号手动登录时,均会触发密码更新功能。
登录的布局介绍请参考账号密码登录,以下仅介绍修改账号密码的标准适配场景。
触发条件及注意事项同账号密码保存功能。
2.3.1 修改账号密码
示例代码如下:
@Entry
@Component
struct RegisterExample {
pathInfos: NavPathStack = new NavPathStack();
@State ReserveAccount: string = '';
@State ReservePassword: string = '';
@State enAbleAutoFill: boolean = true;
onBackPress() {
// 当非成功登录、返回等页面跳转时将enAbleAutoFill设置为false,密码保险箱不使能
this.enAbleAutoFill = false;
return false;
}
@Builder
PageMap(name: string) {
if (name === 'register_result_page') {
RegisterResultPage()
}
}
build() {
Navigation(this.pathInfos) {
Column() {
Text("注册账号")
.commonTitleStyles()
TextInput({ placeholder: '用户名' })
.commonInputStyles()
.type(InputType.USER_NAME) // 账号框使用USER_NAME属性
.onChange((value: string) => {
this.ReserveAccount = value;
})
TextInput({ placeholder: '密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.Password)
.onChange((value: string) => {
this.ReservePassword = value;
})
TextInput({ placeholder: '新密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.NEW_PASSWORD) // 密码框使用 new Password 属性,可以触发生成强密码
.enableAutoFill(this.enAbleAutoFill)
.passwordRules('begin:[upper],special:[yes],len:[maxlen:32,minlen:12]')
.onChange((value: string) => {
this.ReservePassword = value;
})
Button('页面跳转')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.pathInfos.pushPathByName('register_result_page', null)
})
Button('页面跳转(跳转前关闭autofill)')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.enAbleAutoFill = false;
this.pathInfos.pushPathByName('register_result_page', null)
})
}
}
.navDestination(this.PageMap)
.height('100%')
.width('100%')
}
}
@Component
struct RegisterResultPage {
pathInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
Text("Result Page").commonTitleStyles()
}.width('100%').height('100%')
}.title("Result Page")
.onReady((context: NavDestinationContext) => {
this.pathInfos = context.pathStack;
})
}
}
@Extend(Text)
function commonTitleStyles() {
.fontSize(24)
.fontColor('#000000')
.fontWeight(FontWeight.Medium)
.margin({ top: 24, bottom: 16 })
}
@Extend(TextInput)
function commonInputStyles() {
.placeholderColor(0x182431)
.width('100%')
.opacity(0.6)
.placeholderFont({ size: 16, weight: FontWeight.Regular })
.margin({ top: 16 })
}
@Extend(Button)
function commonButtonStyles() {
.width('100%')
.height(40)
.borderRadius(20)
.margin({ top: 24 })
}
2.4 账号密码填充
密码保险箱可以在登录或修改密码时,自动填充已保存的用户名和密码。
触发条件及注意事项:
- 已设置锁屏密码并且开启密码保险箱自动保存和填入账号和密码开关。
- 界面中必须同时存在type为InputType.USER_NAME(表示用户名输入框)和InputType.Password(表示普通密码输入框)的TextInput输入框组件。
具体类型请参考输入框类型说明。
- TextInput组件的enableAutoFill属性的值为true(默认true)。
- 密码保险箱中已保存过当前应用的用户名和密码。
- 用户在界面中首次点击用户名输入框或密码输入框时触发自动填充弹窗。
2.4.1 登录
示例代码如下:
@Entry
@Component
struct LoginExample {
pathInfos: NavPathStack = new NavPathStack();
@State ReserveAccount: string = '';
@State ReservePassword: string = '';
@Builder
PageMap(name: string) {
if (name === 'home_page') {
HomePage()
}
}
build() {
Navigation(this.pathInfos) {
Column() {
Text("账户登录")
.commonTitleStyles()
TextInput({ placeholder: '用户名' })
.commonInputStyles()
.type(InputType.USER_NAME)// 账号框使用USER_NAME属性
.onChange((value: string) => {
this.ReserveAccount = value;
})
TextInput({ placeholder: '密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.Password)// 密码框使用Password属性
.onChange((value: string) => {
this.ReservePassword = value;
})
Button('登录')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.pathInfos.pushPathByName('home_page', null)
})
}
}
.navDestination(this.PageMap)
.height('100%')
.width('100%')
}
}
@Component
struct HomePage {
pathInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
Text("Home Page").commonTitleStyles()
}.width('100%').height('100%')
}.title("Home Page")
.onReady((context: NavDestinationContext) => {
this.pathInfos = context.pathStack;
})
}
}
@Extend(Text)
function commonTitleStyles() {
.fontSize(24)
.fontColor('#000000')
.fontWeight(FontWeight.Medium)
.margin({ top: 24, bottom: 16 })
}
@Extend(TextInput)
function commonInputStyles() {
.placeholderColor(0x182431)
.width('100%')
.opacity(0.6)
.placeholderFont({ size: 16, weight: FontWeight.Regular })
.margin({ top: 16 })
}
@Extend(Button)
function commonButtonStyles() {
.width('100%')
.height(40)
.borderRadius(20)
.margin({ top: 24 })
}
2.4.2 修改密码
示例代码如下:
@Entry
@Component
struct RegisterExample {
pathInfos: NavPathStack = new NavPathStack();
@State ReserveAccount: string = '';
@State ReservePassword: string = '';
@State enAbleAutoFill: boolean = true;
onBackPress() {
// 当非成功登录、返回等页面跳转时将enAbleAutoFill设置为false,密码保险箱不使能
this.enAbleAutoFill = false;
return false;
}
@Builder
PageMap(name: string) {
if (name === 'register_result_page') {
RegisterResultPage()
}
}
build() {
Navigation(this.pathInfos) {
Column() {
Text("注册账号")
.commonTitleStyles()
TextInput({ placeholder: '用户名' })
.commonInputStyles()
.type(InputType.USER_NAME) // 账号框使用USER_NAME属性
.onChange((value: string) => {
this.ReserveAccount = value;
})
TextInput({ placeholder: '密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.Password)
.onChange((value: string) => {
this.ReservePassword = value;
})
TextInput({ placeholder: '新密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.NEW_PASSWORD) // 密码框使用 new Password 属性,可以触发生成强密码
.enableAutoFill(this.enAbleAutoFill)
.passwordRules('begin:[upper],special:[yes],len:[maxlen:32,minlen:12]')
.onChange((value: string) => {
this.ReservePassword = value;
})
Button('页面跳转')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.pathInfos.pushPathByName('register_result_page', null)
})
Button('页面跳转(跳转前关闭autofill)')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.enAbleAutoFill = false;
this.pathInfos.pushPathByName('register_result_page', null)
})
}
}
.navDestination(this.PageMap)
.height('100%')
.width('100%')
}
}
@Component
struct RegisterResultPage {
pathInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
Text("Result Page").commonTitleStyles()
}.width('100%').height('100%')
}.title("Result Page")
.onReady((context: NavDestinationContext) => {
this.pathInfos = context.pathStack;
})
}
}
@Extend(Text)
function commonTitleStyles() {
.fontSize(24)
.fontColor('#000000')
.fontWeight(FontWeight.Medium)
.margin({ top: 24, bottom: 16 })
}
@Extend(TextInput)
function commonInputStyles() {
.placeholderColor(0x182431)
.width('100%')
.opacity(0.6)
.placeholderFont({ size: 16, weight: FontWeight.Regular })
.margin({ top: 16 })
}
@Extend(Button)
function commonButtonStyles() {
.width('100%')
.height(40)
.borderRadius(20)
.margin({ top: 24 })
}
2.5 强密码填充
密码保险箱可以在用户需要输入一个新密码时,自动生成一个高强度的密码。用户选择使用生成的强密码时可以将这个密码填充到新密码输入框。
触发条件及注意事项:
- 已设置锁屏密码并且开启密码保险箱自动保存和填入账号和密码开关。
- 界面中必须同时存在type为InputType.USER_NAME(表示用户名输入框)和InputType.NEW_PASSWORD(表示新密码输入框)的TextInput输入框组件。
具体类型请参考输入框类型说明。
- TextInput组件的enableAutoFill属性的值为true(默认true)。
- 用户在界面中首次点击新密码输入框时触发强密码弹窗,用户点击使用密码按钮可以将弹窗中显示的强密码自动填充到新密码输入框。
- 开发者可以根据一定的规则和建议指定强密码生成规则。
2.5.1 注册
示例代码如下:
@Entry
@Component
struct RegisterExample {
pathInfos: NavPathStack = new NavPathStack();
@State ReserveAccount: string = '';
@State ReservePassword: string = '';
@State enAbleAutoFill: boolean = true;
onBackPress() {
// 当非成功登录、返回等页面跳转时将enAbleAutoFill设置为false,密码保险箱不使能
this.enAbleAutoFill = false;
return false;
}
@Builder
PageMap(name: string) {
if (name === 'register_result_page') {
RegisterResultPage()
}
}
build() {
Navigation(this.pathInfos) {
Column() {
Text("注册账号")
.commonTitleStyles()
TextInput({ placeholder: '用户名' })
.commonInputStyles()
.type(InputType.USER_NAME) // 账号框使用USER_NAME属性
.onChange((value: string) => {
this.ReserveAccount = value;
})
TextInput({ placeholder: '新密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.NEW_PASSWORD) // 密码框使用 new Password 属性,可以触发生成强密码
.enableAutoFill(this.enAbleAutoFill)
.passwordRules('begin:[upper],special:[yes],len:[maxlen:32,minlen:12]')
.onChange((value: string) => {
this.ReservePassword = value;
})
Button('页面跳转')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.pathInfos.pushPathByName('register_result_page', null)
})
Button('页面跳转(跳转前关闭autofill)')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.enAbleAutoFill = false;
this.pathInfos.pushPathByName('register_result_page', null)
})
}
}
.navDestination(this.PageMap)
.height('100%')
.width('100%')
}
}
@Component
struct RegisterResultPage {
pathInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
Text("Result Page").commonTitleStyles()
}.width('100%').height('100%')
}.title("Result Page")
.onReady((context: NavDestinationContext) => {
this.pathInfos = context.pathStack;
})
}
}
@Extend(Text)
function commonTitleStyles() {
.fontSize(24)
.fontColor('#000000')
.fontWeight(FontWeight.Medium)
.margin({ top: 24, bottom: 16 })
}
@Extend(TextInput)
function commonInputStyles() {
.placeholderColor(0x182431)
.width('100%')
.opacity(0.6)
.placeholderFont({ size: 16, weight: FontWeight.Regular })
.margin({ top: 16 })
}
@Extend(Button)
function commonButtonStyles() {
.width('100%')
.height(40)
.borderRadius(20)
.margin({ top: 24 })
}
2.5.2 修改密码
示例代码如下:
@Component
struct RegisterExample {
pathInfos: NavPathStack = new NavPathStack();
@State ReserveAccount: string = '';
@State ReservePassword: string = '';
@State enAbleAutoFill: boolean = true;
onBackPress() {
// 当非成功登录、返回等页面跳转时将enAbleAutoFill设置为false,密码保险箱不使能
this.enAbleAutoFill = false;
return false;
}
@Builder
PageMap(name: string) {
if (name === 'register_result_page') {
RegisterResultPage()
}
}
build() {
Navigation(this.pathInfos) {
Column() {
Text("注册账号")
.commonTitleStyles()
TextInput({ placeholder: '用户名' })
.commonInputStyles()
.type(InputType.USER_NAME) // 账号框使用USER_NAME属性
.onChange((value: string) => {
this.ReserveAccount = value;
})
TextInput({ placeholder: '密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.Password)
.onChange((value: string) => {
this.ReservePassword = value;
})
TextInput({ placeholder: '新密码' })
.showPasswordIcon(true)
.commonInputStyles()
.type(InputType.NEW_PASSWORD) // 密码框使用 new Password 属性,可以触发生成强密码
.enableAutoFill(this.enAbleAutoFill)
.passwordRules('begin:[upper],special:[yes],len:[maxlen:32,minlen:12]')
.onChange((value: string) => {
this.ReservePassword = value;
})
Button('页面跳转')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.pathInfos.pushPathByName('register_result_page', null)
})
Button('页面跳转(跳转前关闭autofill)')
.commonButtonStyles()
.enabled((this.ReserveAccount !== '') && (this.ReservePassword !== ''))
.onClick(() => {
this.enAbleAutoFill = false;
this.pathInfos.pushPathByName('register_result_page', null)
})
}
}
.navDestination(this.PageMap)
.height('100%')
.width('100%')
}
}
@Component
struct RegisterResultPage {
pathInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
Text("Result Page").commonTitleStyles()
}.width('100%').height('100%')
}.title("Result Page")
.onReady((context: NavDestinationContext) => {
this.pathInfos = context.pathStack;
})
}
}
@Extend(Text)
function commonTitleStyles() {
.fontSize(24)
.fontColor('#000000')
.fontWeight(FontWeight.Medium)
.margin({ top: 24, bottom: 16 })
}
@Extend(TextInput)
function commonInputStyles() {
.placeholderColor(0x182431)
.width('100%')
.opacity(0.6)
.placeholderFont({ size: 16, weight: FontWeight.Regular })
.margin({ top: 16 })
}
@Extend(Button)
function commonButtonStyles() {
.width('100%')
.height(40)
.borderRadius(20)
.margin({ top: 24 })
}
3 网页接入密码保险箱
网页中的登录表单,登录成功后,用户可将用户名和密码保存到鸿蒙系统密码保险箱中。再次打开该网页时,密码保险箱可以提供用户名、密码的自动填充。
3.1 手机使用场景
以下以华为开发者官方网站_创新从这里开始网站为例:
- 在网站中输入用户名、密码,登陆成功后,ArkWeb会提示将用户名和密码保存到密码保险箱中。
- 再次打开相同的网站,点击用户名或者密码框中时,会弹出密码保险箱的填充提示。
- 可以选择提示框中的用户名,通过认证,就能直接在网页中填入之前保存的用户名、密码。
- 点击“使用其他账号”,选择密码保险箱中保存的其他账号。认证后在网页中填入选择的用户名、密码。
- 点击“手动输入”或者提示框之外的地方,会弹出小艺输入法,会提示可用于密码填充的用户名和钥匙图标。
点击用户名可触发在网页中填入用户名、密码;点击钥匙图标,进入选择账号的界面。
3.2 2in1使用场景
以下以华为开发者官方网站_创新从这里开始网站为例:
- 在网站中输入用户名、密码,登陆成功后,ArkWeb会提示将用户名和密码保存到密码保险箱中。
- 再次打开相同的网站,点击用户名或者密码框中时,会弹出密码保险箱的下拉框.
- 选择下拉框中的用户名,通过认证,就能直接在网页中填入之前保存的用户名、密码。
- 也可以点击下拉框中的“使用其他账号”,选择密码保险箱中保存的其他账号。认证后在网页中填入选择的用户名、密码。
3.3 网页密码保存规格
1、ArkWeb依赖密码表单提交成功后,触发页面跳转到其他页面,才能触发密码保存。
2、Native应用通过ArkWeb实现H5登入,登录成功后请勿立即销毁ArkWeb实例,将无法提示密码保存。
3.4 网页密码表单规格
ArkWeb使用Chromium智能算法,自动识别网页中的用户名、密码元素。算法对用户名、密码表单的设计,有一定的约束。
3.5 推荐的密码登录表单
1. 使用静态的登录页面或登录表单元素,而不是通过js脚本在页面中动态插入<form>、<input>等表单元素。
2. 用户名密码输入框均使用<input>元素实现,并集成在同一个<form>内,默认可编辑,登录场景有且最多有一个type="password"类型的<input>元素。
3. 点击按钮触发登录,登录成功后,应当触发跳转到新的页面。
4. 用户名框携带autocomplete=“username”,携带id或name属性,并采用如下建议的值,便于算法推断用户名元素:
const char* const kUsernameLatin[] = {
"gatti", "uzantonomo", "solonanarana", "nombredeusuario",
"olumulo", "nomenusoris", "enwdefnyddiwr", "nomdutilisateur",
"lolowera", "notandanafn", "nomedeusuario", "vartotojovardas",
"username", "ahanjirimara", "gebruikersnaam", "numedeutilizator",
"brugernavn", "benotzernumm", "jinalamtumiaji", "erabiltzaileizena",
"brukernavn", "benutzername", "sunanmaiamfani", "foydalanuvchinomi",
"mosebedisi", "kasutajanimi", "ainmcleachdaidh", "igamalomsebenzisi",
"nomdusuari", "lomsebenzisi", "jenengpanganggo", "ingoakaiwhakamahi",
"nomeutente", "namapengguna"};
const char* const kUserLatin[] = {
"user", "wosuta", "gebruiker", "utilizator",
"usor", "notandi", "gumagamit", "vartotojas",
"fammi", "olumulo", "maiamfani", "cleachdaidh",
"utent", "pemakai", "mpampiasa", "umsebenzisi",
"bruger", "usuario", "panganggo", "utilisateur",
"bruker", "benotzer", "uporabnik", "doutilizador",
"numake", "benutzer", "covneegsiv", "erabiltzaile",
"usuari", "kasutaja", "defnyddiwr", "kaiwhakamahi",
"utente", "korisnik", "mosebedisi", "foydalanuvchi",
"uzanto", "pengguna", "mushandisi"};
const char* const kUsernameNonLatin[] = {
"用户名", "کاتيجونالو", "用戶名", "የተጠቃሚስም",
"логин", "اسمالمستخدم", "נאמען", "کاصارفکانام",
"ユーザ名", "όνομα χρήστη", "brûkersnamme", "корисничкоиме",
"nonitilizatè", "корисничкоиме", "ngaranpamaké", "ຊື່ຜູ້ໃຊ້",
"användarnamn", "యూజర్పేరు", "korisničkoime", "пайдаланушыаты",
"שםמשתמש", "ім'якористувача", "کارننوم", "хэрэглэгчийннэр",
"nomedeusuário", "имяпользователя", "têntruynhập", "பயனர்பெயர்",
"ainmúsáideora", "ชื่อผู้ใช้", "사용자이름", "імякарыстальніка", "lietotājvārds",
"потребителскоиме", "uporabniškoime", "колдонуучунунаты", "kullanıcıadı",
"පරිශීලකනාමය", "istifadəçiadı", "օգտագործողիանունը", "navêbikarhêner", "ಬಳಕೆದಾರಹೆಸರು",
"emriipërdoruesit", "वापरकर्तानाव", "käyttäjätunnus", "વપરાશકર્તાનામ", "felhasználónév",
"उपयोगकर्तानाम", "nazwaużytkownika", "ഉപയോക്തൃനാമം", "სახელი", "အသုံးပြုသူအမည်",
"نامکاربری", "प्रयोगकर्तानाम", "uživatelskéjméno", "ব্যবহারকারীরনাম",
"užívateľskémeno", "ឈ្មោះអ្នកប្រើប្រាស់"};
const char* const kUserNonLatin[] = {
"用户", "użytkownik", "tagatafaʻaaogā", "دکارونکيعکس",
"用戶", "užívateľ", "корисник", "карыстальнік",
"brûker", "kullanıcı", "истифода", "អ្នកប្រើ",
"ọrụ", "ተጠቃሚ", "באַניצער", "хэрэглэгчийн",
"يوزر", "istifadəçi", "ຜູ້ໃຊ້", "пользователь",
"صارف", "meahoʻohana", "потребител", "वापरकर्ता",
"uživatel", "ユーザー", "מִשׁתַמֵשׁ", "ผู้ใช้งาน",
"사용자", "bikaranîvan", "колдонуучу", "વપરાશકર્તા",
"përdorues", "ngườidùng", "корисникот", "उपयोगकर्ता",
"itilizatè", "χρήστης", "користувач", "օգտվողիանձնագիրը",
"használó", "faoiúsáideoir", "შესახებ", "ব্যবহারকারী",
"lietotājs", "பயனர்", "ಬಳಕೆದಾರ", "ഉപയോക്താവ്",
"کاربر", "యూజర్", "පරිශීලක", "प्रयोगकर्ता", "användare",
"المستعمل", "пайдаланушы", "အသုံးပြုသူကို", "käyttäjä"};
const char* const kTechnicalWords[] = {
"uid", "newtel", "uaccount", "regaccount", "ureg",
"loginid", "laddress", "accountreg", "regid", "regname",
"loginname", "membername", "uname", "ucreate", "loginmail",
"accountname", "umail", "loginreg", "accountid", "loginaccount",
"ulogin", "regemail", "newmobile", "accountlogin"};
const char* const kWeakWords[] = {"id", "login", "mail"};
5. 登录场景,密码框携带autocomplete=“current-password”。
6. 用户名框下面紧挨密码框,中间不要插入其他<input>元素(包括不可见的<input>)。
7. 静态页面中的用户名密码框不可见,则需要确保在静态页面中就存在,而不是跳转页面时插入密码表单。
【案例1】:
【案例2】:
3.6 不支持自动填充的密码登录表单类型
1. 初始页面内无用户名密码表单元素,点击登录跳转页面后,新增非<form>类型的用户名密码表单。
2. 密码输入框携带了autocomplete=“new-password”属性。
3. 用户名输入框type="number",验证码输入框type="number",无密码输入框。
4. 用户名和密码元素中间存在其他<input>元素,算法推断出的用户名元素,不符合用户预期。
5. 网页通过javacript脚本,变更了<input>元素的焦点或者修改<input>元素的value。
6. 用户名<input>元素上id、name、label内容中匹配到如下密码类型标识
const char* const kNegativeLatin[] = {
"pin", "parola", "wagwoord", "wachtwoord",
"fake", "parole", "givenname", "achinsinsi",
"token", "parool", "firstname", "facalfaire",
"fname", "lozinka", "pasahitza", "focalfaire",
"lname", "passord", "pasiwedhi", "iphasiwedi",
"geslo", "huahuna", "passwuert", "katalaluan",
"heslo", "fullname", "phasewete", "adgangskode",
"parol", "optional", "wachtwurd", "contrasenya",
"sandi", "lastname", "cyfrinair", "contrasinal",
"senha", "kupuhipa", "katasandi", "kalmarsirri",
"password", "loluszais", "tenimiafina",
"second", "passwort", "middlename", "paroladordine",
"codice", "pasvorto", "familyname", "inomboloyokuvula",
"modpas", "salasana", "motdepasse", "numeraeleiloaesesi",
"captcha"};
const char* const kNegativeNonLatin[] = {
"fjalëkalim", "የይለፍቃል", "كلمهالسر", "գաղտնաբառ",
"пароль", "পাসওয়ার্ড", "парола", "密码", "密碼",
"დაგავიწყდათ", "κωδικόςπρόσβασης", "પાસવર્ડ", "סיסמה",
"पासवर्ड", "jelszó", "lykilorð", "paswọọdụ",
"パスワード", "ಪಾಸ್ವರ್ಡ್", "пароль", "ការពាក្យសម្ងាត់",
"암호", "şîfre", "купуясөз", "ລະຫັດຜ່ານ",
"slaptažodis", "лозинка", "पासवर्ड", "нууцүг",
"စကားဝှက်ကို", "पासवर्ड", "رمز", "کلمهعبور",
"hasło", "пароль", "лозинка", "پاسورڊ",
"මුරපදය", "contraseña", "lösenord", "гузарвожа",
"கடவுச்சொல்", "పాస్వర్డ్", "รหัสผ่าน", "пароль",
"پاسورڈ", "mậtkhẩu", "פּאַראָל", "ọrọigbaniwọle"};
7. 用户名<input>元素的autocomplete="one-time-code"或者"cc-*",或者id、name属性上能正则匹配到如下one-time-code或者信用卡标识:
inline constexpr char16_t kOneTimePwdRe[] =
u"one.?time|sms.?(code|token|password|pwd|pass)";
inline constexpr char16_t kCardCvcRe[] =
u"verification|card.?identification|security.?code|card.?code"
u"|security.?value"
u"|security.?number|card.?pin|c-v-v"
u"|código de segurança" // pt-BR
u"|código de seguridad" // es-MX
u"|karten.?prüfn" // de-DE
u"|(?:cvn|cvv|cvc|csc|cvd|ccv)"
// We used to match "cid", but it is a substring of "cidade" (Portuguese for
// "city") and needs to be handled carefully.
u"|\\bcid\\b|cccid";
8. 页面加载完成,<input>的type属性不是"password",点击登录才变成"password"类型。
4 应用与网页共用账号密码
4.1 简介
密码保险箱支持在应用和网页中保存和填充账号密码,为了提供更好的密码管理体验,提供了应用和网页共用账号数据的能力。
当接入本能力后,触发填充能力将优先推荐当前应用/网页的保存的账号,如当前应用/网页没有保存的账号时,则会推荐相关联网页/应用的账号。
同时,选择密码时也会将关联网站/应用的密码展示为推荐密码。

4.2 适用场景
当应用和网页均存在账号密码登录场景,且已经接入密码保险箱能力的情况下,期望其中一方保存密码之后,能够直接在另一方进行使用时,可以通过本能力进行实现。
4.3 接入方式

应用及网页接入App Linking后绑定关联关系,密码保险箱将基于这个关系完成识别。
完成如下配置,即可实现共用密码的能力:
- 应用和网页均已接入密码保险箱自动填充能力。
- 应用和网页通过App Linking完成关联关系的绑定。
接入需完成三步:在AGC控制台开通App Linking服务 > 在开发者网站上关联应用 > 配置网址域名