基于Microsoft Azure AD实现ReactJS+Node.js Express应用的SAML单点登录策略咨询
React + Node.js/Express 集成 Azure AD SAML 单点登录实战方案
我来帮你梳理下这套技术栈集成Azure AD SAML单点登录的完整思路,都是实战中验证过的方案,新手也能一步步跟着来:
一、整体流程先搞懂
整个SSO的核心逻辑是这样的:
- 用户打开你的React应用,访问需要登录的页面
- 前端跳转到后端的认证路由,后端引导用户跳转到Azure AD的登录页面
- 用户在Azure AD完成登录后,Azure会把加密的SAML断言发送到后端的回调地址
- 后端验证这个断言的合法性,确认用户身份后,创建会话或者生成认证令牌
- 后端引导用户回到前端,前端获取用户信息,完成登录状态同步
二、后端(Node.js/Express)配置步骤
1. 选对工具库
后端这边最省心的是用Passport生态里的passport-saml,毕竟Passport在Node.js认证场景下太成熟了,文档也全,踩坑的人多,问题也好找解决方案。另外还要搭配express-session来管理用户会话,或者用JWT替代会话,看你的需求。
先安装依赖:
npm install passport passport-saml express-session
2. 在Azure AD注册你的应用
这一步是配置身份提供商的关键,别漏了细节:
- 登录Azure门户,找到「Azure Active Directory」→「企业应用程序」→「新建应用程序」
- 选择「创建自己的应用」,命名后选择「集成任何其他应用程序(非库中的)」
- 进入应用后,找到「单点登录」→选择「SAML」
- 配置基础SAML设置:
- 标识符(实体ID):填你的应用唯一标识,比如
https://your-app-domain.com - 回复URL(ACS URL):填后端处理SAML回调的地址,比如
https://your-backend-domain/auth/saml/callback - 登录URL:填后端触发SSO的地址,比如
https://your-backend-domain/auth/saml
- 标识符(实体ID):填你的应用唯一标识,比如
- 从Azure AD获取关键信息:
- 「SAML签名证书」:下载Base64格式的证书,后面要用来验证断言
- 「登录URL」和「Azure AD标识符」:这两个是
passport-saml需要的entryPoint和issuer参数
3. 后端代码实现
配置Passport-SAML策略
在你的Express项目里创建认证配置文件,比如config/saml.js:
const passport = require('passport'); const SamlStrategy = require('passport-saml').Strategy; passport.serializeUser((user, done) => { done(null, user); }); passport.deserializeUser((user, done) => { done(null, user); }); passport.use(new SamlStrategy( { issuer: 'https://your-app-domain.com', // 对应Azure里的标识符 entryPoint: 'https://login.microsoftonline.com/{your-tenant-id}/saml2', // Azure的登录URL callbackUrl: 'https://your-backend-domain/auth/saml/callback', // ACS URL cert: '-----BEGIN CERTIFICATE-----\n{Azure下载的Base64证书内容}\n-----END CERTIFICATE-----', validateInResponseTo: false, // 新手可以先关闭,熟悉后再开启 disableRequestedAuthnContext: true }, (profile, done) => { // 这里可以处理用户信息,比如存到数据库,或者直接返回 // profile里包含Azure AD返回的用户属性:邮箱、姓名、用户ID等 return done(null, { id: profile.nameID, email: profile.email, name: profile.displayName }); } )); module.exports = passport;
配置Express路由和中间件
在主入口文件(比如app.js)里加这些:
const express = require('express'); const session = require('express-session'); const passport = require('./config/saml'); const app = express(); // 会话配置,生产环境要换更安全的存储(比如Redis),这里用内存存储仅做测试 app.use(session({ secret: 'your-strong-secret-key', resave: false, saveUninitialized: false, cookie: { secure: true, httpOnly: true } // 生产环境必须开启secure,用HTTPS })); app.use(passport.initialize()); app.use(passport.session()); // 触发SSO登录的路由 app.get('/auth/saml', passport.authenticate('saml')); // SAML回调处理路由 app.post('/auth/saml/callback', passport.authenticate('saml', { failureRedirect: '/login-failed' }), (req, res) => { // 登录成功后,跳回前端的首页或者之前请求的页面 // 也可以返回JWT给前端,这里示例跳转到前端 res.redirect('https://your-frontend-domain.com/dashboard'); } ); // 验证用户是否登录的中间件 const isAuthenticated = (req, res, next) => { if (req.isAuthenticated()) { return next(); } res.redirect('/auth/saml'); }; // 受保护的API示例 app.get('/api/user', isAuthenticated, (req, res) => { res.json(req.user); });
三、前端(React)配置步骤
1. 路由保护
用React Router创建私有路由组件,确保未登录用户不能访问受保护页面:
import { Navigate, Outlet } from 'react-router-dom'; const PrivateRoute = () => { // 这里可以通过调用后端的/api/user接口检查登录状态,或者从本地存储取token const isLoggedIn = localStorage.getItem('user') !== null; return isLoggedIn ? <Outlet /> : <Navigate to="/login" />; }; // 在路由配置中使用 <Routes> <Route path="/login" element={<LoginPage />} /> <Route element={<PrivateRoute />}> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/profile" element={<Profile />} /> </Route> </Routes>
2. 登录状态同步
在登录成功跳转回前端后,调用后端的/api/user接口获取用户信息,存储到本地:
// 比如在Dashboard组件的useEffect里 useEffect(() => { fetch('/api/user', { credentials: 'include' }) // 要带上Cookie,所以加credentials: 'include' .then(res => res.json()) .then(user => { localStorage.setItem('user', JSON.stringify(user)); // 更新前端状态 }) .catch(err => { // 登录状态失效,跳转到登录页 localStorage.removeItem('user'); window.location.href = '/login'; }); }, []);
3. 登出流程
前端提供登出按钮,触发后端的登出路由,后端再调用Azure AD的登出地址:
// 后端登出路由 app.get('/auth/logout', (req, res) => { req.logout(err => { if (err) return next(err); // Azure AD的登出URL,替换成你的租户ID和前端回调地址 const logoutUrl = `https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/logout?post_logout_redirect_uri=https://your-frontend-domain.com`; res.redirect(logoutUrl); }); }); // 前端登出按钮 const handleLogout = () => { localStorage.removeItem('user'); window.location.href = '/auth/logout'; };
四、新手容易踩的坑
- HTTPS必须开启:Azure AD要求回调地址必须是HTTPS,本地开发可以用ngrok或者mkcert生成自签名证书
- 证书格式要正确:Azure下载的Base64证书要包含
-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----,中间的换行不能丢 - 会话存储选择:生产环境不能用内存存储会话,要用Redis或者数据库,避免服务器重启后会话丢失
- 用户属性获取:如果需要更多用户属性,要在Azure AD的SAML配置里添加额外声明,不然profile里不会有
内容的提问来源于stack exchange,提问作者Yassir Bolles
相关产品推荐
相关产品推荐

