
1. 为什么放弃API KeyEntra ID RBAC认证要解决什么问题在Azure AI Search的日常使用里API Key几乎是大部分人上手时的默认选择。控制台里点两下就生成一个Key代码里塞进Header就能调通确实简单。但一旦项目进入多人协作、自动化部署、多环境隔离的阶段API Key的问题会非常让人头疼。我在实际项目里就遇到过这些场景一位开发者在本地调试时把Key硬编码在配置文件中结果代码提交到了共享仓库Key被同事无意间同步到了自己的环境里。还有一次轮换Key之后生产环境忘了更新配置整个搜索服务在业务高峰期直接不可用。API Key本质上是一个“无限权限”的静态凭证只要泄露就等于把搜索服务的数据读取能力完整交给了攻击者而且你无法追溯是谁在使用它。Entra ID RBAC基于角色的访问控制则完全换了一套思路。它不再用一串静态字符串来识别调用方而是让调用方先通过Entra ID完成身份认证拿到一个短期有效的访问令牌再用这个令牌去调用Azure AI Search的服务。令牌的权限范围由RBAC角色决定比如你是只读角色还是读写角色一目了然。而且令牌有过期时间、可以绑定到具体的应用或用户一旦泄露影响面被大大缩小。更关键的是RBAC认证对国内区部署的Azure AI Search同样适用。国内区很多企业客户对合规和数据边界要求很高API Key那种“一把钥匙管全部”的模式在审计和权限管控上很难过关。改用Entra ID RBAC之后谁在什么时间用什么身份访问了搜索服务都能通过Entra ID的日志体系追踪到这对企业内部安全审计是实打实的硬需求。2. 动手前的设计身份选择与权限规划2.1 先想清楚是给“人”用还是给“应用”用这是最容易忽略的一步。很多教程上来就让你创建应用注册、生成客户端密钥却没说清楚这套流程到底适合什么场景。如果你的使用者是开发者本人比如在本地调试代码、用Postman测试接口那大概率适合用“用户身份”走交互式登录。这种情况下代码或工具会跳出登录弹窗用户输入账号密码完成认证然后拿到令牌。这样做的好处是无需维护任何客户端密钥权限跟着个人账号走出了问题也能直接定位到人。如果你的调用方是一个部署在云上的应用、函数、容器或者一台CI/CD流水线里的构建机那就需要用“应用身份”。应用本身没有交互式登录能力只能靠客户端密钥或证书来证明自己是谁。这也是生产环境中用得最多的一种方式。我在给某公司的搜索服务做改造时一开始直接把开发者的个人账号用在了服务到服务server-to-server的调用里结果后面这个开发者离职他的账号被禁用所有依赖他的搜索请求全部瞬间401。后来才改成了独立的应用注册权限和生命周期都独立管理再也没出过这种问题。2.2 内置角色别乱选按权限边界来定Azure AI Search提供了两个与数据平面紧密相关的内置角色Search Index Data Reader搜索索引数据读取者和Search Index Data Contributor搜索索引数据贡献者。只看名字容易产生误解。Data Reader并不代表能读取搜索服务的所有配置它只允许调用方查询索引和执行搜索请求比如发起POST /indexes/{indexName}/docs/search这类操作。它不能创建索引、不能改索引结构、不能看服务本身的设置。Data Contributor则在Data Reader基础上增加了维护索引数据的权限可以新增、修改、删除索引中的文档但同样不能修改索引的Schema。这两个角色之外的“Search Service Contributor”属于管理平面角色管理整个搜索服务的生命周期包括创建、扩容、删除搜索服务。日常业务代码里几乎用不到这个角色如果给应用分配了这个角色反而会扩大攻击面完全没必要。2.3 官方角色不够用自定义角色是备选方案如果内置角色满足不了细粒度需求比如你想让某个应用只能访问特定几个索引不能访问其他索引内置角色做不了这件事。这时需要自定义RBAC角色在Azure门户里通过JSON定义权限范围把DataReader或DataContributor的actions限定在具体索引资源路径下。自定义角色是“最后一道手段”它确实灵活但配置复杂度也上来了。我的建议是绝大多数场景先用内置角色等技术团队对RBAC模型足够熟悉、权限边界确实有更细粒度的需求时再考虑自定义角色。不然分布式团队搞出几十个自定义角色最后没人说得清楚每个角色的具体范围又是一笔运维负担。3. 一步步配置从应用注册到角色分配3.1 创建应用注册并准备客户端凭据开始之前你要先确认一件事当前所在的目录里你是否有权限创建应用注册。没有权限的话直接去找目录管理员开通否则下面的步骤一步也走不下去。在Azure门户里导航到Entra ID服务左侧菜单找到“应用注册”点击“新注册”。这里有几个需要认真填的字段名称建议格式是类似search-app-prod这样能直接看出环境和用途的名字别起成test1这种阶段性名字受支持的账户类型如果应用只在你自己的目录内使用选“仅此组织目录中的账户”即可不要选“任何组织目录”减小不必要的暴露面重定向URI本地调试或需要交互式登录时再填纯服务到服务调用不填也行。创建成功后你会得到一个“应用程序客户端ID”类似xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx这串ID要记好它相当于应用的账户名。同时还要记录你的“目录租户ID”后面换令牌时两个ID都要用到。接下来是客户端凭据。在应用注册页面里找到“证书和密码”点击“新客户端密码”设置一个过期时间我一般建议最长不要超过一年有些严格团队会要求半年一换。创建完成后页面只会展示一次“值”你要立刻复制保存。这个值就是你的客户端密钥请务必存放在安全的密钥管理服务里不要出现在任何代码仓库里。3.2 为应用分配AI Search的RBAC角色这一步的目标只有一个让刚才创建的“应用”拥有访问Azure AI Search数据平面的权限。在Azure门户里进入你的搜索服务资源页面左侧菜单找到“访问控制IAM”点击“添加” - “添加角色分配”。在角色列表里搜索“Search Index Data Reader”或“Search Index Data Contributor”按你的业务需要选一个。分配访问权限的对象类型选“用户、组或服务主体”然后在成员一栏搜索你刚创建的应用注册名称选中它。最后点“查看 分配”完成。这里有一个很隐蔽的坑Azure门户的搜索框对应用注册名称的搜索基于名称前缀匹配而且搜索范围默认可能没覆盖到服务主体。有时候你输入完整名称也搜不到别着急把名称输入换成客户端ID的前几位再搜成功率会高很多。我踩过这个坑当时还以为是IAM分配接口出了问题其实只是门户搜索逻辑太“智能”了。另外如果你是用PowerShell或Azure CLI做自动化角色分配的核心命令是New-AzRoleAssignment或az role assignment create将--assignee指定为应用注册的对象ID不是客户端ID是对象ID这个也容易搞混。下面是一段可供参考的PowerShell示例$searchServiceId /subscriptions/订阅ID/resourceGroups/资源组/providers/Microsoft.Search/searchServices/搜索服务名 New-AzRoleAssignment -ObjectId 应用的对象ID -RoleDefinitionName Search Index Data Reader -Scope $searchServiceId3.3 网络层面的配合当搜索服务开启了IP白名单很多生产环境会给Azure AI Search配置网络访问限制只允许特定IP网段或特定虚拟网络调用。这种情况下即使你的RBAC配置完全正确令牌也没问题请求依然可能因为网络策略直接失败。配置RBAC认证时不一定要处理网络问题但你要有这样的意识在隔离的网络环境下能拿到有效令牌只是第一步你的请求来源IP或虚拟网络也必须被搜索服务信任。否则你会在排查时陷入“身份没问题、网络有问题”的混合困境两个维度都很难快速定位。如果服务开启了“从门户访问”的选项你在Azure门户里调试时可能正常但本地代码一跑就超时或403。遇到这种迹象果断先去IAM里检查权限再去网络隔离设置里看看自己的出口IP是否在白名单内。4. 核心环节实现用Python代码完成RBAC连接4.1 理清原理代码里到底在做什么用RBAC连接Azure AI Search代码层的核心本质是“先用Entra ID换令牌再用令牌换搜索数据”。整个过程分三步用应用注册的客户端ID、客户端密钥和租户ID向Entra ID的令牌端点发起认证请求从认证响应中解析出访问令牌access_token把这个令牌放在HTTP请求的Authorization Header中以Bearer token的形式请求Azure AI Search。为什么需要用令牌而不能直接用客户端密钥调搜索接口因为Azure AI Search对RBAC请求的校验是基于Entra ID签发的令牌来完成的搜索服务会校验令牌的签名、有效期和权限范围。客户端密钥只是用来向Entra ID证明“你是一个合法的应用”搜索服务本身并不认识你的客户端密钥。这里要特别注意一个细节请求令牌时的scope作用域参数值必须是https://search.azure.cn/.default的形式而这里需要根据你使用的区域确认Azure AD的Endpoint。在国内区环境租户认证的Endpoint是https://login.partner.microsoftonline.cn/而非国际版的https://login.microsoftonline.com/同理search服务的scope也对应国内区域名https://search.azure.cn/.default。这些区域相关的Endpoint配置一旦搞错最常见的结果就是拿到的令牌有效但请求搜索服务时却无法被接受。4.2 代码实现与解析我用azure-identity这个官方SDK来做RBAC认证它比直接用HTTP请求去换令牌更省心能帮我们处理令牌缓存的逻辑而且国内版SDK也适配了对应Endpoint。下面是一套可以直接跑通的Python代码示例from azure.identity import ClientSecretCredential from azure.search.documents import SearchClient from azure.search.documents.models import VectorizableTextQuery from azure.core.credentials import AzureKeyCredential # 配置区 tenant_id 你的租户ID client_id 你的应用客户端ID client_secret 你的客户端密钥 # 国内区AI Search服务地址 search_endpoint https://你的搜索服务名.search.azure.cn # 搜索服务里创建好的索引名 index_name hotels-sample-index # 配置区结束 # 使用客户端密钥进行Entra ID认证获取访问令牌 credential ClientSecretCredential( tenant_idtenant_id, client_idclient_id, client_secretclient_secret ) # 创建SearchClient时不传AzureKeyCredential而是传credential # SDK会自动帮我们完成令牌的获取与刷新 search_client SearchClient( endpointsearch_endpoint, index_nameindex_name, credentialcredential ) # 发起一个简单搜索请求 results search_client.search( search_texthotels, query_typesimple, top5 ) for result in results: print(HotelId:, result.get(HotelId)) print(HotelName:, result.get(HotelName)) print(---)有几个容易出问题的细节我单独拿出来说一下ClientSecretCredential中的三个参数顺序是tenant_id、client_id、client_secret别写反。一旦写反认证会直接报错且错误信息不会明确提示你“参数顺序错误”它会拢成一句“Authentication failed”指数。用SDK时不需要手动去获取access_tokenSearchClient内部会通过TokenCredential协议调用凭据的get_token方法所以只需要把credential对象整体传进去即可。azure.search.documents这个SDK包版本需要在安装时确认一下不同版本对RBAC支持情况有所差异。我实践中azure-search-documents 11.4.x以上版本对RBAC支持已经非常稳健可以放心使用。4.3 手动验证SDK之外的另一条路有时候你想快速验证RBAC配置是否成功而不想引入SDK依赖也可以用纯HTTP请求方式。先用requests库向Entra ID换令牌再向搜索服务发请求代码如下import requests # 第一步向Entra ID换取访问令牌 token_url https://login.partner.microsoftonline.cn/{tenant_id}/oauth2/v2.0/token token_headers { Content-Type: application/x-www-form-urlencoded } token_data { grant_type: client_credentials, client_id: 你的客户端ID, client_secret: 你的客户端密钥, scope: https://search.azure.cn/.default } response requests.post(token_url, headerstoken_headers, datatoken_data) access_token response.json().get(access_token) # 第二步使用令牌调用搜索接口这里以索引列表为例 search_api_url https://你的搜索服务名.search.azure.cn/indexes?api-version2023-11-01 search_headers { Authorization: fBearer {access_token} } response requests.get(search_api_url, headerssearch_headers) print(response.json())这段代码的价值在于让你看到“换令牌”和“用令牌”是两个独立的请求方便理解整个链路。但生产环境我不建议直接用这种裸HTTP方式因为令牌缓存的逻辑、刷新逻辑、异常重试逻辑都需要你自己实现而SDK已经把这些都做完了。4.4 用Postman或RestClient调试的小结如果你不太想写代码也可以先用Postman调试整个流程。你在Postman里创建一个新的POST请求URL填https://login.partner.microsoftonline.cn/{tenant_id}/oauth2/v2.0/tokenBody选x-www-form-urlencoded填上client_id、client_secret、grant_typeclient_credentials、scopehttps://search.azure.cn/.default发送后就能拿到包含access_token的返回值。再把token复制到搜索服务的请求头里紧跟在Bearer后面即可。用Postman调试时拿到的token中间有一段灰色的部分那部分就是JWT的Payload。你可以把它复制到jwt.io或解码工具里看看里面的aud受众和roles字段这对后面排查问题非常有帮助。aud应该指向https://search.azure.cnroles里应该能看到你在IAM里分配的角色名称。只有这两处都符合预期RBAC链路才算真正通了。5. 常见问题与排查技巧实录5.1 请求返回403 AuthorizationError优先级最高的排查路径这是RBAC接入时出现频率最高的问题。403代表服务器认识你是谁但它认为你没有权限。这里有一个简单粗暴但高效的排查顺序第一步确认你调用的具体操作是数据平面还是管理平面。如果是https://服务名.search.azure.cn/indexes/{indexName}/docs/search这类带/docs/路径的操作属于数据平面如果只是/indexes则取决于具体API版本和语义很多列表类操作也算数据平面。角色分配错误的话直接看IAM页面里应用是否被分配了对应角色。第二步确认角色和操作匹配。执行搜索请求需要Search Index Data Reader或更高权限执行新增或更新文档需要Search Index Data Contributor。只分配了Reader却执行写操作403是必然的。第三步如果你确认角色没问题还是在403检查一下令牌的roles声明里是否真的有角色名称。有时IAM分配生效后有1到2分钟的延迟你可以等几分钟后重新换一个令牌试试。5.2 令牌拿到了但请求提示“Authentication failed”这种情况比403更隐蔽。403说明你的令牌被搜索服务接受了但权限不足而“Authentication failed”说明搜索服务根本没能验证你的令牌。最大概率的坑在于令牌是“国际版”的。国内区搜索服务并不接受国际版Entra ID签发的令牌。如果你不小心用https://login.microsoftonline.com国际版Endpoint换了令牌拿到的令牌拿到国内区搜索服务上就真的会触发“Authentication failed”。同样的国内区服务token scope必须用https://search.azure.cn/.default。判断自己是否中招的方法解码JWT看iss发行者字段。国内区的令牌iss应该是类似https://sts.chinacloudapi.cn/{tenant_id}/的地址而你拿到的若是https://login.microsoftonline.com/{tenant_id}/感觉明显不对劲那就是换错Endpoint了。5.3 IAM里找不到分配目标应用这个我说过一句但值得再单独列出来。在IAM的成员搜索框里如果你输入应用注册的完整名称搜不到试着改用客户端ID的前8到12位字符搜索或者用对象ID来搜索。我怀疑门户的搜索索引对应用名称的索引粒度不如ID字段准确反正用ID搜索基本一搜一个准。另外你要确保--assignee使用的是应用的对象IDObject ID而不是“应用程序(客户端)ID”那串UUID。这两个ID都在应用注册的“概述”页面上但一个是对象的ID一个是应用的ID。搞混之后角色分配给了一个不存在的“应用”自然没有任何效果。5.4 凭据报错信息过于笼统时先自查这四点SDK报错时经常把真实原因藏在几层异常里外层只显示一个笼统的提示。我一般按以下顺序自查检查客户端ID、租户ID是否和门户中完全一致。手敲非常容易出错建议直接复制。检查客户端密钥是否过期。客户端密钥在创建时可以设有效期如果你是在很久之前创建的密钥而中间过了有效期那无论角色配置得多完美认证都会失败。重新生成密钥并更新配置问题立刻解决。检查scope是否有拼写错误。https://search.azure.cn/.default这个字符串里一定不能多空格、不能少下划线.default后缀是固定格式。检查代码里有没有把client_id和client_secret的位置写反。两个字段在语言模型里看起来都很像UUID写反了是最不容易发现的问题之一。6. 我踩过的一些细节坑与建议6.1 应用身份不能和人身份混用很多人把应用注册的客户端密钥当成“万能钥匙”一个开发者在本地调试时也用生产环境的应用注册身份理由是“这样认证能通不用再配一套”。这个做法非常危险本地调试的请求会被令牌标记为生产身份而一旦出现安全问题或审计需求所有生产环境中的访问日志都会混入开发者的调试流量权限边界形同虚设。我建议至少分三套应用注册开发环境、测试环境、生产环境。每套环境用自己的客户端密钥用自己的角色分配范围。虽然多了一些管理成本但这种隔离带来的安全收益远大于那点维护负担。6.2 密钥建议做好自动化轮换RBAC替我们解决了“静态无限权限”的问题但客户端密钥依然是静态凭据也难逃泄露的风险。在有条件的团队里建议把客户端密钥的轮换做成自动化。在密钥管理服务中可以设置两个密钥轮换池在旧密钥过期前提前半月把新密钥配置到线上经过缓冲期后再废弃旧密钥。如果你一个人维护项目也别偷懒到“密钥永远不过期”。我给自己的最低要求是每次发版本时检查一下当前密钥剩余有效期超过6个月就准备换。一旦密钥过期发生在线上你面临的可不只是一个简单的更新配置的活儿整个搜索服务的中断事故会让你非常被动。6.3 国内区域SDK版本区域Endpoint的一点心得关于国内版SDK与Endpoint我自己在这个问题上吃过不小的亏。一开始接触国内区服务时我以为SDK和公共云完全一致代码里写的是国际版的默认Endpoint然后就开始排查各种奇怪的认证失败折腾了一整天才反应过来说不定是国内区Endpoint和scope不对。如果你也是第一次接触国内区Azure AI Search建议你优先把官方文档的国内版SDK示例通读一遍确认azure.identity的authority、endpoint、scope这三处都指到了国内区。密码学的错误最难看因为报错信息外在表现五花八门根因往往就那么一条。6.4 多索引场景的角色细分策略如果你一个搜索服务下有很多索引而且不同应用只允许访问其中一部分索引建议先从资源组的拆分上思考一次是不是该把不同业务的索引用独立的搜索服务承载服务级别的IAM隔离永远比索引级别的自定义角色好维护得多。只有当服务拆分不现实比如成本原因、数据关联强需要跨索引搜索才值得考虑自定义角色加特定索引资源路径的授权方案。真走到这一步时建议将自定义角色的JSON定义纳入版本管理并在代码仓库里写上清晰的注释说明这个角色为什么存在、覆盖哪些索引、授予谁使用。最后再分享一个小技巧在代码里封装一个get_search_client(index_name)的函数把身份认证逻辑统一收敛到这一处全项目只维护一套认证入口后面换密钥、换角色、调整Endpoint配置时都只改一处。我就是这么做的之前各个服务各自写认证逻辑出了几次“本地改了、线上没改”的乌龙之后我下决心把认证全部抽成了公共模块从那以后RBAC相关的配置问题少了非常多。如果你正在把现有API Key代码迁移到RBAC不妨按这个节奏推进先在测试环境完整的跑通RBAC链路并验证核心搜索请求确认结果与API Key模式一致后再存量切换。整个过程核心就是三件事——身份用对、角色给对、Endpoint填对只要这三件事做扎实RBAC连接Azure AI Search基本不会再有拦路虎。