You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Microsoft Azure AD实现ReactJS+Node.js Express应用的SAML单点登录策略咨询

React + Node.js/Express 集成 Azure AD SAML 单点登录实战方案

我来帮你梳理下这套技术栈集成Azure AD SAML单点登录的完整思路,都是实战中验证过的方案,新手也能一步步跟着来:

一、整体流程先搞懂

整个SSO的核心逻辑是这样的:

  1. 用户打开你的React应用,访问需要登录的页面
  2. 前端跳转到后端的认证路由,后端引导用户跳转到Azure AD的登录页面
  3. 用户在Azure AD完成登录后,Azure会把加密的SAML断言发送到后端的回调地址
  4. 后端验证这个断言的合法性,确认用户身份后,创建会话或者生成认证令牌
  5. 后端引导用户回到前端,前端获取用户信息,完成登录状态同步

二、后端(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
  • 从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 02:59:10