使用Azure服务主体与MSAL从Databricks Python Notebook通过PySpark JDBC驱动连接MS SQL Server时的令牌(访问权限)错误解决方法咨询
Azure服务主体与MSAL从Databricks Python Notebook通过PySpark JDBC驱动连接MS SQL Server时的令牌(访问权限)错误解决方法咨询
嘿,我之前也碰到过一模一样的问题——用ADAL连接完全没问题,但切换到MSAL就各种令牌报错,折腾了好一阵才搞定。下面是我亲测有效的解决步骤和注意事项,应该能帮你顺利打通连接:
先确认基础权限配置
这一步很容易被忽略,但却是所有连接的前提:
- 确保你的Azure服务主体(SPN)已经被添加为MS SQL Server的Azure AD登录用户,并且在目标数据库中创建了对应的用户,分配了所需的角色(比如
db_datareader、db_datawriter)。 - 给SPN分配Azure层面的权限:至少需要
Azure SQL Database Contributor角色(或者更细粒度的权限,根据你的需求调整),确保它能访问目标SQL资源。 - 检查SQL Server的防火墙规则:必须允许Databricks集群的出站IP访问(如果是无服务器集群,可以考虑添加Azure Databricks的服务标签)。
选择正确的依赖库
你提到的两个库不要同时使用,避免驱动版本冲突:
- 优先选择
com.microsoft.azure:spark-mssql-connector_2.12:1.1.0(或更新的稳定版本,比如1.2.0),这个连接器对MSAL的支持更原生,适配Databricks的环境也更好。 - 直接在Databricks集群的“库”配置里添加这个Maven依赖即可,不需要额外安装databricks-jdbc驱动。
用MSAL正确获取访问令牌
这里关键是要拿到SQL Server认可的有效令牌,推荐用两种方式:
方式1:用MSAL Python库手动获取
先在集群里通过pip安装msal库(可以在Notebook里执行%pip install msal),然后用以下代码获取令牌:
import msal # 从Databricks Secrets中读取敏感信息(千万不要硬编码!) tenant_id = dbutils.secrets.get(scope="你的密钥范围名称", key="tenant-id") client_id = dbutils.secrets.get(scope="你的密钥范围名称", key="spn-client-id") client_secret = dbutils.secrets.get(scope="你的密钥范围名称", key="spn-client-secret") sql_resource_url = "https://database.windows.net/" # 初始化MSAL客户端应用 msal_app = msal.ConfidentialClientApplication( client_id=client_id, authority=f"https://login.microsoftonline.com/{tenant_id}", client_credential=client_secret ) # 获取针对Azure SQL的令牌,注意scope必须是`https://database.windows.net/.default` token_result = msal_app.acquire_token_for_client(scopes=[f"{sql_resource_url}.default"]) if "access_token" in token_result: access_token = token_result["access_token"] else: raise Exception(f"令牌获取失败:{token_result.get('error_description', '未知错误')}")
方式2:用Databricks内置TokenLibrary(更安全)
如果你的Databricks工作区支持,可以用内置的TokenLibrary直接获取令牌,省去手动处理MSAL的步骤:
from databricks.sdk.core import TokenLibrary tenant_id = dbutils.secrets.get(scope="你的密钥范围名称", key="tenant-id") access_token = TokenLibrary.getAzureToken("https://database.windows.net/", tenant_id)
配置PySpark JDBC连接
拿到令牌后,就可以配置JDBC连接了,核心是把令牌传递给accessToken参数,而不是用传统的用户名密码:
用Spark SQL Connector的方式(推荐)
sql_server_name = "你的sql-server名称.database.windows.net" database_name = "你的数据库名称" jdbc_url = f"jdbc:sqlserver://{sql_server_name}:1433;databaseName={database_name};encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;" # 读取数据 df = spark.read \ .format("com.microsoft.sqlserver.jdbc.spark") \ .option("url", jdbc_url) \ .option("dbtable", "你的架构名称.你的表名称") \ .option("accessToken", access_token) \ .load() # 写入数据(如果需要) df.write \ .format("com.microsoft.sqlserver.jdbc.spark") \ .option("url", jdbc_url) \ .option("dbtable", "你的架构名称.目标表名称") \ .option("accessToken", access_token) \ .mode("append") \ .save()
用原生JDBC驱动的方式
如果你更倾向于用原生JDBC驱动,也可以这么配置:
df = spark.read.format("jdbc") \ .option("url", jdbc_url) \ .option("dbtable", "你的架构名称.你的表名称") \ .option("accessToken", access_token) \ .option("driver", "com.microsoft.sqlserver.jdbc.SQLServerDriver") \ .load()
常见的令牌错误坑点
- Scope错误:一定要用
https://database.windows.net/.default作为令牌请求的scope,用其他值会导致SQL Server拒绝令牌。 - 依赖冲突:同时安装databricks-jdbc和mssql-connector会导致JDBC驱动版本冲突,令牌处理逻辑混乱,只保留其中一个即可。
- 令牌过期:MSAL客户端令牌默认有效期是1小时,如果你的作业运行超过1小时,需要重新获取令牌,建议把令牌获取逻辑封装成函数,按需调用。
- SPN权限缺失:除了SQL Server的权限,还要确保SPN没有被Azure AD的条件访问策略限制,比如MFA要求(服务主体不需要MFA,但如果策略强制所有主体都要MFA,可能会出问题)。
备注:内容来源于stack exchange,提问作者StephenDonaldHuffPhD
相关产品推荐
相关产品推荐

