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>


目录
相关文章
|
28天前
|
前端开发 JavaScript 安全
HTML+CSS+JS密码灯登录表单
通过结合使用HTML、CSS和JavaScript,我们创建了一个带有密码强度指示器的登录表单。这不仅提高了用户体验,还帮助用户创建更安全的密码。希望本文的详细介绍和代码示例能帮助您在实际项目中实现类似功能,提升网站的安全性和用户友好性。
41 3
|
2月前
|
存储 JSON JavaScript
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
100 0
|
4月前
|
SQL Java 测试技术
在Spring boot中 使用JWT和过滤器实现登录认证
在Spring boot中 使用JWT和过滤器实现登录认证
261 0
|
2月前
|
JSON 安全 算法
|
7月前
|
安全 数据安全/隐私保护
Springboot+Spring security +jwt认证+动态授权
Springboot+Spring security +jwt认证+动态授权
210 0
|
2月前
|
存储 安全 Java
|
5月前
|
JSON 安全 Java
使用Spring Boot和JWT实现用户认证
使用Spring Boot和JWT实现用户认证
|
2月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
1月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
37 0
|
2月前
|
存储 JSON 算法
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)
文章介绍了JWT令牌的基础教程,包括其应用场景、组成部分、生成和校验方法,并在Springboot中使用JWT技术体系完成拦截器的实现。
114 0
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)