# Session 认证机制
-
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。
-
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。
-
注意:千万不要使用 Cookie 存储重要且隐私的数据!
比如用户的身份信息、密码等。
# Session 安装
- 在 Express 项目中,只需要安装 express-session 中间件,即可在项目中使用 Session 认证
| npm install express-session |
# Session 配置
- 具体配置信息详情可查看:https://www.cnblogs.com/loaderman/p/11506682.html
| |
| const session = require('express-session') |
| |
| |
| app.use(session({ |
| secret: 'nekoaimer', |
| resave: false, |
| saveUninitialized: true |
| })) |
# JSON 配置文件
| { |
| "name": "20-express-session", |
| "version": "1.0.0", |
| "description": "", |
| "main": "01.js", |
| "scripts": { |
| "test": "echo \"Error: no test specified\" && exit 1" |
| }, |
| "keywords": [], |
| "author": "", |
| "license": "ISC", |
| "dependencies": { |
| "express": "^4.18.1", |
| "express-session": "^1.17.3" |
| } |
| } |
# Pages
# index.html
| <!DOCTYPE html> |
| <html lang="en"> |
| |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>后台主页</title> |
| <script src="./jquery.js"></script> |
| </head> |
| |
| <body> |
| <h1>首页</h1> |
| |
| <button id="btnLogout">退出登录</button> |
| |
| <script> |
| $(function () { |
| |
| |
| $.get('/api/username', function (res) { |
| |
| if (res.status != 0) { |
| alert('您尚未登录,请登录后再执行此操作!') |
| location.href = './login.html' |
| } else { |
| alert('欢迎您:' + res.username) |
| } |
| }) |
| |
| |
| $('#btnLogout').on('click', function () { |
| |
| $.post('/api/logout', function (res) { |
| if (res.status === 0) { |
| |
| location.href = './login.html' |
| } |
| }) |
| }) |
| }) |
| </script> |
| </body> |
| |
| </html> |
# login.html
- 这里记得引入
jQuery
文件,获取引入外部文件,用来发送网络请求
| <!DOCTYPE html> |
| <html lang="en"> |
| |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>登录页面</title> |
| <script src="./jquery.js"></script> |
| </head> |
| |
| <body> |
| <!-- 登录表单 --> |
| <form id="form1"> |
| <div>账号:<input type="text" name="username" autocomplete="off" /></div> |
| <div>密码:<input type="password" name="password" /></div> |
| <button>登录</button> |
| </form> |
| |
| <script> |
| $(function () { |
| |
| $('#form1').on('submit', function (e) { |
| |
| e.preventDefault() |
| |
| $.post('/api/login', $(this).serialize(), function (res) { |
| |
| if (res.status == 0) { |
| location.href = './index.html' |
| } else { |
| alert('登录失败!') |
| } |
| }) |
| }) |
| }) |
| </script> |
| </body> |
| |
| </html> |
# Session
# router.js
| const express = require('express') |
| |
| const router = express.Router() |
| |
| |
| router.get('/username', (req, res) => { |
| |
| if (!req.session.islogin) |
| return res.send({ status: 1, msg: 'fail' }) |
| |
| |
| |
| res.send({ |
| status: 0, |
| msg: 'success', |
| username: req.session.user.username |
| }) |
| }) |
| |
| |
| router.post('/login', (req, res) => { |
| |
| if (req.body.username !== 'saber' || req.body.password !== '2333') |
| return res.send({ status: 1, msg: '登陆失败' }) |
| |
| |
| |
| |
| req.session.user = req.body |
| |
| req.session.islogin = true |
| |
| res.send({ status: 0, msg: '登陆成功' }) |
| }) |
| |
| |
| router.post('/logout', (req, res) => { |
| |
| req.session.destroy() |
| |
| res.send({ |
| status: 0, |
| msg: '退出登录成功' |
| }) |
| }) |
| |
| module.exports = router |
# index.js
| |
| const session = require('express-session') |
| |
| const express = require('express') |
| |
| const router = require('./router') |
| |
| const app = express() |
| |
| |
| app.use(express.static('./pages')) |
| |
| |
| app.use(express.urlencoded({ extended: false })) |
| |
| |
| app.use(session({ |
| secret: 'nekoaimer', |
| resave: false, |
| saveUninitialized: true |
| })) |
| |
| app.use('/api', router) |
| |
| app.listen(8001, console.log(console, 'Express server running at http://127.0.0.1:8001')) |
# JWT
-
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。注意:
-
当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。
-
当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制。
-
JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案
-
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的 “.” 分隔,格式如下:
| eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjUyODU5MzUxLCJleHAiOjE2NTI4NTkzODF9.1NwJrUEawaU7VsVyL8-s1ybDkoHEn2QHhNhuhXkyDKM |
# JWT 使用方式
- 客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。
- 此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:
| Authorization: BEARER <token> |
# JWT 安装
jsonwebtoken
用于生成 JWT 字符串
express-jwt
用于将 JWT 字符串解析还原成 JSON 对象
| npm install jsonwebtoken express-jwt |
# 导入 JWT
| |
| const jwt = require('jsonwebtoken') |
| |
| |
| const expressJWT = require('express-jwt') |
# 定义 secret 密钥
- 为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的 secret 密钥:
- ①当生成 JWT 字符串的时候,需要使用 secret 密钥对用户的信息进行加密,最终得到加密好的 JWT 字符串
- ②当把 JWT 字符串解析还原成 JSON 对象的时候,需要使用 secret 密钥进行解密
| const secretKey = 'nekoaimer' |
# JWT 案例
| |
| const express = require('express') |
| |
| const app = express() |
| |
| |
| const jwt = require('jsonwebtoken') |
| const expressJWT = require('express-jwt') |
| |
| |
| const cors = require('cors') |
| app.use(cors()) |
| |
| |
| const bodyParser = require('body-parser') |
| app.use(bodyParser.urlencoded({ extended: false })) |
| |
| |
| const secretKey = 'nekoaimer' |
| |
| |
| |
| app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] })) |
| |
| |
| const router = express.Router() |
| |
| function Router(router, jwt) { |
| |
| router.post('/api/login', function (req, res) { |
| |
| const userinfo = req.body |
| |
| if (userinfo.username !== 'admin' || userinfo.password !== '2333') { |
| return res.send({ |
| status: 400, |
| message: '登录失败!', |
| }) |
| } |
| |
| |
| |
| |
| |
| |
| const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' }) |
| res.send({ |
| status: 200, |
| message: '登录成功!', |
| token: tokenStr, |
| }) |
| }) |
| |
| |
| router.get('/admin/getinfo', function (req, res) { |
| |
| console.log(req.user) |
| res.send({ |
| status: 200, |
| message: '获取用户信息成功!', |
| data: req.user, |
| }) |
| }) |
| return router |
| } |
| |
| |
| app.use(Router(router, jwt)) |
| |
| |
| app.use((err, req, res, next) => { |
| |
| if (err.name === 'UnauthorizedError') { |
| return res.send({ |
| status: 401, |
| message: '无效的token', |
| }) |
| } |
| res.send({ |
| status: 500, |
| message: '未知的错误', |
| }) |
| }) |
| |
| |
| app.listen(8001, function () { |
| console.log('Express server running at http://127.0.0.1:8001') |
| }) |