登录鉴权是业务系统中常见的功能:
当登录系统时,如果不是系统的内置用户,则需要提示并引导用户注册,如果是已经注册过的用户,在登录时要判断其输入的用户名和密码是否匹配,如匹配则跳转至首页,如不匹配,则提示用户名和密码错误重新输入;另一种场景是当登录安全性较高的系统时,假设长时间停在某一个页面未进行其他操作,比如某些银行的移动客户端,停留在某个页面三十分钟未进行操作,再次进入的时候会提示长时间未操作请重新登录,然后引导跳转至登录界面重新登录。
下面概述一下实现原理:
在数据库我们会默认留一个用户,并且分发给每个用户一个令牌,首次登录需要判断输入的用户名和密码是否正确,如不正确,需要提示用户输入正确的用户名和密码;如正确,判断令牌是否在有效时间内,如果有效则正常登录进行操作,如果失效则提示重新登录并生成新的令牌传给后端进行校验。
以上功能需要前后端配合实现,下面分别记录一下利用cookie和session实现校验以及利用JWT(JSONWebToken)来实现
一、cookie和session实现登录鉴权
cookie介绍:
Cookie是一种存储在客户端浏览器中的数据,它的作用是让浏览器记住一些信息,以便在后续的请求中使用。当浏览器访问某个网站时,它会发送一个 请求给服务器,请求的内容会包含一些信息,如用户ID、访问时间等。服务器在收到请求后会生成一个Cookie,并将它存储在客户端浏览器中。在后续的 请求中**,浏览器会将生成的Cookie再次发送给服务器**,服务器在接收到Cookie后,可以识别出它是之前发送过的请求,从而简化后续请求的处理流程,提 高网站的性能。
cookie是有大小限制的,它的大小取决于服务器和浏览器设置的具体限制,当cookie过大时会造成浏览器缓存过多,影响服务器的性能。
总的来说,Cookie的作用是提高网站的性能和用户体验,它可以让服务器更好地理解用户的行为,从而生成更个性化的内容,或提供更好的搜索和登录体 验。
总结:大小受限,存储在浏览器的客户端,可手动设置,会随每次请求传到服务端
session介绍:
Session是一种在Web应用程序中实现用户状态的方法。通过Session,Web应用程序可以在用户登录后将用户状态保存在客户端的浏览器中,以便在后续的请求中使用。具体来说,Session可以用于以下场景:
用户登录时保存用户信息:用户在登录时,服务器将用户信息保存到Session中,然后在后续的请求中使用这些信息。
保存用户信息:用户在浏览某个页面时,可以将一些信息(如登录状态、购物车内容等)保存在Session中,以便在后续的页面中使用。
记住用户的搜索历史:当用户在搜索框中输入关键字并点击搜索时,服务器会将用户的搜索历史保存在Session中,以便在用户后续的搜索请求中使用。
总的来说,Session可以提高Web应用程序的用户体验和性能,因为它可以让服务器记住用户的特定信息,并在后续的请求中使用这些信息,从而简化服务器端的工作流程。
在Web应用程序中,Session的大小限制通常取决于服务器端和客户端的具体设置。
服务器端:在服务器端,我们可以通过在Session中设置过期时间来限制其大小。通过设置过期时间,服务器可以确保在一定的时间内使用完保存的信息,从而避免过大的Session对服务器性能造成影响。
客户端:**在客户端,我们可以通过在浏览器设置Cookie大小时限来限制其大小。**通过设置Cookie大小时限,用户可以确保浏览器不会保存过多的信息,从而避免影响后续请求的性能。
总结:存储在客户端,方便存取,大小需要限制
实现流程
将每次用户登录的相关信息(用户名,id等)都存储在cookie中,随着请求传递到服务端,然后后端将这些信息单独存储在数据库中生成一个id, 将这个id返回给客户端,客户端将接收到的sessionID存储在localStorage或者sessionStorage中,随后在每次向服务端的请求中客户端都将读取并将该值写入cookie传递给服务端,服务端判断max-age和cookie对应关系的算法来判断传入的cookie是否是有效的,如果是则返回正确的数据,如果不是则返回401,提示重新登录。
由于暂时写的不是前后端分离的,我们依然利用ejs模板来写前端,后端采用插件:express-session
首先安装express-session: npm i express-session
1.在app.js中进行配置:
1.在app.js中进行配置:
// express-session引入
const expressSession = require('express-session')
//注册session中间件
// [注]:配置一定要放在路由和api文件之前
app.use(expressSession({
name: 'cjSystem', //名称
secret: 'secret',//密钥
cookie: {
maxAge: 1000 * 60 * 60 //一个小时
},
resave: true, //表示重新设置session之后,cookie的过期时间会重新计算,必须设置
saveUninitialized: true, //表示一开始就设置cookie,但是此时cookie是无效的,除非登录之后设置
secure: false,//值为true时表示只能在https中访问cookie,为false时表示可以在http中访问cookie
store:MongoStore.create({
mongoUrl:'mongodb://10.45.126.221:27017/cj_session',//新创建一个数据库 存储session
ttl: 1000 * 60 *60 //与上述cookie的时间保持一致
})
}
))
// 设置中间件: session过期校验
app.use((req,res,next) => {
//排除login相关的路由
if(req.url.includes('login')){
next()
}else if(req.session.user){
// 重新设置session,刷新cookie的有效时间
req.session.date = Date()
next()
}else{
//api是接口 返回500
//是路由
req.url.includes('api') ? res.status(401).send({ok: 1}):res.redirect('/login')
}
})
2.登录接口处理
loginUser:async (req,res) =>{
const data = await userService.loginUser(req,res)
if(data.length){
// 登录成功,设置session,req.session会自动匹配cookie生成的一个对象,挂载一些字段方便后来判断
// 默认存储在内存中: 当服务器重启之后内存被释放,导致session丢失
req.session.user = data[0]
res.send({data: 0})
}else{
res.send({data: 1})
}
},
3.绘制首页时进行判断:
var express = require('express');
var router = express.Router();
const JWT = require('../utils/jwt')
/* GET home page. */
router.get('/', function(req, res, next) {
// 判断req.session
if(req.session.user){
res.render('index', { title: 'Express' });
}else{
// res.render('login', { title: 'Express' });
res.redirect('/login')
}
res.render('index', { title: 'Express' });
// console.log(res.header)
// if(res.header.Authorization){
//
//
// }else{
// res.redirect('/login')
// }
});
最后在前端进行配合设置即可。
二、JWT实现鉴权
1.JWT简介:
JWT是一种用于在分布式系统中跟踪用户身份和授权的开放标准。JWT是由国际数据公司(IDC)制定的,旨在为用户提供安全、可靠的在线身份认证和授权服务。在实际应用中,JWT可用于多种场景,如用户登录、注册、访问API、支付等。JWT的使用可以提高系统的安全性和可扩展性,同时降低开发者的负担。
核心概念:
- Access Token:访问令牌是JWT的核心概念。它是一种数字身份证明,用于证明用户有权访问某个应用程序或系统的数据和功能。访问令牌包含有关用户身份和权限的信息,以及用于验证用户身份的令牌类型。
- Claims:声明是JWT中另一个重要的概念。它是指描述用户身份和访问权限的声明。声明包括用户ID、用户角色、权限等信息,用于验证访问令牌的有效性。
- Expiration Time:到期时间是JWT的一个重要属性。它指定了访问令牌的寿命,即在多长时间内该令牌将被认为是失效的。
- Signature:签名是JWT中的一个重要概念。它是指对数据进行签名,以验证数据的完整性和真实性。
- Token Generation Model:令牌生成模型是JWT中的另一个重要概念。它是指用于生成访问令牌的两种方法,即预先分配令牌和基于用户身份生成令牌。
- Subject:主体是JWT中的一个重要概念。它是指持有访问令牌的用户,也就是可以访问令牌所包含数据的用户。
- Role-Based Access Control:基于角色的访问控制是JWT中的一种访问控制方法。它是指根据用户角色来控制访问权限的方法。
- Protocol:协议是JWT中必不可少的一部分。它是指用于在JWT和应用程序之间传递数据的协议,如HTTP。
加密之后的token组成:
-
用户ID:这是JWT中非常重要的一个部分,用于标识用户身份。
-
令牌类型:这是一种标识符,用于指示该令牌用于什么用途。例如,表示该令牌用于访问某个数据。
-
有效期限:这是一种指示符,用于指示该令牌的有效期限。
-
签名:这是一种数字签名,用于验证该令牌的真实性和完整性。
-
数据:这是令牌中包含的数据,通常是一个JSON格式的字符串。
-
其他信息:包括令牌的序列号、创建时间等。
这些组成部分通常包括在JWT的加密过程中,以确保JWT的安全性和可靠性。
JWT的检验步骤:
-
检查JWT的有效期:在获取JWT时,需要检查JWT中是否包含有效期限。如果JWT中包含有效期限,则需要在使用JWT时检查JWT的有效期。
-
比较JWT的有效期和当前时间:检查JWT的有效期是否已经过期,如果已经过期,则需要重新生成JWT。
-
检查JWT的状态:在生成JWT时,通常会设置一个状态字段,用于指示JWT是否有效。在JWT使用过程中,需要检查JWT的状态,如果状态为无效,则需要重新生成JWT
通过以上步骤,可以确保JWT始终有效,从而提高JWT的安全性和可靠性。
JWT的简单使用:
//测试token的加密与验证
const jwt = require('jsonwebtoken')
const token = jwt.sign({
data:'foobar' //要加密的数据
},'secret',{expiresIn: '10s'}) //'secret'是加密的密钥
console.log(token)
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vYmFyIiwiaWF0IjoxNjkzMjkyODM2LCJleHAiOjE2OTMyOTI4NDZ9
// .70S7HLfs42OLASGP5b-dL3yeE7h0hxrIq0_lUZzYFiA
setTimeout(() => {
var decode = jwt.verify(token,'secret')
console.log(decode)
},11000)
const token = JWT.generate({data:'footbar'},10000)
const register = JWT.verify(token)
console.log(register) //true
实现流程
1.封装JWT
const jwt = require('jsonwebtoken')
const secret = 'secret'
const JWT = {
generate(value,expires){
return jwt.sign(value,secret,{expiresIn: expires}) //参数:加密数据,密钥,过期时间
},
verify(token){
try {
return !!jwt.verify(token,secret)
}catch {js
return false
}
}
}
module.exports = JWT
2.配置和使用
1.app.js中设置中间件进行配置
const JWT = require('./utils/jwt')
// 校验token
app.use((req, res, next) => {
if (req.url.includes('login')) {
next()
return
}
console.log(req.headers['authorization'])
const token = req.headers['authorization']?.split(' ')[1]
if (token) {
console.log(token)
const payload = JWT.verify(token)
if (payload) {
const newToken = JWT.generate({payload}, "10h")
next()
} else {
res.status(401).send({errorCode: -1})
}
// next()
} else {
next()
}
})
2.登录接口进行设置
// 登录校验
router.post('/login', (req, res) => {
const {username, password} = req.body
let temp = []
UserModel.find({username, password}).then(data => {
if (data.length > 0) {
//设置token
const token = JWT.generate({data: data[0]}, '10s')
// token放在header中
res.header('Authorization', token)
res.send({
data: 1
})
} else {
res.send({
data: 0
})
}
})
})
【注】:JWT只适用于前后端分离的开发场景中,当每个请求传递至服务端时,都会使用JWT重新校验并更新token,对应的前端每个接口都应获取并将token存储至session中,在每次请求中重新读取并携带传递给服务端。为方便起见,建议使用axios的拦截器功能,在请求拦截和响应拦截中都添加判断token的功能。简单实现如下:
<!-- 拦截器配置-->
// 添加请求拦截器: 请求之前所作的处理
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
const token = localStorage.getItem('token')
config.headers.Authorization = `Bearer ${token}`
return config;
}, function (error) {
// 对请求错误做些什么
console.log(error)
if (error.response.status === 401) {
localStorage.removeItem('token')
location.href('/')
}
return Promise.reject(error);
});
// 添加响应拦截器:请求成功后第一个调用的方法
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
const {authorization} = response.headers
authorization && localStorage.setItem('token', authorization)
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
别忘了安装axios库哦!
三、两种实现方式对比
cookie-session: cookie容易被伪造,形成CSRF(相关链接:https://blog.csdn.net/nnmmbb/article/details/106137473)
JWT: token比较安全,但是由于加密算法生成的token字段较长,传输更消耗流量一些,并且只适用于前后端分离的项目,对于服务端模板渲染的就无能为力了。