Nodejs 第六十四章(SSO单点登录)

单点登录

单点登录(Single Sign-On,简称SSO)是一种身份认证和访问控制的机制,允许用户使用一组凭据(如用户名和密码)登录到多个应用程序或系统,而无需为每个应用程序单独提供凭据

SSO的主要优点包括:

  1. 用户友好性:用户只需登录一次,即可访问多个应用程序,提供了更好的用户体验和便利性。
  2. 提高安全性:通过集中的身份验证,可以减少密码泄露和密码管理问题。此外,SSO还可以与其他身份验证机制(如多因素身份验证)结合使用,提供更强的安全性。
  3. 简化管理:SSO可以减少管理员的工作量,因为他们不需要为每个应用程序单独管理用户凭据和权限。

举例说明

小满科技,小满教育,都是小满旗下的公司,那么我需要给每套系统做一套登录注册,人员管理吗,那太费劲了,于是使用SSO单点登录,只需要在任意一个应用登录过,其他应用便是免登录的一个效果,如果过期了,在重新登录

但是每个应用是不同的,登录用的是一套,这时候可以模仿一下微信小程序的生成一个AppId作为应用ID,并且还可以创建一个secret,因为每个应用的权限可以不一样,所以最后生成的token也不一样,还需要一个url,登录之后重定向到该应用的地址,正规做法需要有一个后台管理系统用来控制这些,注册应用,删除应用,这里节约时间就写死了。

代码编写

  1. 安装的依赖
  • express 启动服务编写接口
  • express-session 操作cookie
  • jsonwebtoken 生成token
  • cors 跨域
  1. 目录结构
  • vue A项目 用vite创建一个就好 npm init vite
  • react B项目 用vite创建一个就好 npm init vite
  • server/index.js nodejs端
  • sso.html 登录页面

server/index.js

js 复制代码
const appToMapUrl = {
     //A应用id
    'Rs6s2aHi': {
        url: "http://localhost:5173", //对应的应用地址
        secretKey: '%Y&*VGHJKLsjkas', //对应的secretKey
        token:"" //token
    },
    //B应用id
    '9LQ8Y3mB': {
        url: "http://localhost:5174", //对应的应用地址
        secretKey: '%Y&*FRTYGUHJIOKL', //对应的secretKey
        token:"" //token
    },
}

完整版代码

server/index.js

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 = {
    'Rs6s2aHi': {
        url: "http://localhost:5173",
        name:'vue',
        secretKey: '%Y&*VGHJKLsjkas',
        token: ""
    },
    '9LQ8Y3mB': {
        url: "http://localhost:5174",
        secretKey: '%Y&*FRTYGUHJIOKL',
        name:'react',
        token: ""
    },
}
const app = express()
app.use(cors())
app.use(express.json())
app.use(session({
    secret: "$%^&*()_+DFGHJKL",
    cookie: {
        maxAge: 1000 * 60 * 60 * 24 * 7, //过期时间
    }
}))
const genToken = (appId) => {
    return jwt.sign({ appId }, appToMapUrl[appId].secretKey)
}
app.get('/login', (req, res) => {
   //注意看逻辑 如果登陆过 就走if 没有登录过就走下面的
    if (req.session.username) {
    //登录过
        const appId = req.query.appId
        const url = appToMapUrl[appId].url
        let token;
        //登录过如果存过token就直接取 没有存过就生成一个 因为可能有多个引用A登录过读取Token   B没有登录过生成Token 存入映射表
        if (appToMapUrl[appId].token) {
            token = appToMapUrl[appId].token
        } else {
            token = genToken(appId)
            appToMapUrl[appId].token = token
        }
        res.redirect(url + '?token=' + token)
        return
    }
    //没有登录 返回一个登录页面html
    const html = fs.readFileSync(`../sso.html`, 'utf-8')
    //返回登录页面
    res.send(html)
})
//提供protectd get接口 重定向到目标地址
app.get('/protectd', (req, res) => {
    const { appId } = req.query //获取应用标识
    const url = appToMapUrl[appId].url //读取要跳转的地址
    const token = genToken(appId) //生成token
    req.session.username = username //存储用户名称 表示这个账号已经登录过了 下次无需登录
    appToMapUrl[appId].token = token //根据应用存入对应的token
    res.redirect(url + '?token=' + token) //定向到目标页面
})
//启动3000端口
app.listen(3000, () => {
    console.log('http://localhost:3000')
})

sso.html

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>
</head>

<body>
<!--这里会调用protectd接口 并且会传入 账号 密码 和 appId appId会从地址栏读取-->
    <form action="/protectd" method="get">
        <label for="username">
            账号:<input name="username" id="username" type="text">
        </label>
        <label for="password">密码:<input name="password" id="password" type="password"></label>
        <label for="appId"><input name="appId" value="" id="appId" type="hidden"></label>
        <button type="submit" id="button">登录</button>
    </form>
    <script>
       //读取AppId
        const appId = location.search.split('=')[1]
        document.getElementById('appId').value = appId
    </script>
</body>

</html>

A 应用这里用Vue展示 App.vue

html 复制代码
<template>
    <h1>vue3</h1>
</template>

<script setup lang='ts'>
//如果有token代表登录过了 如果没有跳转到 登录页面也就是SSO 那个页面,并且地址栏携带AppID
const token = location.search.split('=')[1]
if (!token) {
    fetch('http://localhost:3000/login?appId=Rs6s2aHi').then(res => {
        location.href = res.url
    })
}
</script>

<style></style>

B应用使用React演示 App.tsx

tsx 复制代码
import { useState } from 'react'
function App() {
  const [count, setCount] = useState(0)
  //逻辑其实一样的只是区分了不用应用的AppId
  const token = location.search.split('=')[1]
  if (!token) {
      fetch('http://localhost:3000/login?appId=9LQ8Y3mB').then(res => {
          location.href = res.url
      })
  }
  return (
    <>
     <h1>react</h1>
    </>
  )
}
export default App
相关推荐
10年前端老司机3 小时前
React无限级菜单:一个项目带你突破技术瓶颈
前端·javascript·react.js
阿芯爱编程8 小时前
2025前端面试题
前端·面试
前端小趴菜059 小时前
React - createPortal
前端·vue.js·react.js
晓13139 小时前
JavaScript加强篇——第四章 日期对象与DOM节点(基础)
开发语言·前端·javascript
菜包eo9 小时前
如何设置直播间的观看门槛,让直播间安全有效地运行?
前端·安全·音视频
烛阴10 小时前
JavaScript函数参数完全指南:从基础到高级技巧,一网打尽!
前端·javascript
chao_78911 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼11 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原11 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
popoxf12 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js