NodeJS Session&Token验证⑧
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
✨文章有误请指正如果觉得对你有用请点三连一波蟹蟹支持
⡖⠒⠒⠒⠤⢄⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⠀⠀⠀⡼⠀⠀⠀⠀ ⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢶⣲⡴⣗⣲⡦⢤⡏⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠋⠉⠉⠓⠛⠿⢷⣶⣦⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠇⠀⠀⠀⠀⠀⠀⠘⡇⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀⠀⠀⠀⠀⠀⢰⠇⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⡴⠊⠉⠳⡄⠀⢀⣀⣀⡀⠀⣸⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠃⠀⠰⠆⣿⡞⠉⠀⠀⠉⠲⡏⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⢧⡀⣀⡴⠛⡇⠀⠈⠃⠀⠀⡗⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣱⠃⡴⠙⠢⠤⣀⠤⡾⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⡇⣇⡼⠁⠀⠀⠀⠀⢰⠃⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣸⢠⣉⣀⡴⠙⠀⠀⠀⣼⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⡏⠀⠈⠁⠀⠀⠀⠀⢀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠃⠀⠀⠀⠀⠀⠀⠀⡼⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⣰⠃⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣀⠤⠚⣶⡀⢠⠄⡰⠃⣠⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⢀⣠⠔⣋⣷⣠⡞⠀⠉⠙⠛⠋⢩⡀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀
⠀⡏⢴⠋⠁⠀⣸⠁⠀⠀⠀⠀⠀ ⠀⣹⢦⣶⡛⠳⣄⠀⠀⠀⠀⠀
⠀⠙⣌⠳⣄⠀⡇ 不能 ⡏⠀⠀ ⠈⠳⡌⣦⠀⠀⠀⠀
⠀⠀⠈⢳⣈⣻⡇ 白嫖 ⢰⣇⣀⡠⠴⢊⡡⠋⠀⠀⠀⠀
⠀⠀⠀⠀⠳⢿⡇⠀⠀⠀⠀⠀⠀⢸⣻⣶⡶⠊⠁⠀⠀
⠀⠀⠀⠀⠀⢠⠟⠙⠓⠒⠒⠒⠒⢾⡛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣠⠏⠀⣸⠏⠉⠉⠳⣄⠀⠙⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⡰⠃⠀⡴⠃⠀⠀⠀⠀⠈⢦⡀⠈⠳⡄⠀⠀⠀⠀⠀⠀⠀
⠀⠀⣸⠳⣤⠎⠀⠀⠀⠀⠀⠀⠀⠀⠙⢄⡤⢯⡀⠀⠀⠀⠀⠀⠀
⠀⠐⡇⠸⡅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⡆⢳⠀⠀⠀⠀⠀⠀
⠀⠀⠹⡄⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣇⠸⡆⠀⠀⠀⠀⠀
⠀⠀⠀⠹⡄⢳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡀⣧⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢹⡤⠳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣷⠚⣆⠀⠀⠀⠀
⠀⠀⠀⡠⠊⠉⠉⢹⡀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡎⠉⠀⠙⢦⡀⠀
⠀⠀⠾⠤⠤⠶⠒⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠒⠲⠤⠽
前言
Node.js
是一个javascript运行环境。它让javascript可以开发后端程序
实现几乎其他后端语言实现的所有功能可以与```PHP、Java、Python、.NET、Ruby等后端语言平起平坐。- Nodejs是基于V8引擎V8是Google发布的开源JavaScript引擎本身就是用于Chrome浏览器的JS解释但是Node之父
Ryan Dahl
把这V8搬到了服务器上用于做服务器的软件。
登录鉴权Cookie&Session
鉴权说明 「HTTP 无状态」我们知道HTTP 是无状态的。也就是说HTTP 请求方和响应方间无法维护状态都是一次性的它不知道前后的请求都发生了什么。但有的场景下我们需要维护状态。最典型的一个用户登陆微博发布、关注、评论都应是在登录后的用户状态下的 「标记」
那解决办法是什么呢
Cookie&Session 图示 ↓
- 使用库 npm install express-session 、 npm install connect-mongo
- expressSession NPM https://www.npmjs.com/package/express-session
- connectmongo NPM https://www.npmjs.com/package/connect-mongo
ExpressSession中间件
//注册session中间件
app.use(session({
name: "先生", //session名字
secret: "serverz~qwer", //服务器生成 session 的签名
cookie: {
maxAge: 1000 * 60 * 60, //过期时间
secure: false // 为 true 时候表示只有 https 协议才能访问cookie
},
rolling: true, 为 true 表示 超时前刷新cookie 会重新计时 为 false 表示在超时前刷新多少次都是按照第一次刷新开始计时。
resave: true, //重新设置session后 会自动重新计算过期时间
saveUninitialized: true, 强制将为初始化的 session 存储
//通过connect-mongo 存储数据库、过期自动销毁、创建新的sesstion储存数据库
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/Oyande', //新创建了一个数据库
ttl: 1000 * 60 * 60 // 过期时间
})
}))
MVC演示
– 登录校验接口
//登录校验接口
router.post("/login", UserController.login)
router.get("/logout", UserController.logout)
APP.js管理
//设置中间件sesssion过期校验
app.use((req, res, next) => {
//排除login相关的路由和接口
if (req.url.includes("login")) {
next()
return
}
if (req.session.user) {
//重新设置以下sesssion
req.session.mydate = Date.now()
next()
} else {
//是接口, 返回错误码
//不是接口就重定向
req.url.includes("api")
? res.status(401).json({ ok: 0 }) : res.redirect("/login")
}
})
– M
login: (username, password) => {
return UserModel.find({ username, password })
}
– V
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>登录页面</h1>
<div>
<div>用户名:<input id="username" /></div>
<div>密码:<input type="password" id="password" /></div>
<div><button id="login">登录</button></div>
</div>
<script>
var username = document.querySelector("#username")
var password = document.querySelector("#password")
var login = document.querySelector("#login")
login.onclick = () => {
console.log(username.value, password.value)
fetch("/api/login", {
method: "POST",
body: JSON.stringify({
username: username.value,
password: password.value,
}),
headers: {
"Content-Type": "application/json"
}
}).then(res => res.json()).then(res => {
console.log(res)
if (res.ok === 1) {
location.href = "/"
} else {
alert("用户名密码不匹配")
}
})
}
</script>
</body>
</html>
– C
login: async (req, res) => {
const { username, password } = req.body
const data = await UserService.login(username, password)
if (data.length === 0) {
res.send({
ok: 0
})
} else {
//设置session {}
req.session.user = data[0] //设置session对象
//默认存在内存中。
res.send({
ok: 1
})
}
},
logout: (req, res) => {
req.session.destroy(() => {
res.send({ ok: 1 })
})
}
演示
-
缺点
- 内容过大时会裂开
- CSRF伪造
登录鉴权JSON Web Token (JWT)
-
session 缺点 占内存占空间容易跨平台伪造 CSRF伪造攻击
-
JSON Web Token (JWT) 使用步骤说明 session 换成 localstorage 数据储存在localstorage防止加密的数据
签名
被反推出来 需要 添加 密钥。
使用Session 图示
使用JSON Web Token (JWT) 图示
- 总结
-
当然 如果一个人的token 被别人偷走了 那也没办法会认为小偷就是合法用户 这其实和一个人的session id 被别人偷走是一样的。
-
这样一来 就不保存session id 只是生成token , 然后验证token 用CPU计算时间获取session 存储空间
-
解除了session id这个负担 可以说是无事一身轻 机器集群现在可以轻松地做水平扩展 用户访问量增大 直接加机器就行。 这种无状态的感觉实在是太好了
缺点
-
占带宽正常情况下要比 session_id 更大需要消耗更多流量挤占更多带宽假如你的网站每月有 10 万次的浏览器就意味着要多开销几十兆的流量。听起来并不多但日积月累也是不小一笔开销。实际上许多人会在 JWT 中存储的信息会更多
-
无法在服务端注销那么久很难解决劫持问题
-
性能问题JWT 的卖点之一就是加密签名由于这个特性接收方得以验证 JWT 是否有效且被信任。对于有着严格性能要求的 Web 应用这并不理想尤其对于单线程环境。
注意
-
CSRF攻击的原因是浏览器会自动带上cookie而不会带上token
-
以CSRF攻击为例 ↓
cookie用户点击了链接cookie未失效导致发起请求后后端以为是用户正常操作于是进行扣款操作token用户点击链接由于浏览器不会自动带上token所以即使发了请求后端的token验证不会通过所以不会进行扣款操作
-
使用库 Jsonwentoken : https://www.npmjs.com/package/jsonwebtoken
-
使用库 axios : https://www.npmjs.com/package/axios
Jsonwebtoken参数
sign 方法
–
说明sign方法用于生成一个jwt字符串该方法接收四个参数依次如
0、jsonwebtoken.sign(payload, secretOrPrivateKey, [options, callback]);
参数
1、接收一个对象用于传入用户身份信息
2、接收一个字符串作为jwt.signature的加密密钥
3、options包括以下选项
- algorithm加密算法默认值HS256
- expiresIn支持秒表示或描述时间跨度zeit / ms的字符串。如60“2 days”“10h”“7d“ 过期时间
- notBefore定义在什么时间之前该jwt都是不可用的。支持秒表示或描述时间跨度zeit / ms的字符串。如60“2days”“10h”“7d”
- issuerjwt签发者
- jwtid jwt的唯一身份标识主要用来作为一次性token,从而回避重放攻击
- subjectjwt所面向的用户
- noTimestamp
- header
- keyid
- mutatePayload
4、callback(err,token)生成jwt结束时执行的回调函数遵循nodejs的错误优先原则第一个参数是生成jwt过程抛出的异常信息第二个参数是成功生成的jwt字符串值
❗注意 : 需要注意的是一旦指定了sign方法的第四个参数回调函数则sign方法变为异步方法jwt字符串只能通过回调函数获取。
verify 方法
–
说明verify方法用于校验和解析jwt字符串该方法入参四个参数如
1、tokenjwt字符串
2、secret加密密钥
3、options配置对象 配置如下
- algorithms 签名加密算法默认为HS256
- audience 受众
- complete 默认为false表示只返回payload解密数据若为true表示返回{ payload, 1. header, signature }解密数据
- issuer 签发人
- ignoreExpiration if true do not validate the expiration of the token.
- ignoreNotBefore if true do not validate the not before of the token.
- subject 主题
- clockTolerance number of seconds to tolerate when checking the nbf and exp claims, to deal with small clock differences among different servers
- maxAge the maximum allowed age for tokens to still be valid.
- clockTimestamp the time in seconds that should be used as the current time for all necessary comparisons.
- nonce if you want to check nonce claim, provide a string value here. It is used on Open ID for the ID Tokens.
4、callback(err,decode)回调函数遵循nodejs错误优先原则第一个参数是异常信息第二个参数是根据jwt解码出来的数据
❗注意1 : 需要注意其中complete属性该属性为true则根据jwt字符串解码出来完整数据即包含header,payload,signature否则只包含payload
❗注意2 : 一旦verify传入callbackverify方法不再同步返回解码数据但是verify入参callback并不是异步执行的而是同步执行的。
封装JsonWebToken
规范文件路径 util/JWT.js
const jwt = require("jsonwebtoken")
const secret = "guoxiansheng"
const JWT = {
//获取的token 格式 XXX.XXX.XXX
generate(value, expires) {
return jwt.sign(value, secret, { expiresIn: expires }) //内容数据、密钥、token的时长
//第四个回调函数(err,token)=>{}
},
verify(token) {
try {
return jwt.verify(token, secret,{complete:false}) //加密的token 、 密钥
//第四个回调函数(err,token)=>{}
} catch (error) {
return false
}
}
}
module.exports = JWT
校验异常情况Verify
- NotBeforeError 该异常发生在jwt尚未生效时使用
- TokenExpiredError 该异常发生在jwt字符串校验成功但是已失效
- JsonWebTokenError 该异常发生在
规范文件路径 util/JWT.js
const jwt = require("jsonwebtoken")
const secret = "guoxiansheng"
const JWT = {
//获取的token 格式 XXX.XXX.XXX
generate(value, expires) {
return jwt.sign(value, secret, { expiresIn: expires,notBefore:'0.2h' }) //内容数据、密钥、token的时长
//第四个回调函数(err,token)=>{}
},
verify(token) {
try {
return jwt.verify(token, secret,{complete:false}) //加密的token 、 密钥
//第四个回调函数(err,token)=>{}
} catch (error) {
return false
}
}
}
module.exports = JWT
阿贾克斯Interceptors
<script>
//拦截器
//请求发出前执行的方法
axios.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});
//请求成功后 第一个调用的方法
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
</script>
代码实现步骤 演示
1、访问Login 获取打开登录页面
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
//拦截器
axios.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
const { authorization } = response.headers
authorization && localStorage.setItem("token", authorization)
return response;
}, function (error) {
return Promise.reject(error);
});
</script>
</head>
<body>
<h1>登录页面</h1>
<div>
<div>用户名:<input id="username" /></div>
<div>密码:<input type="password" id="password" /></div>
<div><button id="login">登录</button></div>
</div>
<script>
var username = document.querySelector("#username")
var password = document.querySelector("#password")
var login = document.querySelector("#login")
login.onclick = () => {
axios.post("/api/login", {
username: username.value,
password: password.value,
}).then(res => {
console.log(res.data)
if (res.data.ok === 1) {
location.href = "/"
} else {
alert("用户名密码不匹配")
}
})
}
</script>
</body>
</html>
接口
//使用到ejs
var express = require('express');
var router = express.Router();
router.get('/', function (req, res, next) {
res.render('login', { title: 'Express' });
});
module.exports = router;
2、输入登录密码完成登录登录成功设置token
登录判断
login: async (req, res) => {
const { username, password } = req.body
const data = await UserService.login(username, password)
if (data.length === 0) {
res.send({
ok: 0
})
} else {
//设置token
const token = JWT.generate({
_id: data[0]._id,
username: data[0].username
}, "1d")
//token返回在header
res.header("Authorization", token)
//默认存在内存中。
res.send({
ok: 1
})
}
},
3、登录成功后端设置请求拦截器成功前 设置localStorage token 并且返回响应头header
<script>
//拦截器
axios.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
const { authorization } = response.headers
authorization ?.localStorage.setItem("token", authorization)
return response;
}, function (error) {
return Promise.reject(error);
});
</script>
4、首页发送数据接口请求…
说明首页发送数据接口请求且 首页设置 “阿贾克斯” 请求拦截器请求前获取localStorage token 且 设置请求头headersapp.js(设置中间件判断token通过后返回响应头) , 请求成功前重新设置localStorage token出错移除token重定向到 /login
前端代码
<!DOCTYPE html>
<html>
<head>
<title>
首页
</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
//请求发出前执行的方法
axios.interceptors.request.use(function (config) {
const token = localStorage.getItem("token")
config.headers.Authorization = `Bearer ${token}`
return config;
}, function (error) {
return Promise.reject(error);
});
// 请求成功后 第一个调用的方法
axios.interceptors.response.use(function (response) {
const {
authorization
} = response.headers
authorization && localStorage.setItem("token", authorization)
return response;
}, function (error) {
if (error.response.status === 401) {
localStorage.removeItem("token")
location.href = "/login"
}
return Promise.reject(error);
});
</script>
</head>
<body>
<script>
//获取列表
axios.get("/api/user?page=1&limit=10").then(res => {
res = res.data
var tbody = document.querySelector("tbody")
tbody.innerHTML = res.map(item => `
<tr>
<td>${item._id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
</tr>
`).join("")
})
</script>
</body>
</html>
5、App 入口
//设置中间件token过期校验
app.use((req, res, next) => {
//排除login相关的路由和接口
if (req.url.includes("login")) {
next()
return
}
const token = req.headers["authorization"]?.split(" ")[1]
if (token) {
const payload = JWT.verify(token)
if (payload) {
//重新计算token过期时间
const newToken = JWT.generate({
_id: payload._id,
username: payload.username
}, "1d")
res.header("Authorization", newToken)
next()
} else {
res.status(401).send({ errCode: -1, errInfo: "token过期" })
}
} else {
next()
}
})
总结
以上是个人学习Node的相关知识点一点一滴的记录了下来有问题请评论区指正共同进步这才是我写文章的原因之如果这篇文章对您有帮助请三连支持一波
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |