Python脚本实现Azure OAuth2本地ADFS用户名密码登录问题
我之前处理过完全相同的场景——用AD Connect同步本地AD用户到Azure但不同步密码,依赖ADFS做联邦认证,用ROPC方式调用Azure端点就会报50126。问题根源很明确:
当你的用户是**联邦身份(由ADFS托管认证)**且未启用密码同步时,Azure AD本身没有用户的密码哈希,它无法直接验证你的用户名密码。虽然Azure登录页面会自动跳转到ADFS,但ROPC(资源所有者密码凭据)流程是纯后台HTTP请求,不会处理这种跳转逻辑,所以直接调用login.microsoftonline.com的端点就会返回"无效用户名或密码"的错误(实际是Azure AD没法验证,而非真的凭据错)。
解决步骤
1. 切换请求端点到你的ADFS服务器
你需要直接向本地ADFS的OAuth2令牌端点发送请求,而不是Azure AD的通用端点。首先找到你的ADFS服务器的元数据地址:https://<你的ADFS服务器域名>/adfs/.well-known/openid-configuration
访问这个地址,找到token_endpoint字段,这就是你要调用的URL(通常是https://<你的ADFS服务器域名>/adfs/oauth2/token)。
2. 调整请求参数(适配ADFS要求)
ADFS的ROPC参数和Azure AD的基本一致,但可能需要额外注意几点:
- 确保
resource参数是你要访问的CRM组织的URI(和之前一样) - 如果你的应用需要刷新令牌,可添加
scope: "openid offline_access"参数 - 确认应用在ADFS中已配置为信任的客户端
修改后的Python代码示例:
import requests # 替换为你的ADFS令牌端点 adfs_token_url = "https://your-adfs-server-domain/adfs/oauth2/token" tokenpost = { 'client_id': clientid, # 保持Azure应用注册的client_id不变 'resource': crmorg, # CRM组织的URI,和之前一致 'password': password, 'username': 'user@domain.com', 'grant_type': 'password', # 可选:如果需要刷新令牌,添加scope 'scope': 'openid offline_access' } tokenres = requests.post(adfs_token_url, data=tokenpost) # 检查响应 if tokenres.status_code == 200: print("获取令牌成功:", tokenres.json()['access_token']) else: print("错误详情:", tokenres.json())
3. 关键配置检查
- Azure应用注册:确保在"身份验证"页面启用了"允许公共客户端流"(因为脚本属于公共客户端,没有客户端密钥);同时确认已添加正确的delegated权限(且已授予管理员同意)。
- ADFS应用信任:在ADFS管理控制台中,确保已为你的Azure应用创建了应用程序信任,其中
客户端ID要和Azure应用的client_id一致,且信任规则允许该客户端使用ROPC流程。 - MFA限制:ROPC流程不支持多因素认证,如果你的用户启用了MFA,这种方式会直接失败,需要确保目标用户没有MFA要求。
- 网络连通性:运行脚本的机器必须能访问ADFS服务器的443端口(HTTPS),没有防火墙或代理拦截。
为什么原来的Azure端点不行?
Azure AD对于联邦用户,收到ROPC请求时会返回一个跳转指令(类似302重定向到ADFS),但requests.post默认不会自动跟随这种跳转(即使跟随,也需要处理ADFS的认证页面逻辑,而ROPC是凭据直接提交,不适用浏览器跳转)。所以必须绕过Azure AD,直接向ADFS提交凭据获取令牌。
内容的提问来源于stack exchange,提问作者Wyphorn

