Vue.js SPA调用带Azure AD授权的ASP.Net Core API返回401错误
编辑1:我最初以为应用部署到服务器后调用正常,但实际并非如此。
背景信息
- 基于Vue.js(vue3组合式API)开发的单页应用(SPA)
- 使用MSAL库(@azure/msal-browser@^2.32.2)实现Azure AD认证
- ASP.Net Core Web API(.Net 7)
- 通过Axios(axios@1.2.4)从SPA调用API
- 请求拦截器通过MSAL获取访问令牌(JWT Bearer),并在请求中作为Authorization头发送。
问题现象
从SPA调用API时返回401未授权错误。
以下调用场景可正常工作:
- 本地通过Swagger UI调用
- 本地使用带
--insecure参数的curl命令调用 - 本地通过禁用证书验证的Insomnia工具调用
服务器上部署的SPA调用同服务器上的API(原以为正常)
编辑1:实际此场景也无法正常工作。- 本地SPA调用API中无需授权的接口
环境信息
- Windows 10开发电脑
- SPA通过Vite在本地运行
- 使用
vite preview运行构建版本时存在相同问题。
- 使用
- 测试使用Edge(Chromium内核),Firefox或其他Chromium内核浏览器也存在相同问题。
- API通过IIS Express在本地运行(使用IIS Express生成的证书启用HTTPS)
由于服务器上的解决方案原以为正常,我曾认为Azure AD配置无误。
编辑1:Azure AD配置可能存在错误。
且由于可通过其他方式本地调用API,我认为API配置无误。
我猜测问题可能出在Axios调用上,但仅在本地且涉及授权时出现。
我按照官方教程配置Azure AD:
单页应用配置
遵循Azure AD JavaScript单页应用教程,使用MSAL与Azure AD交互。我还添加了Axios请求拦截器,为每个请求自动获取访问令牌。
Web API配置
遵循Azure AD ASP.NET API教程,授权配置中设置了带范围要求的策略,策略名为AuthZPolicy,我的范围名为access_as_user。
控制器使用特性[Authorize(Policy = "AuthZPolicy")]修饰。
我理解的认证/授权流程如下:
- 用户使用公司凭据完成认证
- MSAL获取IdToken并存储到本地存储中
- 当页面发起请求时,拦截器通过MSAL获取访问令牌,该令牌包含声明
"scp": "access_as_user"。 - 访问令牌在请求的Authorization头发送给API。
- 此时我预期API会验证令牌并返回响应,但实际返回401错误。
禁用授权
我尝试通过移除控制器上的Authorize特性来禁用API的授权。
也可通过在授权配置中不指定任何范围来禁用授权:
builder.Services.AddAuthorization(config => { config.AddPolicy("AuthZPolicy", policyBuilder => policyBuilder.Requirements.Add(new ScopeAuthorizationRequirement(/* no scopes required */))); }
两种方式下调用均可返回预期数据,但这不符合我们的授权需求。
为SPA启用HTTPS
我原本通过HTTP启动SPA,改为通过HTTPS启动(使用@vitejs/plugin-basic-ssl)后问题仍存在。
在Axios中禁用rejectUnauthorized
我希望将rejectUnauthorized设为false,以达到curl的--insecure参数或Insomnia禁用证书验证的效果。
但无法实现,因为浏览器环境中无法使用"https"模块,报错如下:
Module "https" has been externalized for browser compatibility. Cannot access "https.Agent" in client code. See vite官方故障排除文档了解更多详情。
我未尝试设置自定义证书,因为不清楚具体操作步骤。我推测需要为IIS Express配置证书,然后获取证书链来配置Axios。
SPA发起的调用(失败)
在开发者工具中可看到以下信息:
通用请求头
Request URL: https://localhost:44342/api/Reason/GetAll Request Method: GET Status Code: 401 Remote Address: [::1]:44342 Referrer Policy: strict-origin-when-cross-origin
响应头
access-control-allow-credentials: true access-control-allow-origin: https://localhost:44340 access-control-expose-headers: Content-Disposition date: Fri, 02 Jun 2023 14:07:59 GMT server: Microsoft-IIS/10.0 vary: Origin www-authenticate: Bearer x-powered-by: ASP.NET
请求头
:authority: localhost:44342 :method: GET :path: /api/Reason/GetAll :scheme: https accept: application/json, text/plain, */* accept-encoding: gzip, deflate, br accept-language: fr,fr-FR;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 authorisation: Bearer t0K3n origin: https://localhost:44340 referer: https://localhost:44340/ sec-ch-ua: "Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" sec-fetch-dest: empty sec-fetch-mode: cors sec-fetch-site: same-site user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57
API日志
在API控制台中可看到以下日志:
info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/2 OPTIONS https://localhost:44342/api/Reason/GetAll - - info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4] CORS policy execution successful. info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished HTTP/2 OPTIONS https://localhost:44342/api/Reason/GetAll - - - 204 - - 0.4755ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/2 GET https://localhost:44342/api/Reason/GetAll - - info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4] CORS policy execution successful. info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: ScopeAuthorizationRequirement:Scope= info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12] AuthenticationScheme: Bearer was challenged. info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished HTTP/2 GET https://localhost:44342/api/Reason/GetAll - - - 401 - - 0.5798ms
尽管访问令牌中包含该范围,但仍出现以下日志:
Authorization failed. These requirements were not met:
ScopeAuthorizationRequirement:Scope=
Swagger UI发起的调用(成功)
启动API时会打开Swagger UI。
如果使用从开发者工具请求头中复制的令牌在Swagger UI中认证,可返回200状态码及预期结果。
curl发起的调用(带--insecure参数时成功)
Swagger UI生成的curl命令如下:
curl -X 'GET' \ 'https://localhost:44342/api/Reason/GetAll' \ -H 'accept: text/plain' \ -H 'Authorization: Bearer t0K3n'
在Git Bash终端中执行时,会出现以下错误:
curl: (60) SSL certificate problem: self signed certificate
More details here: curl官方sslcerts文档
curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above.
我理解这是因为API运行在使用自签名证书的IIS Express上。
使用--insecure参数时可返回预期结果。
Insomnia发起的调用(禁用证书验证时成功)
Insomnia是一款REST API测试工具。
与curl类似,当Insomnia验证服务器证书时会报错,但禁用证书验证后可返回预期响应。
内容的提问来源于stack exchange,提问作者Xavier Stévenne

