前端实现记住密码

框架: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启用自动填充(但具体行为浏览器自己判断)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值