如何在Django中实现多提供商OpenID Connect单点登录
Django 动态多租户 OpenID Connect SSO 实现方案
不需要强依赖那些已废弃、或者绑定死静态配置/本地认证逻辑的第三方库,直接基于OIDC标准协议实现核心流程即可,灵活度最高,后续新增身份提供商也方便。
第一步:搭建动态SSO配置存储模型
专门建一张数据库表存储所有企业客户自主配置的SSO连接参数,完全脱离settings.py静态配置限制:
- 关联字段:绑定配置所属的企业租户,标记配置归属
- 核心OIDC参数字段:
client_id、client_secret(必须做字段级加密存储,禁止明文落库)、授权端点、令牌端点、用户信息端点、签发方标识,默认授权范围固定填openid email profile即可 - 身份提供商类型字段:预置Azure、Okta两个选项,后续新增其他IdP直接追加选项就行;用户选择对应类型时可以自动填充对应IdP的标准端点模板,比如Azure自动填充
https://login.microsoftonline.com/{租户ID}/v2.0/路径下的各端点,减少客户手动输入成本 - 状态控制字段:标记配置是否启用,支持客户在后台测试连通性后再正式上线
第二步:落地目标登录流程
完全匹配需要的交互逻辑:
- 登录页加载时,从配置表拉取所有已启用的企业SSO配置,为每个企业生成专属登录按钮,按钮携带对应配置的唯一ID参数
- 用户点击对应企业的SSO按钮后,后端根据配置ID查库拿到对应IdP的全量参数,动态生成授权跳转请求:
- 生成随机
state参数存在当前用户session中,回调时做CSRF校验 - 回调地址拼接当前配置的唯一标识,方便回调环节快速定位对应配置
- 针对不同IdP的特殊参数要求做适配,比如Azure默认带
prompt=select_account参数,这部分逻辑按IdP类型拆分即可,不耦合核心流程
- 生成随机
- 处理IdP回调请求:
- 首先校验回调携带的
state参数和session中存储的是否一致,不一致直接拦截请求 - 拿回调返回的授权码,搭配库中存储的
client_id、client_secret请求对应IdP的令牌端点,换取访问令牌 - 用访问令牌请求用户信息端点,拉取用户姓名、邮箱字段;这里加一层字段映射适配,兼容不同IdP返回的字段名差异,比如Azure返回姓名字段为
name,Okta可能同时返回name和preferred_username,统一映射成标准的姓名、邮箱字段即可
- 首先校验回调携带的
- 完成本地登录:拿到用户邮箱后直接查询本地用户表匹配已有账号,匹配成功直接调用Django原生的
login()函数写入会话完成登录,不需要绑定第三方库的认证后端;如果匹配不到账号,可以按业务规则走引导流程,比如提示联系企业管理员开通,或者按规则自动创建账号。
第三步:做可扩展设计,适配后续新增IdP
把不同IdP的差异逻辑拆成独立适配类,基类统一约定三个核心方法:生成授权地址、兑换令牌、解析用户信息。后续新增身份提供商时,只需要继承基类实现对应差异逻辑,注册到IdP类型映射表即可,不需要改动核心登录流程代码。
关键安全注意点
- 数据库中存储的所有
client_secret必须做字段级加密,用Django配套的加密组件实现即可,禁止明文存储 - 回调环节严格校验跳转地址,防止开放重定向漏洞
- 拿到IdP返回的令牌后,必须校验
iss(签发方)、aud(受众,即对应的client_id)字段合法性,防止伪造令牌绕过登录 - 管理后台给客户提供配置测试功能,保存配置时自动模拟走一遍授权流程,提前提示参数错误,减少线上登录故障
内容的提问来源于stack exchange,提问作者Marko Markovic
相关产品推荐
相关产品推荐

