关于登录这块应该是每一个系统中都必须的一个环节之一,虽然现在的第三方登录确实很流行,也很安全,但别人的始终是别人的。所以我们今天就来说一下如何使用原生的node实现客户端登录验证
// 解析cookie
req.cookie = {
}
const cookieStr = req.headers.cookie
cookieStr.spilt(";").forEach(item => {
if(!item){
return
}
const arr = item.split("=")
const key = arr[0]
const value = arr[1]
req.cookie[key] = value
})
经过如上的解析后我们便将cookie中的键值信息以对象的形式存入到了req.cookie中,后文中我们使用cookie信息便直接到req.cookie中去取得了
而在nodejs 中我们如何操作写入信息到cookie中,可以使用res.setHeader()方法去设置响应头中的cookie信息,如下所示:
res.setHeader('Set-Cookie', `userid=${
userId}; path=/; httpOnly; expires=${
getCookieExpires()};`)
其中Set-Cookie表示我们要往cookie中设置值, userid是我们之后写入到cookie中用于标识用户是否登陆的一段字符, path="/" 表示该cookie在当前的所有路径下均有效, httpOnly 表示的是限制客户端操作和修改cookie中的值, expires="" 用于设置cookie的过期时间,如果不进行设置,则该cookie将永久有效;(其中的getCookieExpires()是我定义的一个用于生成cookie过期时间的一个函数)
于是我们可以这样做,在用户访问我们的登录接口时,我们根据用户传递的登录信息,校验正确后往客户端cookie中写入一段userid, 之后的所有接口访问时我们就解析一下cookie中是否有这样一个userid来返回不一样的值: 如下所示:
// 定义一个全局的对象用于存储session
const SESSION_DATA = {
}
// 由于我们之前完成了对于cookie的解析,所以我们的req.cookie中便有了cookie的数据
let userId = req.cookie.userid
if(userId){
if(!SESSION_DATA[userId]){
SESSION_DATA[userId] = {
} // 判断SESSION_DATA中是否存放由userId对应的信息, 若没有则将其赋值为一个空对象
}
} else {
userId = `${
Date.now()}_${
Math.random()}` // 若userId不存在,则将其赋值为一个任意值: 一般的userId值都是需要经过加密处理的,这里我们为了方便直接就用一串时间戳加一个随机数来代替了
SESSION_DATA[userId] = {
} // 在创建一个userid的同时我们也往SESSION_DATA中存入了该userId的信息
}
// 将SESSION_DATA中的userId对应的信息放到req.session中
req.session = SESSION_DATA[userId]
// 这里我就简单仿制一份数据,就不到mysql中去拿了,同时为了避免post请求出现跨域问题,这里我就使用GET请求来模拟,将登录信息放到query中传递
if(req.method === "GET" && req.path === '/api/login'){
const {
username, password } = req.query
if(username === "zhangsan" && password === "123") {
// 这里由于我们上面已经将req.session 和 SESSION_DATA[userId]进行了绑定,所以可以直接往req.session中加入信息
req.session.username = username
res.setHeader('Set-Cookie', `userid=${
userId}; path=/; httpOnly; expires=${
getCookieExpires()};`)
res.end("登录成功")
}
res.end("登录失败")
}
这里我们引出了新的问题,就是我们不断的将session存入到全局变量SESSION_DATA中,这就存在一个很大的问题: 服务端运行时的变量信息都是存储在进程运行的内存中,所以当不断有用户登录时,SESSION_DATA中存储的信息就会越来越多,而我们知道,一个进行分配的内存空间是很有限的,当进程内存被占用完,进程运行也就崩溃了。 除此之外,还有一个问题是,多进程间的数据信息是无法共享的,所以当一个进程中存储了用户的userid后,当用户进入到另一个服务进程时是无法访问到其userid的
const redis = require('redis')
// 创建客户端连接: 参数1: redis端口, 参数2: 连接的域地址
const redisClient = redis.createClient(6379, '127.0.0.1')
// 监听错误事件
redisClient.on('error', (err) => {
console.log(err)
})
// 测试连接
// 使用set()方法往redis 中存入数据:redis中存储的数据域格式为key-value的形式
/**
* 参数1: 数据的键值(key)
* 参数2: 数据的值(value)
* 参数3: 传递一个redis.print表示数据存储成功后会打印出信息
*/
redisClient.set("myname", "chenshao", redis.print)
// 使用get()方法获取redis中指定键的值(key), 获取是数据是异步的
/**
* 参数1: 需要获取数据的键值(key)
* 参数2: 获取数据成功或者失败时的回调
*/
redisClient.get("myname", (err, val) => {
if(err){
throw err
}
console.log(val)
})
// 退出连接: 使用quit()函数关闭连接
redisClient.quit()
const redis = require('redis')
// 定义配置项,一般项目中均需要抽离出来的,这里我就直接写在这里了
const conf = {
port: 6379,
host: '127.0.0.1'
}
const redisClient = redis.createClient(conf.port, conf.host)
redisClient.on('error', (err) => {
console.log(err)
})
/*
* @params key: 存储的session的key; val: 存储的session的value值
*/
function set(key, val){
if(typeof val === 'object'){
val = JSON.stringify(val)
}
redisClient.set(key, val, redis.print)
}
/**
* @params key: 获取指定session的key值
*/
function get(key){
// 因为获取redis中的数据异步的,所以我们这里作为一个Promise返回
return new Promise((resolve, reject) => {
redisClient.get(key, (err, val) => {
if(err){
reject(err)
return
}
if(val === null){
resolve(null)
return
}
try{
resolve(JSON.parse(val))
} catch (ex) {
resolve(val)
}
})
})
}
module.exports = {
set,
get
}
以上代码不完善的地方大家可以自行完成,基本的关键步骤以上是都说到了的,文章中的一些描述错误或者打字错误深感抱歉,也希望大家帮助指正,谢谢