Open edX 本地 SAML 认证测试完全指南:使用 MockSAML 在 devstack 中配置与联调
【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform
本篇指南基于 openedx-platform 仓库中的官方 HOW-TO 文档(common/djangoapps/third_party_auth/docs/how_tos/testing_saml_locally.rst),详细讲解如何在本地 Open edX devstack 环境中,借助 MockSAML.com 这一免费测试身份提供商(IdP)搭建端到端的 SAML 单点登录链路。读完本文,你将掌握 SAMLConfiguration、SAMLProviderConfig、SAMLProviderData 三个核心配置对象的正确配置方法、背后硬编码约束的源码依据,以及完整的本地登录验证流程,可复用于其他真实 IdP 的接入调试。
为什么需要本地 SAML 测试
SAML(Security Assertion Markup Language)是教育机构接入单点登录的主流协议,Open edX 以 Service Provider(SP)身份与各类 Identity Provider(IdP)对接。真实 IdP(如学校统一认证平台)往往部署在受限网络内,联调周期长、日志不可见。MockSAML.com 提供了免费的 SAML 测试端点,能在本地 devstack 中模拟完整的 SP → IdP 重定向 → 断言回传 → 用户落库流程,是验证 Open edX 侧 SAML 配置是否正确的最快捷手段。
SAML 认证的三个核心配置对象
在 Open edX 中,SAML 认证的正常工作依赖三个配置对象协同(定义均位于 common/djangoapps/third_party_auth/models.py):
| 配置对象 | 模型类 | 职责 |
|---|---|---|
| SAMLConfiguration | SAMLConfiguration(ConfigurationModel)(models.py:473) | 定义本 Open edX 实例作为 SP 的元数据:实体 ID(entity_id)、密钥对(private_key/public_key)、组织信息(org_info_str)等 |
| SAMLProviderConfig | SAMLProviderConfig(ProviderConfig)(models.py:628) | 定义某个具体 IdP 的连接信息:实体 ID、元数据 URL(metadata_source)、属性映射(attr_email 等)、各种跳过选项 |
| SAMLProviderData | SAMLProviderData(models.Model)(models.py:925) | 存储从 IdP 元数据端点抓取到的运行时数据:SSO URL、签名公钥、有效期,仅在真实认证过程中使用 |
三者关系可以通过SAMLProviderConfig.get_config()(models.py:862-922)看得很清楚:该方法取出 ProviderConfig 上的属性映射字段,再以entity_id为键查询SAMLProviderData(models.py:898),将公钥与 SSO URL 组装进conf,最后通过self.saml_configuration or SAMLConfiguration.current(self.site.id, 'default')(models.py:917-919)挂上 SP 配置。
关键硬编码约束:
SAMLConfiguration对象的 slug必须为default。这个值被硬编码在认证执行路径中:当 ProviderConfig 未显式绑定 SAMLConfiguration 时,代码会回退调用SAMLConfiguration.current(self.site.id, 'default')(models.py:919);provider.py 中SAMLConfiguration.is_enabled(..., 'default')、saml.py 中SAMLConfiguration.current(..., 'default')以及 views.py 中saml_config = 'default'均以'default'作为默认查找键。slug 不匹配将导致认证路径找不到 SP 配置,直接报错或返回 404。
前置条件
- 本地 Open edX devstack 已启动并正常运行;
- 可访问 Django Admin 后台:http://localhost:18000/admin/ ;
- 注册一个 MockSAML.com 账号(免费的 SAML 测试服务);
- 了解 SAML 基础概念 中关于 SP/IdP 的角色划分。
Step 1:配置 SAMLConfiguration(SP 侧)
SAMLConfiguration将 Open edX 实例声明为一个 SAML Service Provider,是整套配置的地基。
- 进入 Django Admin →Third Party Auth → SAML Configurations;
- 点击Add SAML Configuration;
- 按下表填写必填字段:
| 字段 | 值 |
|---|---|
| Site | localhost:18000 |
| Slug | default(必须为default,代码中硬编码,见上文) |
| Entity ID | https://saml.example.com/entityid |
| Enabled | ✓(勾选) |
- 本地测试可留空密钥:字段
private_key与public_key留空即可。源码中SAMLConfiguration.get_setting()(models.py:592-609)在数据库字段为空时会回退读取 Django 设置SOCIAL_AUTH_SAML_SP_PUBLIC_CERT/SOCIAL_AUTH_SAML_SP_PRIVATE_KEY(slug 为default时),因此本地 MockSAML 场景下可以完全不配置密钥。若日后接入生产 IdP,可用openssl req -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.key生成密钥对并粘贴进去(见 models.py:500-519 的字段帮助文本); - Organization Info(可选)可保持默认或自定义为:
{ "en-US": { "url": "http://localhost:18000", "displayname": "Local Open edX", "name": "localhost" } }该 JSON 会被get_setting("ORG_INFO")(models.py:588-589)解析,写入 python-saml 生成的 SP 元数据的<md:Organization>节点;默认值为{"en-US": {"url": "http://www.example.com", "displayname": "Example Inc.", "name": "example"}}(models.py:523),字段定义中明确要求每个语言键下包含url、displayname、name三个子键;
- 点击Save保存。
Step 2:配置 SAMLProviderConfig(IdP 侧)
SAMLProviderConfig负责建立到具体 IdP(此处为 MockSAML)的连接。
- 进入 Django Admin →Third Party Auth → Provider Configuration (SAML IdP);
- 点击Add Provider Configuration (SAML IdP);
- 按下表填写:
| 字段 | 值 |
|---|---|
| Name | Test Localhost(或任意描述性名称) |
| Slug | default(与测试 URL 保持一致) |
| Backend Name | tpa-saml |
| Entity ID | https://saml.example.com/entityid |
| Metadata Source | https://mocksaml.com/api/saml/metadata |
| Site | localhost:18000 |
| SAML Configuration | 选择 Step 1 创建的 SAMLConfiguration |
| Enabled | ✓(勾选) |
| Visible | ☐(测试阶段不勾选,避免出现在登录页提供商列表中) |
| Skip hinted login dialog | ✓(勾选,推荐) |
| Skip registration form | ✓(勾选,推荐) |
| Skip email verification | ✓(勾选,推荐) |
| Send to registration first | ✓(勾选,推荐) |
- 属性映射全部留空以使用默认值(User ID、Email、Full Name 等):
SAMLProviderConfig上attr_user_permanent_id、attr_email、attr_full_name、attr_first_name、attr_last_name、attr_username等字段(models.py:654-702)留空后,get_config()(models.py:873-895)会使用 python-social-auth 内置的default_email、default_full_name等默认属性名去 SAML 断言中取值; - 点击Save保存。
重要:SAMLProviderConfig中的Entity ID 必须与 SAMLConfiguration 中的 Entity ID 完全一致(本文均使用https://saml.example.com/entityid)。因为认证时 IdP 元数据正是以entity_id为键从SAMLProviderData中查询的(models.py:898),且保存 ProviderConfig 时源码也会校验同名 entity_id 是否已被其他 slug 占用(models.py:808-818)。
各勾选选项的源码含义
- Skip hinted login dialog(models.py:730-737):开启后访问带
?tpa_hint=[provider_name]的 URL 会直接跳转到 IdP 登录页,不再弹出确认对话框; - Skip registration form(models.py:738-745):新用户认证后不再要求确认姓名、邮箱等资料,直接完成注册(仅建议用于可信任、能提供准确用户信息的 IdP);
- Skip email verification(models.py:747-753):用户注册后立即激活账号,无需邮箱验证;
- Send to registration first(models.py:754-760):第三方认证成功后直接进入注册页而非登录页。
Step 3:设置 IdP Data(SAMLProviderData)
SAMLProviderData存放从 IdP 元数据端点获取的运行时信息。手动创建一条记录,填写:
- Entity ID:
https://saml.example.com/entityid - SSO URL:
https://mocksaml.com/api/saml/sso - Public Key:IdP 的签名证书(从 MockSAML 元数据中提取)
- Expires At:设置为抓取时间起 1 年后(例如抓取于 2026-02-27,则设为 2027-02-27)
SAMLProviderData.is_valid()(models.py:947-952)会同时校验entity_id、sso_url、public_key三者非空,且当前时间未超过expires_at;任何一项不满足即视为无效。get_config()中只有is_valid()返回 True 的记录才会被采纳为签名公钥(models.py:901-903),若没有任何有效记录,会抛出AuthNotConfigured并提示运行manage.py saml pull(models.py:905-910)。
更推荐的自动化方式:在实际场景中
SAMLProviderData由系统自动抓取维护,无需手填。在 Step 2 配置好Metadata Source后,执行:
./manage.py lms saml --pull该命令(实现于 common/djangoapps/third_party_auth/management/commands/saml.py)会调用 tasks.py 中的fetch_saml_metadata()Celery 任务:遍历所有启用的 ProviderConfig,校验元数据 URL 后请求并解析 XML,提取公钥、SSO URL 与过期时间,写入SAMLProviderData(tasks.py:89-92)。同时建议用manage.py saml --run-checks检查 ProviderConfig 与 SAMLConfiguration 的关联是否过期、site 是否匹配、是否存在缺失配置等问题(saml.py:78-191)。
Step 4:测试 SAML 认证流程
- 浏览器访问:http://localhost:18000/auth/idp_redirect/saml-default
- 页面应 302 跳转到 MockSAML.com(对应 IdP 登录页);
- 在 MockSAML 页面直接点击Sign In(表单内已有预置测试用户数据);
- 认证完成后被重定向回 Open edX;
- 若是新用户,会看到注册表单;
- 完成注册后即成功登录。
该重定向端点由 views.py 中的IdPRedirectView提供,URL 规则定义在 urls.py:auth/idp_redirect/<slug:provider_slug>。视图通过pipeline.get_login_url(provider_slug, ...)生成 IdP 登录 URL,slug 不存在时返回 404;URL 中的saml-前缀来自SAMLProviderConfig.prefix = 'saml'(models.py:634)与 slug 拼接而成的 provider_id。
期望行为(完整时序)
- 首次跳转至 MockSAML:
https://mocksaml.com/api/saml/sso; - MockSAML 展示登录页面;
- 用户完成认证后,MockSAML 将 SAML 断言以 HTTP-POST 形式回传至 Open edX 的断言消费端点(ACS URL,见下文参考配置);
- Open edX 侧
SAMLAuthBackend(saml.py,backend 名为tpa-saml)校验断言签名与有效期,查找或创建对应用户; - 用户被重定向至 dashboard(老用户)或注册表单(新用户)。
参考配置:一份可复现的完整示例
以下是文档作者实测有效的完整配置快照,可直接对照排错:
SAMLConfiguration(id=6)
- Site:
localhost:18000 - Slug:
default - Entity ID:
https://saml.example.com/entityid - Enabled:True
SAMLProviderConfig(id=11)
- Name:
Test Localhost - Slug:
default - Entity ID:
https://saml.example.com/entityid - Metadata Source:
https://mocksaml.com/api/saml/metadata - Backend Name:
tpa-saml - Site:
localhost:18000 - SAML Configuration:→ SAMLConfiguration(id=6)
- Enabled:True
SAMLProviderData(id=3)
- Entity ID:
https://saml.example.com/entityid - SSO URL:
https://mocksaml.com/api/saml/sso - Public Key:(取自 MockSAML 元数据的签名证书)
- Fetched At:
2026-02-27 18:05:40+00:00 - Expires At:
2027-02-27 18:05:41+00:00 - Valid:True
MockSAML 侧配置
- SP Entity ID:
https://saml.example.com/entityid - ACS URL:
http://localhost:18000/auth/complete/tpa-saml/
(auth/complete/tpa-saml/正是SAMLAuthBackend的断言消费端点,其redirect_uri由saml_metadata_view(views.py:97-100)动态生成并写入 python-saml 的assertionConsumerService.url,见 saml.py:70-74。)
- Test User Attributes:
email、firstName、lastName、uid
MockSAML 返回的这组属性正好对应SAMLProviderConfig的默认属性映射,无需在 admin 中额外配置。
常见问题排查
- 登录页上找不到测试 IdP:确认
SAMLProviderConfig的Visible未勾选(测试阶段应保持不勾选),或直接使用idp_redirectURL 绕过登录页入口; - Entity ID 不匹配导致断言被拒:严格保持 SAMLConfiguration、SAMLProviderConfig、MockSAML 三处 Entity ID 完全一致(本文均为
https://saml.example.com/entityid); - 报错 "No SAMLProviderData found ... Run manage.py saml pull":
SAMLProviderData无有效记录或已过期,运行./manage.py lms saml --pull重新抓取,或检查手填记录中Public Key、SSO URL是否完整、Expires At是否已过期(models.py:947-952); - slug 不是
default导致 404/配置找不到:将SAMLConfiguration.slug改回default——认证执行路径中SAMLConfiguration.current(site_id, 'default')是硬编码回退(models.py:919); - 断言回传异常:开启
SAMLProviderConfig的Debug Mode(models.py:716-722),所有 SAML XML 请求与响应会被完整记录到日志,便于定位签名或属性解析问题(排查完毕后务必关闭)。
按照上述四步配置,你即可在本地 devstack 中拥有一个可反复演练、日志透明的 SAML 联调环境;将 MockSAML 替换为真实 IdP 的元数据端点后,同一套配置思路可以直接迁移到生产接入。
【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考