CORS - Ajax错误函数将返回401的API请求的错误代码报告为0

时间:2013-01-10 22:48:54

标签: ajax jquery cors

背景

当身份验证(JWT)成功时,我在本地开发环境中使用CORS。我的客户端页面运行localhost并调用api.mycompany.com获取数据。 我的api项目检查有效的JWT,如果通过,则返回内容。我花了一段时间才到这里,但这一切都很好。

如果我没有发送有效的JWT,api会正确响应401(在Fiddler中检查过),但客户端上的错误函数回调报告错误代码为0,状态为"错误&# 34。

我希望ajax回调函数检查错误的状态代码,如果是401,请检查标题名为location的标题(将包含uri到身份验证服务)。

设置

  • (API项目)在本地IIS Express上运行MVC4项目的Visual Studio 2012实例

    • 本地主机文件将127.0.0.1映射到api.mycompany.com
    • 设置项目 - >属性 - > Web到IIS Express
      • 使用本地IIS Express(已选中)
      • 项目网址:http://localhost:8080
      • 创建虚拟目录
      • 覆盖应用程序根URL(已选中)
      • 覆盖应用程序根URL:http://api.mycompany.com:8080
    • 在sites:

      下的applicationhost.config中
      <site name="StuffManagerAPI" id="1">
        <application path="/" applicationPool="Clr4IntegratedAppPool">
          <virtualDirectory path="/" physicalPath="C:\Users\me\Documents\Visual Studio 2012\Projects\StuffManagerAPI\StuffManagerAPI" />
        </application>
        <bindings>
          <binding protocol="http" bindingInformation="*:8080:localhost" />
          <binding protocol="http" bindingInformation="*:8080:api.mycompany.com" />
        </bindings>
      </site>
      
  • (客户端项目)将Visual Studio实例与ASP.net空Web应用程序分开

    • 设置项目 - &gt;属性 - &gt; Web到IIS Express
      • 使用本地IIS Express(已选中)
      • 项目网址:http://localhost:22628
      • 创建虚拟目录
  • 使用Google Chrome作为测试客户端

  • 使用Fiddler查看流量

代码

我认为这些应该是我的概念验证中的重要部分。再次,CORS预检和数据检索都可以正常工作。它只是未经授权的案例无效。如果您还有其他需要,请告诉我。谢谢你的帮助。

API项目

授权标头处理程序

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace StuffManagerAPI.Handlers
{
public class AuthorizationHeaderHandler : DelegatingHandler
{
    private const string KEY = "theKey";

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();

        const string identityProviderUri = "https://idp.mycompany.com";

        IEnumerable<string> apiKeyHeaderValues = null;
        if (request.Headers.TryGetValues("Authorization", out apiKeyHeaderValues))
        {
            var apiKeyHeaderValue = apiKeyHeaderValues.First();
            var token = apiKeyHeaderValue.Split(' ').LastOrDefault();
            var tokenProcessor = new JasonWebTokenDecryptor.JasonWebToken(token, KEY);

            if (tokenProcessor.IsValid)
            {
                base.SendAsync(request, cancellationToken).ContinueWith(t => taskCompletionSource.SetResult(t.Result));
            }
            else
            {
                var response = FailedResponseWithAddressToIdentityProvider(identityProviderUri);
                taskCompletionSource.SetResult(response);
            }

        }
        else
        {
            if(request.Method.Method != "OPTIONS")
            {
                //No Authorization Header therefore needs to redirect
                var response = FailedResponseWithAddressToIdentityProvider(identityProviderUri);
                taskCompletionSource.SetResult(response);
            }
            else
            {
                base.SendAsync(request, cancellationToken).ContinueWith(t => taskCompletionSource.SetResult(t.Result));
            }
        }

        return taskCompletionSource.Task;
    }

    private static HttpResponseMessage FailedResponseWithAddressToIdentityProvider(string identityProviderUri)
    {
        // Create the response.
        var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        response.Headers.Add("Location", identityProviderUri);
        return response;
    }
}
}

填充控制器

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;
using StuffManagerAPI.Attributes;
using StuffManagerAPI.Models;

namespace StuffManagerAPI.Controllers
{
[HttpHeader("Access-Control-Allow-Origin", "*")]
[HttpHeader("Access-Control-Allow-Methods", "OPTIONS, HEAD, GET, POST, PUT, DELETE")]
[HttpHeader("Access-Control-Allow-Headers", "Authorization")]
[HttpHeader("Access-Control-Expose-Headers", "Location")]
public class StuffController : ApiController
{
    private readonly Stuff[] _stuff = new[]
        {
            new Stuff
                {
                    Id = "123456",
                    SerialNumber = "112233",
                    Description = "Cool Item"
                },
            new Stuff
                {
                    Id = "456789",
                    SerialNumber = "445566",
                    Description = "Another Cool Item"
                }
        };

    public Stuff Get(string id)
    {
        var item = _stuff.FirstOrDefault(p => p.Id == id);
        if (item == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        return item;
    }

    public IEnumerable<Stuff> GetAll()
    {
        return _stuff;
    }

    public void Options()
    {
       // nothing....
    }

}
}

客户项目

main.html中

<!DOCTYPE html>
<html lang="en">
<head>
    <title>ASP.NET Web API</title>
    <link href="../Content/Site.css" rel="stylesheet" />
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.js"></script>

    <script type="text/javascript">
        var baseUrl = "http://api.mycompany.com:8080/api/";
        $.support.cors = true;

        $(document).ready(
            getListofStuff()
        );

        function setHeader(xhr) {
            xhr.setRequestHeader('authorization', 'Bearer blah.blah.blah');
        }

        function getListofStuff() {
            var apiUrl = baseUrl + "stuff/";

            $.ajax({
                url: apiUrl,
                type: 'GET',
                dataType: 'json',
                crossDomain: true,
                success: receivedListOfStuff,
                error: receiveError,
                beforeSend: setHeader,
                statusCode: {
                    0: function() {
                        alert('got 0');
                    },
                    401: function () {
                        alert('finally got a 401');
                    }
                }
            });
        }

        function getIndividualPieceOfStuff(id) {
            var apiUrl = baseUrl + "stuff/" + id;

            $.ajax({
                url: apiUrl,
                type: 'GET',
                dataType: 'json',
                crossDomain: true,
                success: receivedIndividualStuffItem,
                error: receiveError,
                beforeSend: setHeader
            });
        }

        function receivedListOfStuff(data) {
            $.each(data, function (key, val) {

                var listItem = $('<li/>').text(val.Description);
                listItem.data("content", { id: val.Id});
                $(".myStuff").prepend(listItem);
            });

            $(".myStuff li").click(function () {
                getIndividualPieceOfStuff($(this).data("content").id);
            });
        }

        function receivedIndividualStuffItem(data) {
            $("#stuffDetails #id").val(data.Id);
            $("#stuffDetails #serialNumber").val(data.SerialNumber);
            $("#stuffDetails #description").val(data.Description);
        }

        function receiveError(xhr, textStatus, errorThrown) {
            var x = xhr.getResponseHeader("Location");
            var z = xhr.responseText;

            if (xhr.status == 401){
                alert('finally got a 401');
               }

            alert('Error AJAX');
        }
    </script>

</head>
<body>
.
.
.
.
</body>
</html>

2 个答案:

答案 0 :(得分:3)

我终于明白了。在Authorization Header Handler中,当tokenProcessor.IsValid为false时,我跳转到FailedResponseWithAddressToIdentityProvider,然后立即设置结果并将任务标记为完成。因此,我从未访问过Stuff Controller并添加了访问控制标题:

if (tokenProcessor.IsValid)
{
    base.SendAsync(request, cancellationToken).ContinueWith(t => taskCompletionSource.SetResult(t.Result));
}
else
{
    var response = FailedResponseWithAddressToIdentityProvider(identityProviderUri);
            taskCompletionSource.SetResult(response);
}
.
.
.
private static HttpResponseMessage FailedResponseWithAddressToIdentityProvider(string identityProviderUri)
{
    // Create the response.
    var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
    response.Headers.Add("Location", identityProviderUri);
    return response;
}

}

可能有更好的方法,但我只是在FailedResponseWithAddressToIdentityProvider方法中将标题添加到我的响应中,浏览器最终在Chrome,Firefox和IE8中看到了401。这是改变:

private static HttpResponseMessage FailedResponseWithAddressToIdentityProvider(string identityProviderUri)
{
    // Create the response.
    var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
    response.Headers.Add("Location", identityProviderUri);
    response.Headers.Add("Access-Control-Allow-Origin", "*");
    response.Headers.Add("Access-Control-Allow-Methods", "OPTIONS, HEAD, GET, POST, PUT, DELETE");
    response.Headers.Add("Access-Control-Allow-Headers", "Authorization");
    response.Headers.Add("Access-Control-Expose-Headers", "Location");
    return response;
}

答案 1 :(得分:0)

直接在ajax上检查状态代码可以使用此代码在onComplete上检查...

> $.ajaxSetup({
>     error: function (x) {     
>         if (x.status == 401) {
>             alert("401");
>         }
>     } });