• Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单


    一、SSO介绍

    单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    在这里插入图片描述

    如图所示,图中有4个系统,分别是Application1、Application2、Application3、和SSO。Application1、Application2、Application3没有登录模块,而SSO只有登录模块,没有其他的业务模块,当Application1、Application2、Application3需要登录时,将跳到SSO系统,SSO系统完成登录,其他的应用系统也就随之登录了。这完全符合我们对单点登录(SSO)的定义。

    1、使用SSO的好处
    • 方便用户 用户使用应用系统时,能够一次登录,多次使用。用户不再需要每次输入用户名称和用户密码,也不需要牢记多套用户名称和用户密码。单点登录平台能够改善用户使用应用系统的体验。
    • 方便管理员 系统管理员只需要维护一套统一的用户账号,方便、简单。相比之下,系统管理员以前需要管理很多套的用户账号。每一个应用系统就有一套用户账号,不仅给管理上带来不方便,而且,也容易出现管理漏洞。
    • 简化应用系统开发 开发新的应用系统时,可以直接使用单点登录平台的用户认证服务,简化开发流程。单点登录平台通过提供统一的认证平台,实现单点登录。因此,应用系统并不需要开发用户认证程序。

    在这里插入图片描述

    二、中间件介绍

    1、Express

    Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。

    安装
    npm install express
    
    导入
    const express = require('express')
    
    使用
    const express = require('express')
    const app = express()
    const port = 3000
    
    app.get('/', (req, res) => {
      res.send('Hello World!')
    })
    
    app.post('/', (req, res) => {
      res.send('Got a POST request')
    })
    
    app.listen(port, () => {
      console.log(`Example app listening on port ${port}`)
    })
    
    2、cors

    cors 是 Express 的一个第三方中间件。通过安装和配置 cors 中间件,可以很方便地解决跨域问题。

    CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。

    安装
    npm install cors
    
    导入
    const cors = require('cors')
    
    配置
    • 启用所有 CORS 请求
    app.use(cors())
    
    • 指定URL配置
    app.use(cors({ origin: 'http://127.0.0.1:5500' }))
    
    • 为单个路由启用 CORS
    app.get('/data', cors(), (req, res) => {
      res.json({
        name: 'cors in node.js',
        language: 'JavaScript',
        server: 'Express.js',
      })
    })
    
    • 使用选项配置 CORS
    const options = {
      origin: 'http://127.0.0.1:5500',
      methods: 'GET, PUT',
    }
    app.use(cors(options))
    
    • 使用函数配置动态 CORS 源
    const options = {
      origin: dynamicConfiguration(),
      methods: 'GET, PUT',
    }
     
    const dynamicConfiguration = async (req) => {
      const db = getDB() // simulating database object
      let origin = await db.getOrigin(req.headers) //simulating fetching origin from DB based on the headers
      return origin
    }
     
    app.use(cors(options))
    
    3、express-session

    express-session中间件将会话数据存储在服务器上;它仅将会话标识(而非会话数据)保存在 cookie 中。从1.5.0版本开始, express-session不再依赖cookie-parser,直接通过req/res读取/写入;默认存储位置内存存储(服务器端)

    安装
    npm install express-session
    
    导入
    const session = require('express-session')
    
    配置
    app.use(session({
        secret: 'YOUR_SESSION_SECRET',//加密字符串。 使用该字符串来加密session数据,自定义
        resave: false,//强制保存session即使它并没有变化
        saveUninitialized: true,//强制将未初始化的session存储。当新建了一个session且未设定属性或值时,它就处于未初始化状态。
        cookie: {
            maxAge: 30 * 60 * 1000
        }
    }))
    
    使用
    //设置
    req.session.userName = userName;
    //获取
    const userName = req.session.userName
    
    4、jsonwebtoken

    JSON Web Token(JWT)是一种用于在web上传递信息的标准,它以JSON格式表示信息,通常用于身份验证和授权。

    JWT由三个部分组成:Header(头部)、Payload(负载)和Signature(签名)。它们用点号分隔开,形成了一个JWT令牌。

    在现代web应用中,用户身份认证是非常重要且必不可少的一环。而使用Node.js和Express框架,可以方便地实现用户身份认证。而在这个过程中,jsonwebtoken这个基于JWT协议的模块可以帮助我们实现安全且可靠的身份认证机制,可以让我们轻松地生成、解析和验证JWT。

    安装
    npm install jsonwebtoken
    
    导入
    const jwt = require('jsonwebtoken')
    
    使用
    // 生成token
    const token = jwt.sign({
        id: 'appId',
        name: 'zhangsan',
        secret: 'YOUR_SECRET_KEY'
    }, '123456', {
        expiresIn: '2h'
    })
        
    //验证token
    jwt.verify(token, 'shhhhh', (err, decoded) => {
      if (err) {
        console.error('无效的令牌');
      } else {
        // 使用解码后的令牌数据
        console.log(decoded);
      }
    });
    
    5、jwt和session对比
    对比因素JWTSession
    存储存储在客户端,不需要服务器保持会话状态。存储在服务器,需要服务器维护会话信息。
    安全性加密较严密,但如果token被窃取,攻击者可以任意使用。如果sessionID被窃取,攻击者可以冒充用户登陆。
    性能在每次请求时需要验证和解码token,性能较差。只需查找sessionID就能获取会话信息,性能较好。
    扩展性在多服务器或者跨域环境中更易扩展。在多服务器环境中需要同步session,扩展性较差。
    数据大小JWT的大小比sessionID大,因此需要更多的带宽。sessionID大小稳定,对带宽需求较小。
    到期时间可以为每个token设置不同的过期时间。所有session的过期时间通常相同。
    客户端存储位置可以存储在Cookie, LocalStorage, SessionStorage中存储在Cookie中。
    跨域问题无跨域问题,且对于移动应用而言友好。跨域问题复杂,需要服务器支持CORS。
    状态无状态,服务器不需要保存用户信息。有状态,服务器需要保存用户信息。
    使用场景用于认证和信息交换,尤其适合单页应用(SPA)和前后端分离的项目。主要用于记录用户状态,适配传统的后端渲染的Web服务。

    三、SSO实现方案

    1、安装依赖

    启动服务:express
    操作cookie:express-session
    生成token:jsonwebtoken
    解决跨域:cors

    npm install express
    npm install express-session
    npm install jsonwebtoken
    npm install cors
    
    2、结构

    vueA项目:使用vite创建项目
    vueB项目:使用vite创建项目
    nodejs端:server/index.js
    登录页面:login.html

    3、实现原理
    • 用户首次访问系统A或B时,需要进行登录。
    • 系统统A或B带着appId信息重定向登录页面。
    • 认证系统验证用户登录信息。
    • 验证通过后,设置session值,用户返回一个token。
    • 认证系统带着token重定向给系统A或B,得知用户是已登录状态。
    • 系统A或B正常进入系统。
    • 用户再访问另一个系统时。
    • 通过session值,得知用户是已登录状态。
    • 认证系统带着token重定向给系统。
    • 系统正常进入系统。

    在这里插入图片描述

    三、示例代码

    1、nodejs端 server/index.js
    import express from "express"
    import session from 'express-session'
    import fs from "node:fs"
    import cors from "cors"
    import jwt from 'jsonwebtoken'
    
    
    // 应用列表
    const appToMapUrl = {
        'fd8xIoDC': {
            url: 'http://localhost:5173',
            name: 'appA',
            secret: '123456',
            token: ''
        },
        'DDkq0YYh': {
            url: 'http://localhost:5174',
            name: 'appB',
            secret: '789102',
            token: ''
        }
    }
    
    
    // 创建服务器
    const app = express()
    
    
    // 解析post请求体
    app.use(express.json())
    
    // 跨域
    app.use(cors())
    
    // 创建session配置项,注册为express-session中间件
    app.use(session({
        secret: '123456',//加密字符串。 使用该字符串来加密session数据,自定义
        resave: false,//强制保存session即使它并没有变化
        saveUninitialized: true,//强制将未初始化的session存储。当新建了一个session且未设定属性或值时,它就处于未初始化状态。
        cookie: {
            maxAge: 30 * 60 * 1000
        }
    }))
    
    
    //获取token
    const getToken = (appId) => {
        const appInfo = appToMapUrl[appId]
        if (!appInfo) {
            return null;
        }
        // 生成token
        const token = jwt.sign({
            id: appId,
            name: appInfo.name,
            secret: appInfo.secret
        }, '123456', {
            expiresIn: 60 * 60
        })
        return token;
    }
    
    
    //是否登录
    app.get('/login', (req, res) => {
        const {appId} = req.query
        if (!appId) {
            return res.send('请输入appId')
        }
        // 判断是否登录
        if (req.session.userName) {
            let token
            if (appToMapUrl[appId].token) {
                // 获取token
                token = appToMapUrl[appId].token
            } else {
                // 生成token
                token = getToken(appId)
                // 存入appToMapUrl
                appToMapUrl[appId].token = token
            }
            // 跳转
            res.redirect(`${appToMapUrl[appId].url}?token=${token}`)
            return;
        } else {
            // 读取登录页面
            const html = fs.readFileSync('./login.html', 'utf-8')
            res.send(html)
        }
    })
    
    // 解析表单数据
    app.use(express.urlencoded({ extended: true }));
    
    // 登录
    app.post('/protected', (req, res) => {
        const {username,password,appId} = req.body
        if (username === 'admin' && password === '123456') {
            const token = getToken(appId);
            // 存入appToMapUrl
            appToMapUrl[appId].token = token;
            //存入session,证明已经登录
            req.session.userName = username;
            res.redirect(`${appToMapUrl[appId].url}?token=${token}`)
        } else {
            res.send('用户名或密码错误')
        }
    })
    
    
    // 监听端口
    app.listen(3000, () => {
        console.log('http://localhost:3000')
    })
    
    2、vueA项目app.vue
    
    
    
    
    
    
    3、vueB项目app.vue
    
    
    
    
    
    
    4、登录页面login.html
    
    
    
        
        
        登录
    
    
        

    登录页面

  • 相关阅读:
    Json Schema简介和Json Schema的高性能.net实现库 LateApexEarlySpeed.Json.Schema
    SMBMS系统_准备工作
    【K8S专栏】Kubernetes应用配置管理
    什么是全媒体整合营销?如何做好全媒体整合营销呢?
    后缀是SS的文件怎么打开
    java导出word并向导出的word中添加附件
    hdfs和yarn的常用命令
    4D5D影院设备发展前景7D互动影院体验馆应用
    【linux】【docker】docker的安装 + 拿到docker开发环境压缩包如何使用
    代码设计:C++ 一个CSV功能类(源码)
  • 原文地址:https://blog.csdn.net/shanghai597/article/details/139289318