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
相关推荐
咬人喵喵8 分钟前
CSS Flexbox:拥有魔法的排版盒子
前端·css
LYFlied9 分钟前
TS-Loader 源码解析与自定义 Webpack Loader 开发指南
前端·webpack·node.js·编译·打包
yzp011210 分钟前
css收集
前端·css
暴富的Tdy10 分钟前
【Webpack 的核心应用场景】
前端·webpack·node.js
遇见很ok11 分钟前
Web Worker
前端·javascript·vue.js
风舞红枫13 分钟前
前端可配置权限规则案例
前端
zhougl99624 分钟前
前端模块化
前端
暴富暴富暴富啦啦啦40 分钟前
Map 缓存和拿取
前端·javascript·缓存
天问一40 分钟前
前端Vue使用js-audio-plugin实现录音功能
前端·javascript·vue.js
dodod201243 分钟前
Ubuntu24.04.3执行sudo apt install yarnpkg 命令失败的原因
java·服务器·前端