使用MSAL令牌认证连接Azure SQL数据库时出现“Login failed for user <token-identified principal>”错误
使用MSAL令牌认证连接Azure SQL数据库时出现“Login failed for user ”错误
你遇到的这个问题我之前帮好几个开发者排查过,大概率是几个容易忽略的配置或代码细节没处理对,咱们一步步来梳理解决:
一、先排查最关键的权限问题:身份是否能登录Azure SQL
这是90%的人踩坑的点!哪怕你拿到了合法的令牌,如果这个身份没在Azure SQL里被授权,肯定会报这个登录失败的错。
你得先登录Azure Portal找到你的SQL服务器,进入「查询编辑器」(需要你有SQL管理员权限),执行以下SQL语句:
- 先在服务器级别创建对应身份的登录名:
CREATE LOGIN [你的身份Client ID] FROM EXTERNAL PROVIDER;
- 切换到你的目标数据库,创建数据库用户并赋予必要权限:
USE [你的数据库名]; CREATE USER [你的身份Client ID] FROM EXTERNAL PROVIDER; -- 给基础的读写权限,根据你的需求调整 ALTER ROLE db_datareader ADD MEMBER [你的身份Client ID]; ALTER ROLE db_datawriter ADD MEMBER [你的身份Client ID];
划重点:这里的「你的身份Client ID」要和你代码里用的一致——要么是之前ADAL里的
service_principal_id,要么是现在MSAL里的AZURE_CLIENT_ID,必须完全匹配!
二、检查MSAL令牌的获取是否正确
你的代码里用ConfidentialClientApplication拿令牌,有几个细节要核对:
- 确认身份凭证是否对应
你现在MSAL代码用的是环境变量里的AZURE_CLIENT_ID和AZURE_CLIENT_SECRET,这俩和之前ADAL从Key Vault拿的service_principal_id、service_principal_password是不是同一个服务主体?如果是不同的身份,那肯定没权限登录。 - 验证令牌有效性
把你拿到的token['access_token']复制到微软的JWT.ms(这个工具不会上传数据,放心用)里解析,检查:
aud字段必须是https://database.windows.net/(完全匹配,不能多后缀)iss字段是你的租户登录地址(比如https://sts.windows.net/你的租户ID/)exp字段要在有效期内
- 简化令牌获取逻辑(推荐)
你之前说不想硬编码密码,其实完全可以用DefaultAzureCredential直接拿令牌,不用绕MSAL的机密客户端——它支持托管身份、Azure CLI、环境变量等多种认证方式,更安全也更符合你的需求:
from azure.identity import DefaultAzureCredential credential = DefaultAzureCredential() # 直接获取Azure SQL的令牌 token = credential.get_token("https://database.windows.net/.default")
三、修正令牌的编码处理
你现在处理令牌的代码有点绕,而且如果令牌里有非ASCII字符可能会出问题。Azure SQL要求令牌是UTF-16LE编码的字节数组,前面加4字节的小端序长度,正确的写法应该是:
# 替换你原来的令牌处理代码 token_bytes = token["access_token"].encode("utf-16-le") token_struct = struct.pack("<I", len(token_bytes)) + token_bytes
这样比你原来的循环拼接要简洁准确得多。
四、优化后的完整代码示例
结合上面的所有修正,给你一个更简洁安全的版本,用托管身份代替硬编码的服务主体密码:
import pyodbc import os import struct from azure.keyvault.secrets import SecretClient from azure.identity import DefaultAzureCredential # 用DefaultAzureCredential,自动适配托管身份、环境变量等多种认证方式 credential = DefaultAzureCredential() # 从Key Vault读取数据库配置 client = SecretClient( vault_url=os.environ.get('AZURE_KEY_VAULT_URI'), credential=credential ) driver = client.get_secret('python-db-driver').value server = client.get_secret('SqlServerName').value database = client.get_secret('SqlDbName').value # 基础连接字符串 connection_string = f'Driver={driver};SERVER={server};DATABASE={database}' # 获取Azure SQL令牌 resource_uri = "https://database.windows.net/.default" token = credential.get_token(resource_uri) # 处理成Azure SQL要求的令牌格式 token_bytes = token.token.encode("utf-16-le") token_struct = struct.pack("<I", len(token_bytes)) + token_bytes # 尝试连接数据库 try: sql_conn = pyodbc.connect(connection_string, attrs_before={1256: token_struct}) print("数据库连接成功!") # 测试执行SQL cursor = sql_conn.cursor() cursor.execute("SELECT @@VERSION") row = cursor.fetchone() print(f"SQL Server版本: {row[0]}") except pyodbc.Error as e: print(f"连接失败: {str(e)}") finally: if 'sql_conn' in locals(): sql_conn.close()
最后再补几个检查点
- 如果你用的是用户分配的托管身份,记得在
DefaultAzureCredential里指定它的Client ID:
credential = DefaultAzureCredential(managed_identity_client_id=os.environ['USER_ASSIGNED_MI_ID'])
- 确保SQL服务器的防火墙允许你的Kubernetes集群IP访问,或者开启「允许Azure服务和资源访问此服务器」
- 确认Key Vault的访问策略里,你的身份有
Secret Get的权限
内容来源于stack exchange
相关产品推荐
相关产品推荐

