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

简介: Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)

Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一):https://developer.aliyun.com/article/1628445


三、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
<script setup lang="ts">
const token = location.search.split('token=')[1]
if (!token) {
  fetch('http://localhost:3000/login?appId=fd8xIoDC').then(res => {
    location.href = res.url
  })
} else {
  localStorage.setItem('token', token)
}
</script>

<template>
  <div>这里是appA</div>

</template>

<style scoped>
</style>
3、vueB项目app.vue
<script setup>
const token = location.search.split('token=')[1]
if (!token) {
  fetch('http://localhost:3000/login?appId=DDkq0YYh').then(res => {
    location.href = res.url
  })
} else {
  localStorage.setItem('token', token)
}
</script>

<template>
  <div>这里是appB</div>
</template>

<style scoped>
</style>
4、登录页面login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录</title>
</head>
<body>
    <div>
        <h1>登录页面</h1>
        <form action="/protected" method="post">
            <input type="text" name="username">
            <input type="password" name="password">
            <input type="hidden" name="appId">
            <input type="submit" value="登录">
        </form>
    </div>
    <script>
        const appId = location.search.split('?')[1].split('=')[1]
        document.querySelector('input[name="appId"]').value = appId
    </script>
</body>
</html>


目录
相关文章
|
5天前
|
存储 JSON JavaScript
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
27 0
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
83 2
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的医院综合管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的医院综合管理系统附带文章源码部署视频讲解等
42 5
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
102 4
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
76 4
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
66 4
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
85 4
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp宿舍管理系统的附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp宿舍管理系统的附带文章源码部署视频讲解等
77 3
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的家政平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的家政平台附带文章源码部署视频讲解等
57 3
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物医院系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物医院系统附带文章源码部署视频讲解等
48 2