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

背景

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

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

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

建立

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

    • 本地主机文件将127.0.0.1映射到api.mycompany.com
    • 将项目 – >属性 – > Web设置为IIS Express
      • 使用本地IIS Express(已选中)
      • 项目url: http://localhost:8080
      • 创建虚拟目录
      • 覆盖应用程序根URL(已选中)
      • 覆盖应用程序根URL: http://api.mycompany.com:8080http://api.mycompany.com:8080http://api.mycompany.com:8080
    • 在站点下的applicationhost.config中:

                
  • (客户端项目)将Visual Studio实例与ASP.net空Web应用程序分开

    • 将项目 – >属性 – > Web设置为IIS Express
      • 使用本地IIS Express(已选中)
      • 项目url: http://localhost:22628
      • 创建虚拟目录
  • 使用Google Chrome作为测试客户端

  • 使用Fiddler查看流量

我认为这些应该是我的概念certificate的重要部分。 再次,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 SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { var taskCompletionSource = new TaskCompletionSource(); const string identityProviderUri = "https://idp.mycompany.com"; IEnumerable 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 GetAll() { return _stuff; } public void Options() { // nothing.... } } } 

客户项目

main.html中

    ASP.NET Web API    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 = $('
  • ').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'); } . . . .
  • 我终于弄明白了。 在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; } 

    而是直接在ajax上检查状态代码,你可以使用这个代码在onComplete上检查…

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