【问题标题】:ASP.NET WebAPI2 CORS: null request in GetOwinContext on preflightASP.NET WebAPI2 CORS:预检时 GetOwinContext 中的空请求
【发布时间】:2014-08-17 14:28:07
【问题描述】:

我正在创建一个带有 WebAPI2 后端的 AngularJS (Typescript) SPA,需要来自 API 的身份验证和授权。 API 托管在不同的服务器上,因此我使用 CORS,主要遵循 http://www.codeproject.com/Articles/742532/Using-Web-API-Individual-User-Account-plus-CORS-En 上的指导,因为我是该领域的新手。

一切正常,我可以注册和登录,然后通过在客户端传递接收到的访问令牌,向受限访问控制器操作(这里是默认 VS WebAPI 2 模板中的虚拟“值”控制器)发出请求 -使用此相关代码的辅助服务:

private buildHeaders() {
    if (this.settings.token) {
        return { "Authorization": "Bearer " + this.settings.token };
    }
    return undefined;
}

public getValues(): ng.IPromise<string[]> {
    var deferred = this.$q.defer();
    this.$http({
        url: this.config.rootUrl + "api/values",
        method: "GET",
        headers: this.buildHeaders(),
    }).success((data: string[]) => {
        deferred.resolve(data);
    }).error((data: any, status: any) => {
        deferred.reject(status.toString() + " " +
            data.Message + ": " +
            data.ExceptionMessage);
    });
    return deferred.promise;
}

现在,我想在登录后检索用户的角色,以便 AngularJS 应用程序能够做出相应的行为。因此,我在我的帐户 API 中添加了此方法(在类级别具有属性 [Authorize][RoutePrefix("api/Account")][EnableCors(origins: "*", headers: "*", methods: "*")]* 用于测试目的):

[Route("UserRoles")]
public string[] GetUserRoles()
{
    return UserManager.GetRoles(User.Identity.GetUserId()).ToArray();
}

然后我将此代码添加到我的登录控制器:

private loadUserRoles() {
    this.accountService.getUserRoles()
        .then((data: string[]) => {
            // store roles in an app-settings service
            this.settings.roles = data;
        }, (reason) => {
            this.settings.roles = [];
        });
}

public login() {
    if ((!this.$scope.name) || (!this.$scope.password)) return;

    this.accountService.loginUser(this.$scope.name,
            this.$scope.password)
        .then((data: ILoginResponseModel) => {
            this.settings.token = data.access_token;
            // LOAD ROLES HERE
            this.loadUserRoles();
        }, (reason) => {
            this.settings.token = null;
            this.settings.roles = [];
        });
}

帐户控制器的方法在哪里:

public getUserRoles() : ng.IPromise<string[]> {
    var deferred = this.$q.defer();
    this.$http({
        url: this.config.rootUrl + "api/account/userroles",
        method: "GET",
        headers: this.buildHeaders()
    }).success((data: string[]) => {
        deferred.resolve(data);
    }).error((data: any, status: any) => {
        deferred.reject(status.toString() + ": " +
            data.error + ": " +
            data.error_description);
    });
    return deferred.promise;            
}

无论如何,这都会触发 OPTIONS 预检请求,进而导致 500 错误。如果我检查响应,我可以看到 GetOwinContext 方法收到一个空请求。这是错误堆栈跟踪的开头:

{"message":"An error has occurred.","exceptionMessage":"Value cannot be null.\r\nParameter name: request","exceptionType":"System.ArgumentNullException","stackTrace":" at System.Net.Http.OwinHttpRequestMessageExtensions.GetOwinContext(HttpRequestMessage request)\r\n at Accounts.Web.Controllers.AccountController.get_UserManager() ...}

然而,我用于获取角色的代码与我用于从 WebAPI 测试控制器获取虚拟“值”的代码没有什么不同。我不能完全理解为什么需要预检的原因,但无论如何我都会在 OWIN 代码中遇到这个讨厌的异常。

我的请求标头是(API 位于端口 49592):

OPTIONS /api/account/userroles HTTP/1.1 Host: localhost:49592 Connection: keep-alive Access-Control-Request-Method: GET Origin: http://localhost:64036 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 Access-Control-Request-Headers: accept, authorization Accept: */* Referer: http://localhost:64036/ Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8,it;q=-5.4

谁能解释一下?

【问题讨论】:

  • 你确定 User.Identity.GetUserId() 有值吗?
  • 我无法在此处设置断点,异常在我的代码之外和之前由管道中的 OWIN 内容引发。我也这么认为,但后来我看到断点永远不会被命中,并且堆栈跟踪指向参数“request”为空的那个 OWIN 方法。
  • 我必须补充一点,我尝试了以下操作:(a) 按照stackoverflow.com/questions/13624386/… 中的建议,我添加了帖子中指定的 Application_BeginRequest,但这会触发另一个异常:System.Web.HttpException: Server cannot在发送 HTTP 标头后设置状态; (b)按照文档中的建议,我用 [AcceptVerbs(new[] { "GET"})] 修饰了控制器操作,没有 OPTION 以避免 MVC 与 OPTIONS 请求混淆,但这似乎没有任何效果。还有其他想法吗?
  • 您是否确保在 web.config 中已将允许的动词设置为包含选项?
  • 谢谢,我的 web.config 基本上没有受到 webapi 标准模板的影响。我能找到的对 OPTIONS 的唯一引用是在 system.webServer/handlers 下:。我尝试删除它,但没有任何改变。

标签: angularjs cors owin asp.net-web-api2


【解决方案1】:

我想我找到了某种可行的解决方案,即使它看起来有些脏,但至少它有效。我将它发布在这里,以便其他人最终可以利用它,但我愿意接受建议。 (抱歉格式不好,但我尝试了几次,编辑器不允许我正确标记代码)。

基本上,解决方案是由这篇帖子的答案提出的:Handling CORS Preflight requests to ASP.NET MVC actions,但我更改了对我不起作用的代码(WebAPI 2 和 .NET 4.5.1)。就是这样:

  1. Global.asax,方法Application_Start,添加BeginRequest += Application_BeginRequest;

  2. 添加覆盖,它只是通过允许一切来响应OPTIONS 请求(这在我的测试环境中是可以的):

    protected void Application_BeginRequest(object sender, EventArgs e) { if ((Request.Headers.AllKeys.Contains("Origin")) && (Request.HttpMethod == "选项")) { Response.StatusCode = 200; Response.Headers.Add("Access-Control-Allow-Origin", "*"); Response.Headers.Add("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE");

      string sRequestedHeaders = String.Join(", ",
          Request.Headers.GetValues("Access-Control-Request-Headers") ?? new string[0]);
      if (!String.IsNullOrEmpty(sRequestedHeaders))
          Response.Headers.Add("Access-Control-Allow-Headers", sRequestedHeaders);
    
      Response.End();
    

    } }

  3. 装饰accounts控制器方法的属性就是RouteAttribute

    [路线(“用户角色”)] 公共字符串[] GetUserRoles() { 字符串 id = User.Identity.GetUserId(); Debug.Assert(id != null); string[] aRoles = UserManager.GetRoles(id).ToArray(); 返回一个角色; }

这样OPTIONS 请求会得到正确的响应,并且后续的 GET 会成功。

添加

我还必须补充一点,EnableCors 属性是不够的,因为我们不仅要处理 OPTIONS 动词,还要确保任何 CORS 请求都获得 Access-Control-Allow-Origin 标头。否则,您可能会观察到明显正确的响应(代码 200 等),但会看到 $http 调用失败。在我的情况下,我添加到 global.asax 这一行:

GlobalConfiguration.Configuration.MessageHandlers.Add(new CorsAllowOriginHandler());

我的CorsAllowOriginHandler 是一个DelegatingHandler,它只是确保这个值为* 的标头出现在请求包含Origin 标头的每个响应中:

public sealed class CorsAllowOriginHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync
        (HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        // all CORS-related headers must contain the Access-Control-Allow-Origin header,
        // or the request will fail. The value may echo the Origin request header, 
        // or just be `*`.
        if ((request.Headers.Any(h => h.Key == "Origin")) &&
            (response.Headers.All(h => h.Key != "Access-Control-Allow-Origin")))
        {
            response.Headers.Add("Access-Control-Allow-Origin", "*");
        }
        return response;
    }
}

【讨论】:

    猜你喜欢
    • 2019-10-21
    • 1970-01-01
    • 2012-11-17
    • 2016-08-20
    • 1970-01-01
    • 1970-01-01
    • 2017-10-16
    • 2021-03-27
    • 2014-10-31
    相关资源
    最近更新 更多