本文讲解在node平台下,使用express web服务框架,通过cookie和session以及封装express中间件,实现用户登录信息的保存(下次打开网站自动登录功能)。
我们先介绍功能的实现思路,后面再具体讲解代码实现的细节问题。
思路:首先是用户登录. 用户登录成功以后,express中我们将用户登录标识保存到session中,比如用户的token。每次前端请求express服务端的时候,我们都要判断session中是否有用户登录的标识,如果有,代表当前用户是登录状态。如果没有,则代表用户当前没有登录,将用户登录界面响应给前端。由于session的生命周期,当我们关闭浏览器以后,session就会销毁,所以无法做到保存用户登录状态信息。我们就需要通过cookie来保存用户账号信息,并且设置cookie的有效期,cookie过了有效期以后,就会销毁。
经过上面总结,实现步骤为:
1.用户登录成功,将登录成功的标识(比如token)保存到session中,并且将用户名和密码经过加密(防止cookie被盗取,用户名和密码泄露)保存到cookie中。
2.每次前端请求express服务端接口的时候
a:服务端要判断session中有没有登录成功的标识(比如token),如果有,则说明用户已经登录,将结果返回给用户。
b:如果session中没有登录成功的标识,那么再去cookie中检查是否有当初用户登录成功以后保存的经过加密的用户名和密码,
c:如果有,我们将加密的用户名和密码进行解密,获取真实的用户名和密码,去进行登录。
d:如果登录成功,则把成功的标识再次保存到session中,说明用户登录成功了。
e:如果解密后的用户名和密码,进行登录,没有登录成功。那么说明cookie中保存的信息不对,可能是被人恶意篡改,此时,服务端认为此时用户没有登录,返回给前端用户登录页面。
f:如果cookie中没有用户信息。那么认为用户没有登录。
以上步骤对应的流程图:
首先大家读懂了上面的实现流程,接下来我们再看express中代码的具体实现。
技术点包括:
1.express中cookie,session的使用
2.node中crypto库用来对用户名和密码实现加密解密
3.通过express中间件实现所有接口均实现用户是否登录检查
4.前端部分,vue通过axios中请求拦截实现未登录路由跳转
服务端处理用户登录的逻辑:
exports.login = async (req, res, next) => {
// 在请求体里面获取客服端传递过来的参数,
//我们需要配置express.json中间件,才能获取到请求体的参数。
//具体使用方法见下段代码
const {username, password} = req.body
try {
const loginRes = await request({
url: 'xxx/login',
method: 'POST',
data: {
username,
password
}
})
//如果 获取到用户的token表明登录成功
if (loginRes.data.auth_token.length > 0) {
// login success 记录session保存用户登录状态
req.session.auth_token = loginRes.data.auth_token
// 把 用户名 和 密码 经过加密(对称加密)以后, 保存到cookie中, 实现保持用户登录状态
// 对用户名和密码 进行加密, 防止cookie被盗取 破解
// encrypt 方法的封装 见下段代码
const encryptUser = encrypt(JSON.stringify({
username,
password
}))
// cookie存储的形式为键值对,所以 第一个参数 为键, 第二个值为value。
//需要配置cookie中间件,具体写法见下面
res.cookie('user', encryptUser, {
maxAge: 1000 * 60 * 60 * 24 * 7 //设置cookie的保存时间,7天为例
})
res.status(200).json({
'code': 0,
'msg': 'ok'
})
} else {
res.status(200).json({
'code': 1,
'msg': 'auth_token is null'
})
}
} catch (e) {
res.status(200).json({
'code': 2,
'msg': '账号或密码错误'
})
}
}
配置express.json和express.urlencode中间件,用来解析请求体的参数以及url中的参数
const express = require('express')
const cookieParser = require('cookie-parser') // npm i cookie-parser -S
const app = express()
// session
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
// 配置解析 Cookie 的中间件
app.use(cookieParser())
// 解析请求体
app.use(express.json())
app.use(express.urlencoded({
extended: true
}))
node平台下crypto加密库的使用
const crypto = require('crypto')
exports.encrypt = data => {
// createCipher函数接收两个参数
// 第一个参数为 加密方式
//const ciphers = crypto.getCiphers();
//console.log(ciphers); // ['aes-128-cbc', 'aes-128-ccm', ...]
// 通过上述代码 可以获取到crypto支持的所有加密方式
//第二个参数为加密向量,也称为 加盐(绝对隐私,解密需要用到)
const cipher = crypto.createCipher('aes-256-cfb', 'aaron')
let encrypted = cipher.update(data, 'utf8', 'hex')
encrypted += cipher.final('hex')
return encrypted
}
exports.decrypt = (data) => {
// 解密数据 解密方式要和加密时使用的一致
const decipher = crypto.createDecipher('aes-256-cfb', 'aaron')
let decrypted = decipher.update(data, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}
经过上述操作,当用户登录成功以后,我们分别把token记录到session中,用户名和密码保存到cookie中。以后,当客户端请求其他接口服务的时候,代码的具体写法为:
// 获取登录时记录到session中的token
const { auth_token } = req.session
// 如果session 中没有登录信息记录 那么 再去cookie中获取 用户名和 密码
if (!auth_token) {
const { user: cookieUser } = req.cookies
if (!cookieUser) {
// cookie中 没有用户登录信息 说明 用户没有登录过
// 响应给客户端401错误
return res.status(401).json({
msg: 'not login'
})
}
//如果 cookie中有 加密的用户登录信息 解密 获得 用户名 和 密码 进行 登录
const decryptUser = JSON.parse(decrypt(cookieUser))
try {
const loginRes = await request({
url: 'xxx/login/',
method: 'POST',
data: {
username: decryptUser.username,
password: decryptUser.password
}
})
// 获取到token代表
if (loginRes.data.auth_token.length > 0) {
// cookie 信息 登录 成功 保存到session中
req.session.auth_token = loginRes.data.auth_token
//处理客户端请求(比如是获取用户详情,此时将用户详情信息返回给客户端)
} else {
// 登录失败 重新登录
return res.status(401).json({
msg: 'not login'
})
}
} catch (e) {
// 登录失败 重新登录
return res.status(401).json({
msg: 'not login'
})
}
else {
// 此时代表session中有token,说明客户端已经登录
//处理客户端请求(比如是获取用户详情,此时将用户详情信息返回给客户端)
}
具体检查用户是否登录逻辑就完成了,同学们可以根据自己业务的具体情况,对代码进行修改。总之,实现思路是通用的。
难道,我们在服务端的每个服务中都要写这么多的逻辑代码进行检测用户登录吗?首先每个服务确实都要检测用户是否登录,但是我们可以封装一个中间件,对客户端的请求进行统一的检测。接下来,请看封装后中间件的代码:
module.exports = async (req, res, next) => {
// 如果客户端访问的是Login服务,那么我们不需要检查用户是否登录
// 直接 return next() 传递到下一步, 也就是我们的登录服务的逻辑处理
// 比如此时用户请求的是获取用户详情接口(/api/userinfo),next()此时代表将事件传递给
//我们所写的(/api/userinfo)事件中
if (req.path === '/api/login') {
return next()
}
const { auth_token } = req.session
// 如果session 中没有登录信息记录 那么 再去cookie中获取 用户名和 密码
if (!auth_token) {
const { user: cookieUser } = req.cookies
if (!cookieUser) {
// cookie中 没有用户登录信息 说明 用户没有登录过
// 直接返回给客户端401
return res.status(401).json({
msg: 'not login'
})
}
//如果 cookie中有 加密的用户登录信息 解密 获得 用户名 和 密码 进行 登录
const decryptUser = JSON.parse(decrypt(cookieUser))
try {
const loginRes = await request({
url: 'xxx/login/',
method: 'POST',
data: {
username: decryptUser.username,
password: decryptUser.password
}
})
if (loginRes.data.auth_token.length > 0) {
// cookie 信息 登录 成功 保存到session中
req.session.auth_token = loginRes.data.auth_token
// next()传递到下一个处理逻辑。
return next()
} else {
// 登录失败 重新登录
return res.status(401).json({
msg: 'not login'
})
}
} catch (e) {
// 登录失败 重新登录
return res.status(401).json({
msg: 'not login'
})
}
}
// session中有用户登录标识,直接next()
return next()
}
中间件封装完毕以后,我们要向express中注入中间件,这样所有的客户端的请求,会先经过我们封装的这个中间件,如果中间件中next().那么才会走向下一个中间件。这样,就实现了对客户端所有的请求统一进行用户登录状态的验证。
const express = require('express')
const router = require('./router')
const checkSession = require('./middleware/checkSession')
const app = express()
//***重点注意, 我们封装的中间件 一定要在路由中间件之前use。
app.use(checkSession)
app.use(router)
app.listen(3000, () => {
console.log('api listening port 3000...')
})
上面的所有代码为express服务端需要处理的逻辑。然后我以前端vue使用axios为例,通过axios的请求拦截器,在前端对客户端返回的401进行路由跳转处理。
import axios from 'axios'
import router from '../router/index'
const instance = axios.create()
// interceptors 用于对所有http请求进行拦截
instance.interceptors.response.use(res => {
return res
}, error => {
if (error.response) {
switch (error.response.status) {
case 401:
// 如果是401状态, 路由直接跳转到login界面
router.replace({
path: '/login'
})
}
}
// 将错误信息传递下去
return Promise.reject(error.response.data)
})
export default instance
这样,我们就实现了express服务端的用户登录状态的保存。
如有错误,请大家进行指正,谢谢!