在使用 Cookie + Session 实现鉴权功能之前,先来看看 Cookie 和 Session 是什么。

Cookie 是什么

cookie服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。它通常用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。

Cookie 的原理

当浏览器首次向服务器发送请求的时候,服务器会生成一份 cookie 数据,以 Set-Cookie 消息头的方式发送给客户端,浏览器一般都会自动将 cookie 数据存储起来。当浏览器再次访问同一服务器时,会以 Cookie 消息头的方式携带cookie数据发送给服务器,服务端就可以根据 cookie 数据来识别用户的身份或进行一些特别的处理并返回响应。

Session 是什么

Session字面含义就是会话。由于HTTP是无状态协议,为了保持浏览器与服务器之间的联系,才有了Session。Session就是用于在服务器端保存用户状态的协议。通常用来保存用户的登录状态。

Session 原理

  1. 客户端首次访问服务器时,服务器创建 Session,然后保存(Session 是保存在服务器端,通常是保存在内容中,当然也可以保存在文件、数据库等),然后给这个 Session 生成一个唯一的标识字符串,将 SessionId 设置到 Set-Cookie 响应头里
  2. 签名:通过md5等加密算方法对 SessionId 进行签名处理,这是为了避免客户端通过 document.cookie 修改 SessionId。(非必需步骤)
  3. 浏览器收到请求响应后会解析响应头,然后将SessionId 保存到 Cookie 中,以后的每次HTTP请求,SessionId 都会随着 Cookie 被传递到服务器。
  4. 服务器通过解析请求头 Cookie 中的SessionId,然后根据这个 SessionId 取到对应的Session信息,判断这个请求来自于哪个客户端/用户,从而判断该请求是否合法。

客户端

image.png
前端页面如上图

Login: 点击Login 按钮,将用户名和密码发送到服务端,服务端创建 session 并保存
Logout:点击 Logout 按钮,将清空 session
GetUser:点击 GetUser 按钮,从 session 中获取用户信息

前端代码如下:

  1. <html>
  2. <head>
  3. <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  4. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  5. <!-- 引入样式 -->
  6. <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
  7. <!-- 引入组件库 -->
  8. <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  9. </head>
  10. <body>
  11. <div id="app">
  12. <el-row style="margin-top: 15px ;">
  13. <el-input v-model="username" placeholder="请输入用户名"></el-input>
  14. </el-row>
  15. <el-row style="margin-top: 15px ;">
  16. <el-input v-model="password" show-password placeholder="请输入密码"></el-input>
  17. </el-row>
  18. <el-row style="margin-top: 15px ;">
  19. <el-button type="primary" plain v-on:click="login" size="small">Login</el-button>
  20. <el-button type="primary" plain v-on:click="logout" size="small">Logout</el-button>
  21. <el-button type="primary" plain v-on:click="getUser" size="small">GetUser</el-button>
  22. <el-button plain onclick="document.getElementById('log').innerHTML = ''" size="small">Clear Log</el-button>
  23. </el-row>
  24. </div>
  25. <h6 id="log"></h6>
  26. </div>
  27. <style>
  28. .el-input {
  29. width: 400px;
  30. }
  31. </style>
  32. <script>
  33. // axios.defaults.baseURL = 'http://localhost:3000'
  34. axios.defaults.withCredentials = true
  35. axios.interceptors.response.use(
  36. response => {
  37. document.getElementById('log').append(JSON.stringify(response.data))
  38. return response;
  39. }
  40. );
  41. var app = new Vue({
  42. el: '#app',
  43. data: {
  44. username: 'test',
  45. password: 'test'
  46. },
  47. methods: {
  48. async login() {
  49. await axios.post('/login', {
  50. username: this.username,
  51. password: this.password
  52. })
  53. },
  54. async logout() {
  55. await axios.post('/logout')
  56. },
  57. async getUser() {
  58. await axios.get('/getUser')
  59. }
  60. }
  61. });
  62. </script>
  63. </body>
  64. </html>

服务端

服务器端使用 koa 框架来创建,使用 koa-session 中间件来管理 cookie 和 session,将session 保存在 cookie 中。

我们实现一个中间件,用来实现鉴权的功能

  1. // 实现一个中间件,实现鉴权功能
  2. app.use((ctx, next) => {
  3. if (ctx.url.indexOf('login') > -1) {
  4. // 登录
  5. next()
  6. } else {
  7. console.log('session', ctx.session.userinfo)
  8. if (!ctx.session.userinfo) {
  9. ctx.body = {
  10. message: "登录失败"
  11. }
  12. } else {
  13. // 用户已经登录成功,执行下一个中间件
  14. next()
  15. }
  16. }
  17. })

下面是服务端完整代码:

  1. const Koa = require('koa')
  2. const router = require('koa-router')()
  3. const session = require('koa-session')
  4. const cors = require('koa2-cors')
  5. const bodyParser = require('koa-bodyparser')
  6. const static = require('koa-static')
  7. const app = new Koa();
  8. // 跨域请求处理
  9. app.use(cors({
  10. credentials: true
  11. }))
  12. app.keys = ['some secret'];
  13. app.use(static(__dirname + '/'));
  14. app.use(bodyParser())
  15. //配置session的中间件
  16. app.use(session(app));
  17. // 实现一个中间件,实现鉴权功能
  18. app.use((ctx, next) => {
  19. if (ctx.url.indexOf('login') > -1) {
  20. // 登录
  21. next()
  22. } else {
  23. console.log('session', ctx.session.userinfo)
  24. if (!ctx.session.userinfo) {
  25. ctx.body = {
  26. message: "登录失败"
  27. }
  28. } else {
  29. // 用户已经登录成功,执行下一个中间件
  30. next()
  31. }
  32. }
  33. })
  34. router.post('/login', async (ctx) => {
  35. const {
  36. body
  37. } = ctx.request
  38. console.log('body', body)
  39. // 登陆成功,创建Session并保存
  40. ctx.session.userinfo = body.username;
  41. ctx.body = {
  42. message: "登录成功"
  43. }
  44. })
  45. router.post('/logout', async (ctx) => {
  46. // 登出系统后,清除 Session
  47. delete ctx.session.userinfo
  48. ctx.body = {
  49. message: "登出系统"
  50. }
  51. })
  52. router.get('/getUser', async (ctx) => {
  53. // 从 Session 中获取用户信息
  54. ctx.body = {
  55. message: "获取数据成功",
  56. userinfo: ctx.session.userinfo
  57. }
  58. })
  59. app.use(router.routes());
  60. app.use(router.allowedMethods());
  61. app.listen(3000);