切换语言为:繁体
Vue实现记住密码自动登录功能,基于Cookies实现

Vue实现记住密码自动登录功能,基于Cookies实现

  • 爱糖宝
  • 2024-07-17
  • 2125
  • 0
  • 0

Cookies 和localStorage存储比对

实现记住密码功能时,使用 Cookies 和使用 localStorage 各有其优势和考虑因素,具体需要取决于需求和安全考量:

1、Cookies 的优势:

  1. 广泛支持:Cookies 是 HTTP 协议的一部分,因此在几乎所有的浏览器和 Web 应用程序中都有良好的支持。

  2. 服务器端处理:可以通过设置 Cookie 的过期时间来实现自动登录功能,因为浏览器会在过期时间之前自动发送该 Cookie。这使得记住密码的实现变得相对简单。

  3. 可配置性:可以通过设置 Cookie 的属性(如过期时间、域名、路径等)来控制其在不同情况下的行为,例如只在 HTTPS 连接中发送、只在特定域名下可用等。

  4. 与服务器端状态同步:在某些情况下,特别是涉及到跨站点状态共享或服务器端状态跟踪时,Cookies 可以更自然地与服务器端的状态同步。

2、localStorage 的优势:

  1. 本地存储:localStorage 存储在用户的浏览器中,相比于 Cookies,更难以通过网络请求访问,从而提供了更好的安全性。

  2. 容量较大:localStorage 的存储容量通常比 Cookies 大得多(大约5MB左右),这使得它更适合存储大量的用户信息或配置数据。

  3. 更简单的 API:使用 localStorage 比起操作 Cookies 更加简单和直观,因为它提供了简洁的 key-value 存储接口。

3、综合考虑:

  • 安全性:对于敏感数据如密码,localStorage 通常被认为比 Cookies 更安全,因为它不会在每个 HTTP 请求中自动发送给服务器。

  • 便捷性:Cookies 更适合需要与服务器端状态同步或通过 HTTP 请求进行状态管理的场景,而 localStorage 更适合客户端本地的长期数据存储。

  • 合规性:在 GDPR 等隐私法规的要求下,需要谨慎处理用户数据。Cookies 的使用受到更严格的监管和规范。

综上所述,选择使用 Cookies 还是 localStorage 实现记住密码功能取决于具体需求,特别是安全性、数据大小和与服务器的交互方式等方面的考量。

4、用户登录信息加密

因为每个 Cookie 的大小限制在 4KB 到 8KB 之间。这包括了 Cookie 的名称、值以及其他元数据(如域名、路径、过期时间等)。浏览器对单个域名可以存储的 Cookie 数量也有限制,通常在几百个到几千个之间。

所以对用户信息进行前端cookies进行存储时,需要提取关键信息,此方案实现只对用户密码进行公钥加密,对用户其他的非关键信息进行自定义加密函数

5、主要加密函数示例

// auth.js
import Cookies from 'js-cookie';
import CryptoJS from 'crypto-js';
const Base64 = require('js-base64').Base64;
const EncryptionKey = 'encryptionKey';
// 加密
export const encrypt = word => {
  if (word == '' || word === undefined) {
    return '';
  }
  word = Base64.encode(word);
  var key = CryptoJS.enc.Utf8.parse(EncryptionKey);
  var srcs = CryptoJS.enc.Utf8.parse(word);
  var encrypted = CryptoJS.AES.encrypt(srcs, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  });
  return Base64.encode(encrypted.toString());
};

// 解密
export const decrypt = word => {
  if (word == '' || word === undefined) {
    return '';
  }
  word = Base64.decode(word);
  var key = CryptoJS.enc.Utf8.parse(EncryptionKey);
  var decrypt = CryptoJS.AES.decrypt(word, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  });
  return Base64.decode(CryptoJS.enc.Utf8.stringify(decrypt).toString());
};

export function setCookie(name, value = '', seconds) {
  let expires = new Date(new Date() * 1 + seconds * 1000);
  Cookies.set(name, value, { expires });
}

export function getCookieByName(name) {
  return Cookies.get(name);
}

// 设置记住密码信息
export function setRememberInfo(info) {
  const newInfo = JSON.parse(JSON.stringify(info));
  const ecKey = 'abcdefg1234567xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // 公钥加密KEY
  newInfo.pwd = encryptFn(ecKey, newInfo.pwd);
  const encryptInfo = encrypt(JSON.stringify(newInfo));
  setCookie('rememberInfo_' + newInfo.deptId, encryptInfo, 2592000);
}

// 公钥密码加密
export function encryptFn(publicKey, data) {
  const crypto = require('crypto');
  // 使用公钥加密
  const publicKeyStr = '-----BEGIN PUBLIC KEY-----\n' + publicKey + '\n' + '-----END PUBLIC KEY-----';
  const encryptStr = crypto.publicEncrypt(
    { key: publicKeyStr, padding: crypto.constants.RSA_PKCS1_PADDING },
    Buffer.from(data, 'utf8')
  );
  const encrypted = encryptStr.toString('base64');
  return encrypted;
}

6、逻辑处理

1、在用户登录判断是否选了记住密码,如果勾选进行正常流程登录,并将用户信息记录用加密函数对主要信息进行信息加密存储在cookies中,看需要设置过期时间等其他cookies配置项。

2、用户重新进入登录页时、判断cookies中是否存在对应的用户信息,如果存在进行解密。拿出用户信息。

3、拿到的用户信息存在公钥加密的关键信息,比如用户密码,需要后端提供接口传递用户信息参数,后端使用私钥解密,校验用户信息是否匹配。如果匹配,一般接口返回包含token的成功响应。

4、已经拿到token了,就可以进行正常登录的后续流程实现自动登录了。

5、退出登录时,需要清除对应Cookies。

7、自动登录流程图

Vue实现记住密码自动登录功能,基于Cookies实现

8、完整代码示例

只包含关键处理

<template>
  <!-- 登录首页 -->
  <div>
    <!-- 登录表单 -->
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" auto-complete="on" label-position="left">
      <el-form-item prop="userName">
        <el-input placeholder="请输入用户名" v-model="loginForm.userName">
          <i slot="prefix" class="el-input__icon iconfont icon-yonghu-filled"></i>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input placeholder="请输入密码" type="password" v-model="loginForm.password">
          <i slot="prefix" class="el-input__icon iconfont icon-mima-filled"></i>
        </el-input>
      </el-form-item>
      <el-form-item prop="captcha" v-if="captchaType == '2'">
        <el-input placeholder="请输入验证码" v-model="loginForm.captcha" maxlength="10">
          <i slot="prefix" class="el-input__icon iconfont icon-yanzheng-filled"></i>
          <img class="image_code" slot="suffix" :src="verifyKey" alt />
        </el-input>
      </el-form-item>
    </el-form>
    <div>
      <el-checkbox v-model="isRemember">记住密码</el-checkbox>
    </div>
    <el-button type="primary" @click.prevent="loginRequest()">登录</el-button>
  </div>
</template>

<script>
import { loginRequest } from '@/api/user';
import { setToken, decrypt, encrypt, setRememberInfo } from '@/utils/auth';
import { getLoginInfoByRember } from '@/api/public/index';

export default {
  data() {
    return {
      deptId: null,
      loginForm: {
        userName: '',
        password: '',
        captchaKey: '',
        captcha: '',
        keyId: ''
      },
      loginRules: {
        userName: [{ required: true, trigger: 'blur', message: '请输入用户名' }],
        password: [{ required: true, trigger: 'blur', message: '请输入密码' }]
      },
      publicKey: null,
      isRemember: false, //是否勾选记住密码
      userParams: {} // 需要存储的登录信息
    };
  },
  created() {
    this.deptId = this.$route.query.deptId;
    const rmInfo = this.getCookieByName('rememberInfo_' + this.deptId);
    if (rmInfo) {
      this.isRemember = true;
      this.getRememberInfo(rmInfo);
    }
  },
  methods: {
    // 根据记住密码获取登录信息
    getRememberInfo(remberInfo) {
      remberInfo = JSON.parse(decrypt(remberInfo));
      this.loginForm.userName = remberInfo.userName;
      const params = {
        userName: remberInfo.userName,
        password: remberInfo.pwd, // remberInfo.pwd是使用公钥加密的
        deptId: this.deptId
        // ......
      };
      getLoginInfoByRember(params) // 此请求接口为后端提供、对前端加密的密码进行验证
        .then(res => {
          // 校验成功、处理登录逻辑
          // 一般返回携带Token的响应,例如{ result:token:"2e7d220f2f60492b8835189d2d6f3463", .... }
          this.loginAfterOperate(res);
        })
        .catch(error => {
          // 校验失败、处理失败逻辑
          this.loginErrorOperate(error);
        });
    },
    // 获取加密key
    initKey() {
      return initKey().then(res => {
        if (res.code === 1000) {
          this.publicKey = res.result.publicKey;
        }
      });
    },
    // 正常登录处理
    async loginRequest() {
      await this.initKey();
      const params = {
        userName: this.loginForm.userName.trim(),
        password: encrypt(this.publicKey, this.loginForm.password)
        // 其他登录参数......
      };
      loginRequest(params)
        .then(res => {
          if (this.isRemember) {
            this.userParams = { ...params, pwd: this.loginForm.password };
            setRememberInfo(this.userParams); // 存储用户登录信息Cookies
          }
          this.loginAfterOperate(res);
        })
        .catch(error => {
          this.loginErrorOperate(error);
        });
    },
    // 统一登录后处理
    async loginAfterOperate(res, params = null) {
      const { code, result } = res;
      if (code == 1000) {
        setToken(result.token);
        // 后续登录逻辑......
      }
    },
    // 统一登录异常处理
    async loginErrorOperate(error) {
      this.$message({ showClose: true, message: error.message, type: 'error', offset: 100 });
      // 其他逻辑......
    }
  }
};
</script>

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.