框架:vue3+vite
组件:arco design
组件原有实现
明文存储在localStroge中
// 初始化
const loginConfig = useStorage('login-config', {
rememberMe: true,
// 演示默认值
userCode: null,
// demo default value
password: null,
})
// 从localStroge中获取值
const userInfo = reactive({
userCode: loginConfig.value.userCode,
password: loginConfig.value.password,
})
// 登陆之后保存值到localStroge
const { rememberMe } = loginConfig.value
const { userCode, password } = values
// 实际生产环境需要进行加密存储。
// The actual production environment requires encrypted storage.
loginConfig.value.userCode = rememberMe ? userCode : ''
loginConfig.value.password = rememberMe ? password : ''
问题:账号密码敏感信息明文存储在浏览器中
deepSeek回答1. 设备物理被盗风险
若电脑/手机丢失或被他人临时使用,攻击者可直接从浏览器保存的密码中获取所有账号的明文密码,无需任何技术手段。
2. 恶意软件窃取
键盘记录程序或间谍软件可能直接读取浏览器存储的密码文件(如Chrome的
Login Data
数据库)。部分恶意扩展程序可绕过权限限制,窃取浏览器保存的密码。
3. 浏览器漏洞利用
浏览器可能存在安全漏洞(如CVE-2023-3079等历史漏洞),攻击者可通过漏洞导出所有保存的密码。
即使主密码功能启用(如Firefox),也可能被绕过。
4. 跨站点脚本攻击(XSS)
恶意网站可能利用XSS漏洞访问浏览器自动填充的密码字段,窃取敏感信息。
5. 同步功能的风险
浏览器云同步(如Chrome Sync、Firefox Sync)可能将密码明文同步到云端,若云端账户被攻破,所有密码将泄露。
部分浏览器默认加密同步数据,但加密强度依赖用户的主密码(如Firefox)。
6. 内部人员滥用
企业环境中,IT管理员或有设备访问权限的人员可能直接查看同事的浏览器保存密码。
7. 密码复用风险
用户可能在多个网站使用相同密码,一旦一个密码泄露,其他账户也会被连锁攻破。
修复
触发浏览器的保存密码功能
关键在于遵循标准的 HTML 表单行为,而不是依赖 Vue 框架或手动存储在 localStorage
。
浏览器记住密码依赖以下条件:
-
使用标准的
<form>
标签。 -
输入框使用标准的
type="text"
和type="password"
。 -
有明确的
name
属性。 -
自动填充未被禁用(如
autocomplete="off"
会关闭自动填充)。
实现
用原生的<form>标签包裹住登录的部分,尤其是登录的按钮要在原生的<form>表单层
<form
autocomplete="on"
class="out-form"
@keyup.enter="submitAndValidate"
@submit.prevent="submitAndValidate"
>
<a-form
ref="loginForm"
:model="userInfo"
class="login-form"
layout="vertical"
>
<div
class="login-form-label font-grey font-size-14 font-family-ping-fang"
>{{ $t('login.form.label') }}</div
>
<a-form-item
field="userCode"
:rules="[
{ required: true, message: $t('login.form.userName.errMsg') },
{
message: $t('login.form.userName.matchMsg'),
match: /^[^\W_]{1,}$/,
},
]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input
v-model="userInfo.userCode"
name="username"
type="text"
autocomplete="username"
allow-clear
:max-length="20"
class="login-form-input"
:placeholder="$t('login.form.userName.placeholder')"
/>
</a-form-item>
<div
class="login-form-label font-grey font-size-14 font-family-ping-fang mt16"
>{{ $t('login.form.password') }}</div
>
<a-form-item
field="password"
:rules="[
{ required: true, message: $t('login.form.password.errMsg') },
]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input-password
v-model="userInfo.password"
name="password"
:autocomplete="
loginConfig.rememberMe ? 'current-password' : 'new-password'
"
:max-length="20"
class="login-form-input"
:placeholder="$t('login.form.password.placeholder')"
allow-clear
/>
</a-form-item>
</a-form>
<a-space direction="vertical">
<div class="login-form-password-actions">
<a-checkbox
checked="rememberMe"
:model-value="loginConfig.rememberMe"
@change="setRememberPassword"
>
<span class="font-grey font-size-14 font-family-ping-fang">{{
$t('login.form.rememberMe')
}}</span>
</a-checkbox>
<a-link
class="login-form-link font-grey font-size-14 font-family-ping-fang"
disabled
>
{{ $t('login.form.forgetPassword') }}
</a-link>
</div>
<a-button
type="primary"
html-type="submit"
class="login-form-btn font-size-14 ft18 font-family-ping-fang"
size="large"
long
:disabled="userInfo.password === '' || userInfo.userCode === ''"
:loading="loading"
>
{{ $t('login.form.login') }}
</a-button>
</a-space>
</form>
注意事项
浏览器依赖 name
来识别字段,所以输入框必须添加name
属性
autocomplete="off"
表示浏览器不保存密码
对密码输入框设置new-password
表示不填
密码框的autocomplete
值 | 含义 |
---|---|
off | 明确告诉浏览器禁用自动填充(包括记住密码提示) |
current-password | 表示这是一个当前登录的密码字段,允许填充和保存密码 |
new-password | 表示这是一个注册/重设密码字段,不触发保存密码提示,也不填充 |
on | 启用自动填充(但具体行为浏览器自己判断) |