CORS 预检请求在 Azure 托管的 Web API 中以 302 重定向响应

Posted

技术标签:

【中文标题】CORS 预检请求在 Azure 托管的 Web API 中以 302 重定向响应【英文标题】:CORS preflight request responds with 302 redirect in Azure hosted Web API 【发布时间】:2015-03-21 18:26:53 【问题描述】:

场景:

我有两个分别托管在 Windows Azure 上的 ASP.NET Web 应用程序,并且都与同一个 Azure Active Directory 租户相关联:

    具有 AngularJS SPA 前端和 adal.js 库的 MVC 应用程序,用于在客户端处理 Azure AD 身份验证。

    带有 Microsoft OWIN 中间件的 Web API,用于处理服务器上的 Azure AD 身份验证。

问题:

当 Angular 引导客户端应用程序时,页面在通过 oauth 重定向到正确的身份授权后正确加载,并且 adal.js 库为每个应用程序正确检索和存储不同的令牌(通过检查资源验证/Session-Storage 选项卡在 Chrome 开发工具中)。 但是当客户端应用程序尝试访问或更新 API 中的任何数据时,CORS 预检请求响应 302 重定向到身份授权,这会导致以下错误在控制台中:

XMLHttpRequest 无法加载 https://webapi.azurewebsites.net/api/items。 请求被重定向到 'https://login.windows.net/authority-guid/oauth2/authorize?response_type=id_token&redirect_uri=..etc..etc..', 对于需要预检的跨域请求,这是不允许的。

示例标头(匿名):

请求
选项 /api/items HTTP/1.1
主机:webapi.azurewebsites.net
连接:保持活动
访问控制请求方法:GET
Access-Control-Request-Headers:接受、授权
来源:https://mvcapp.azurewebsites.net
用户代理:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/39.0.2171.99 Safari/537.36
接受: */*
推荐人:https://mvcapp.azurewebsites.net/

回复
找到 HTTP/1.1 302
内容长度:504
位置:https://login.windows.net/authority-guid/oauth2/authorize?response_type=id_token&redirect_uri=https%3A%2F%2F....etc..etc.%2F&client_id=api-guid &scope=openid+profile+email&response_mode=form_post&state=...等...
服务器:Microsoft-IIS/8.0
X-Powered-By: ASP.NET
设置 Cookie:ARRAffinity=4f51...snip....redact....db6d;Path=/;Domain=webapi.azurewebsites.net

我做过/尝试过的事情

    确保 Azure AD 租户允许 OAuth2 隐式流,如 here 和其他地方所述。 确保 API exposes access permissions 和 MVC/SPA registers for access 使用这些公开的权限。 在 API 的 web.config 中显式添加了一个 OPTIONS 动词处理程序(见下文)。 使用了在 API 服务器上启用 CORS 的各种组合、OWIN 本身以及 EnableCorsAttribute(见下文)。

问题

有没有办法让与 Azure AD 租户关联的 Web API 不会在 CORS 预检请求上重定向? 我是否缺少 adal.js 库和/或 OWIN 启动代码中的一些初始化设置(见下文)? Azure 门户中是否有允许 OPTIONS 请求通过 OWIN 管道的设置?

相关代码:

adal.js 初始化

angular.module("myApp", ["ngRoute", "AdalAngular"])

.config(["$routeProvider", "$locationProvider", "$httpProvider", "adalAuthenticationServiceProvider",
    function ($routeProvider, $locationProvider, $httpProvider, adalProvider) 

        $routeProvider.when("/",  // other routes omitted for brevity
            templateUrl: "/content/views/home.html",
            requireADLogin: true // restrict to validated users in the Azure AD tenant
        );

        // CORS support (I've tried with and without this line)
        $httpProvider.defaults.withCredentials = true;

        adalProvider.init(
            tenant: "contoso.onmicrosoft.com",
            clientId: "11111111-aaaa-2222-bbbb-3333cccc4444", // Azure id of the web app
            endpoints: 
                // URL and Azure id of the web api
                "https://webapi.azurewebsites.net/": "99999999-zzzz-8888-yyyy-7777xxxx6666"
            
        , $httpProvider);
    
]);

OWIN 中间件初始化

public void ConfigureAuth(IAppBuilder app)

    // I've tried with and without the below line and also by passing
    // in a more restrictive and explicit custom CorsOptions object
    app.UseCors(CorsOptions.AllowAll);

    app.UseWindowsAzureActiveDirectoryBearerAuthentication(
        new WindowsAzureActiveDirectoryBearerAuthenticationOptions
        
            TokenValidationParameters = new TokenValidationParameters
            
                // Azure id of the Web API, also tried the client app id
                ValidAudience = "99999999-zzzz-8888-yyyy-7777xxxx6666"
            ,
            Tenant = "contoso.onmicrosoft.com"
        
    );

    // I've tried with and without this
    app.UseWebApi(GlobalConfiguration.Configuration);

WebApiConfig 初始化

public static void Register(HttpConfiguration config)

    // I've tried with and without this and also using both this
    // and the OWIN CORS setup above. Decorating the ApiControllers
    // or specific Action methods with a similar EnableCors attribute
    // also doesn't work.
    var cors = new EnableCorsAttribute("https://mvcapp.azurewebsites.net", "*", "*")
    
        cors.SupportsCredentials = true // tried with and without
    ;
    config.EnableCors(cors);

    // Route registration and other initialization code removed

API OPTIONS 动词处理程序注册

<system.webServer>
  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="OPTIONSVerbHandler" />
    <remove name="TRACEVerbHandler" />
    <add name="OPTIONSHandler" path="*" verb="OPTIONS" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

相关资源

我曾经尝试过以下(以及更多)论坛和博客文章以及 github 示例代码中几乎所有可以想象的组合。

    ADAL javascript and AngularJS – Deep Dive Secure ASP.NET Web API 2 using Azure Active Directory, Owin Middleware, and ADAL Token Based Authentication using ASP.NET Web API 2, Owin, and Identity AzureADSamples/SinglePageApp-DotNet (github) AngularJSCORS (github) How to make CORS Authentication in WebAPI 2? AngularJS and OWIN Authentication on WebApi

【问题讨论】:

【参考方案1】:

我有类似的问题来找出合适的包。只需设置 Owin cors 即可。请先检查 owin.cors 的软件包。

<package id="Microsoft.Owin" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Owin.Cors" version="2.1.0" targetFramework="net45" />

处理程序的 WebConfig 选项:

<system.webServer>
<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

您在 owin 配置中使用指定 cors 选项做得很好。

 public void ConfigureAuth(IAppBuilder app)
    
        app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            
                Audience = ConfigurationManager.AppSettings["ida:Audience"],
                Tenant = ConfigurationManager.AppSettings["ida:Tenant"]

            );
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    

Controller 不需要 CORS 相关的属性。

   [Authorize]
public class ContactsController : ApiController


    // GET api/<controller>
    public IEnumerable<string> Get()
    
        return new string[]  "person1", "person2" ;
    

    // GET api/<controller>/5
    public string Get(int id)
    
        return "person" + id;
    

WebAPIConfig 不需要 CORS 相关条目。

工作示例在这里:https://github.com/omercs/corsapisample

您可以使用以下代码在您的应用中进行测试:

app.factory('contactService', ['$http', function ($http) 
var serviceFactory = ;

var _getItems = function () 
    $http.defaults.useXDomain = true;
    delete $http.defaults.headers.common['X-Requested-With'];
    return $http.get('http://yourhostedpage/api/contacts');
;

serviceFactory.getItems = _getItems;

return serviceFactory;

]);

预检响应示例:

Remote Address:127.0.0.1:8888
Request URL:http://localhost:39725/api/contacts
Request Method:OPTIONS
Status Code:200 OK
Request Headersview source
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, authorization
Access-Control-Request-Method:GET
Host:localhost:39725
Origin:http://localhost:49726
Proxy-Connection:keep-alive
Referer:http://localhost:49726/myspa.html
User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36
Response Headersview source
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:authorization
Access-Control-Allow-Origin:http://localhost:49726
Content-Length:0
Date:Fri, 23 Jan 2015 01:10:54 GMT
Server:Microsoft-IIS/8.0
X-Powered-By:ASP.NET

【讨论】:

感谢您的回复。您有早期版本的 Microsoft.Owin.Cors NuGet 包(我使用的是 3.0.0),并且使用租户 ID 而不是租户名称来进行 Owin 和 adal.js 初始化。除此之外基本相同。抱着希望,我换成早期的包并尝试使用租户 ID。不幸的是没有骰子。您可以发布从您的工作示例执行的 OPTIONS 预检请求的请求和响应标头吗?我很想知道它们有何不同(如果有的话)。 我为选项请求添加了示例。您是否收到 302 错误?它可能与应用程序的权限设置有关。我还更新了工作示例。 感谢您发布标题。有趣的。而且,是的,我已经在这两个应用程序中配置了 Azure AD 访问权限。我已更新问题,将该部分添加到“我尝试过的内容”部分。 您的预检响应正在尝试重定向到登录。我认为 owin 中间件不接受令牌并重定向到登录。您可以使用我的 cors 示例并更改 Web.config 以尝试您的设置。我在上面提供了 github repo 链接。受众将是您从具有角度框架的网络应用程序中定位的资源。 看起来 Owin 并没有在预检中获得令牌。已发布的 OPTIONS 请求标头均未显示带有 Bearer 令牌的 Authorization 标头,Chrome 开发工具也未显示发送任何请求 cookie。我还查看了来自Katana 和Web API 的相关源代码。在预检请求期间似乎只检查了 Origin、Method 和 Headers。【参考方案2】:

已解决(某种程度)

这似乎是由部署问题引起的,特别是我最初将应用程序发布到 Azure 的方式。这两个应用程序最初都是使用 Windows 身份验证编写的,并部署到标准(即非 Azure)Web 服务器。使用这些技术,应用程序按预期工作。

根据新的业务需求,我一直致力于将它们迁移到 Azure。我遵循的过程是:

    按照最初编写的方式,从 Visual Studio 2013 发布向导直接将这两个应用程序部署/发布到 Azure。当然,正如预期的那样,这失败了,因为他们无法从 Azure 中与本地 Active Directory 进行通信。 按照问题末尾所有链接的详细信息逐步更新代码以删除 Windows 身份验证并替换为 Azure AD 身份验证。 手动将应用与 Azure AD 租户关联,以使用 Azure AD 门户进行身份验证。

在某个时候,我注意到 VS 发布向导的“设置”选项卡上的“启用组织身份验证”选项,并开始使用它来将应用程序与租户相关联。因为我已经手动关联了它们,所以 Visual Studio 最终为每个创建了另一个 Azure AD 应用程序,结果是两个。

最后是这样的:

    使用门户删除了两个应用程序的所有 Azure 记录,包括 Azure 网站本身以及 Azure AD 站点/关联。 将两个应用程序中的所有代码更改恢复为原始状态。 在应用程序中重新实现了 Azure AD 身份验证(OWIN、adal.js 等)。 是否重新发布了两个应用程序,让 VS 向导处理所有 Azure 关联。 使用新创建的客户端 ID 更新了 Web.config 文件和 adal.js 初始化。 再次发布。

现在 OPTIONS 预检请求不再是 302 重定向。

相反,它们现在是 405 Method Not Allowed,与 this thread 非常相似。进步,某种意义上的。

即使它仍然不能端到端工作,我会留下这个答案(而不是删除问题),以防它可能帮助其他人遇到 Azure 302 重定向 CORS 预检。

【讨论】:

您解决过这个问题吗?我被困在 302 上,并尽一切可能绕过它。我已经删除了所有内容并重新开始了多次。您能提供的任何帮助将不胜感激。 @IsaacLevin,302 已如上所述解决。随后的 405 也解决了不久,但老实说我不记得它的解决方案是什么。 您是否有机会在 Git 和 Azure 屏幕中提供您的解决方案?我知道这是否要求太多,但我在这里无能为力:) @IsaacLevin,并非没有违反 NDA 和与我的雇主签订的其他合同义务。 :) 哦,来吧,伙计......你认为你的工作比更大的利益更重要吗? facist ;) 我继续让客户端在同一个应用程序服务和 API 中,它工作正常。不理想,但它的工作原理

以上是关于CORS 预检请求在 Azure 托管的 Web API 中以 302 重定向响应的主要内容,如果未能解决你的问题,请参考以下文章

在提琴手工作之前,CORS 预检请求不会命中 IIS

ASP.NET WebAPI2 CORS:预检时 GetOwinContext 中的空请求

Azure 代理,请求 IIS 托管 API 时出现 CORS 错误

CORS 请求预检请求,以用户代码结尾

CORS 选项预检悬挂

Angular CORS 简单请求在 POST 中触发带有 Authorization 标头的预检