vue基于Cookies实现记住密码自动登录功能
Cookies 和localStorage存储比对
实现记住密码功能时,使用 Cookies 和使用 localStorage 各有其优势和考虑因素,具体需要取决于需求和安全考量:
1、Cookies 的优势:
- 广泛支持:Cookies 是 HTTP 协议的一部分,因此在几乎所有的浏览器和 Web 应用程序中都有良好的支持。
- 服务器端处理:可以通过设置 Cookie 的过期时间来实现自动登录功能,因为浏览器会在过期时间之前自动发送该 Cookie。这使得记住密码的实现变得相对简单。
- 可配置性:可以通过设置 Cookie 的属性(如过期时间、域名、路径等)来控制其在不同情况下的行为,例如只在 HTTPS 连接中发送、只在特定域名下可用等。
- 与服务器端状态同步:在某些情况下,特别是涉及到跨站点状态共享或服务器端状态跟踪时,Cookies 可以更自然地与服务器端的状态同步。
2、localStorage 的优势:
- 本地存储:localStorage 存储在用户的浏览器中,相比于 Cookies,更难以通过网络请求访问,从而提供了更好的安全性。
- 容量较大:localStorage 的存储容量通常比 Cookies 大得多(大约5MB左右),这使得它更适合存储大量的用户信息或配置数据。
- 更简单的 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、自动登录流程图
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>
本文由博客一文多发平台 OpenWrite 发布!