CORS - 阿贾克斯误差函数报告错误code为0的API请求返回401误差、函数、错误、报告

2023-09-10 17:14:02 作者:寂陌つ

背景

我有CORS在我的本地开发环境中工作时的身份验证(JWT)成功。我有流失的本地主机客户端页面,并调用到api.mycompany.com数据。 我的API项目检查有效JWT,并且如果通过,则返回的内容。我花了一段时间才能到达这里,但是这一切工作正常。

如果我不发送一个有效的智威汤逊的API与401(提琴手选中此)正确响应,但是客户端上的错误回调函数报告为0的错误code和错误的状态

我想Ajax回调函数来检查错误的状态code,如果是401,检查头头球指定位置(其中将包含URI来验证服务)。

设置

(API项目)的Visual Studio 2012实例上运行MVC4项目在本地IIS防爆preSS

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

在以下网站对ApplicationHost.config:

 <网站名称=StuffManagerAPIID =1>
  <应用路径=/applicationPool =Clr4IntegratedAppPool>
    < virtualDirectory路径=/physicalPath =C:\用户\我\文档\的Visual Studio 2012 \项目\ StuffManagerAPI \ StuffManagerAPI/>
  < /用途>
  <绑定>
    <约束力的议定书=HTTPbindingInformation =*:8080:本地主机/>
    <约束力的议定书=HTTPbindingInformation =*:8080:api.mycompany.com/>
  < /绑定>
< /网站>
 
W店UA,OB反混淆,抓包替换CORS跨域错误分析

(客户端项目)单独的Visual Studio实例与ASP.net空的Web应用程序

设置项目 - >属性 - >网络到IIS防爆preSS 使用本地IIS防爆preSS(选中) 项目地址:的http://本地主机:22628 在创建的虚拟目录

使用谷歌浏览器的测试客户端

使用招来查看流量

code

我觉得这应该是从我的概念验证的重要位。再次,CORS preflight和数据检索都做工精细。它只是不工作的未经授权的情况下。如果您有什么需要,请让我知道。感谢您的帮助。

API项目

认证头处理程序

 使用System.Collections.Generic;
使用System.Linq的;
使用System.Net;
使用System.Net.Http;
使用System.Threading.Tasks;

命名空间StuffManagerAPI.Handlers
{
公共类AuthorizationHeaderHandler:DelegatingHandler
{
    私人常量字符串键=theKey;

    保护覆盖任务< Htt的presponseMessage> SendAsync(Htt的prequestMessage要求,System.Threading.CancellationToken的CancellationToken)
    {
        VAR taskCompletionSource =新TaskCompletionSource< Htt的presponseMessage>();

        常量字符串identityProviderUri =htt​​ps://idp.mycompany.com;

        IEnumerable的<字符串> apiKeyHeaderValues​​ = NULL;
        如果(request.Headers.TryGetValues​​(授权,出apiKeyHeaderValues​​))
        {
            变种apiKeyHeaderValue = apiKeyHeaderValues​​.First();
            VAR令牌= apiKeyHeaderValue.Split('').LastOrDefault();
            VAR tokenProcessor ​​=新JasonWebTokenDecryptor.JasonWebToken(令牌,KEY);

            如果(tokenProcessor.IsValid)
            {
                base.SendAsync(请求的CancellationToken).ContinueWith(T => taskCompletionSource.SetResult(t.Result));
            }
            其他
            {
                VAR响应= FailedResponseWithAddressToIdentityProvider(identityProviderUri);
                taskCompletionSource.SetResult(响应);
            }

        }
        其他
        {
            如果(request.Method.Method!=选项。)
            {
                //没有认证头,因此需要重定向
                VAR响应= FailedResponseWithAddressToIdentityProvider(identityProviderUri);
                taskCompletionSource.SetResult(响应);
            }
            其他
            {
                base.SendAsync(请求的CancellationToken).ContinueWith(T => taskCompletionSource.SetResult(t.Result));
            }
        }

        返回taskCompletionSource.Task;
    }

    私有静态的Htt presponseMessage FailedResponseWithAddressToIdentityProvider(字符串identityProviderUri)
    {
        //创建响应。
        VAR响应=新的Htt presponseMessage(的HTTPStatus code.Unauthorized);
        response.Headers.Add(位置,identityProviderUri);
        返回响应;
    }
}
}
 

的东西控制器

 使用System.Collections.Generic;
使用System.Linq的;
使用System.Net;
使用System.Web.Http;
使用StuffManagerAPI.Attributes;
使用StuffManagerAPI.Models;

命名空间StuffManagerAPI.Controllers
{
[HttpHeader(访问控制 - 允许 - 起源,*)]
[HttpHeader(访问控制 - 允许 - 方法,选项,HEAD,GET,POST,PUT,DELETE)]
[HttpHeader(访问控制 - 允许 - 头,授权)
[HttpHeader(访问控制 - 暴露报头,位置)
公共类StuffController:ApiController
{
    私人只读东西[] _stuff =新的[]
        {
            新的东西
                {
                    的id =123456,
                    的SerialNumber =112233,
                    说明=酷产品
                },
            新的东西
                {
                    的id =456789,
                    的SerialNumber =445566,
                    说明=另一个很酷的项目
                }
        };

    公物获取(串号)
    {
        VAR项目= _stuff.FirstOrDefault(P => p.Id == ID);
        如果(项目== NULL)
        {
            抛出新的Htt presponseException(的HTTPStatus code.NotFound);
        }

        返回的项目;
    }

    公开的IEnumerable<东西> GETALL()
    {
        返回_stuff;
    }

    公共无效选项()
    {
       //没什么....
    }

}
}
 

客户端项目

main.html中

 <!DOCTYPE HTML>
< HTML LANG =EN>
< HEAD>
    <冠军>的ASP.NET Web API< /标题>
    <链接HREF =../内容/ Site.css相对=样式/>
    &LT;脚本的src =// ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.js"></script>

    &LT;脚本类型=文/ JavaScript的&GT;
        VAR的baseUrl =htt​​p://api.mycompany.com:8080/api/;
        $ .support.cors = TRUE;

        $(文件)。就绪(
            getListofStuff()
        );

        功能SetHeader可以(XHR){
            xhr.setRequestHeader('授权,承载blah.blah.blah');
        }

        传播getListofStuff(){
            VAR apiUrl =的baseUrl +东西/;

            $阿贾克斯({
                网址:apiUrl,
                键入:GET,
                数据类型:JSON,
                跨域:真正的,
                成功:receivedListOfStuff,
                错误:receiveError,
                beforeSend:SetHeader可以,
                状态code:{
                    0:函数(){
                        警报('得到0');
                    },
                    401:函数(){
                        警报('终于拿到了401');
                    }
                }
            });
        }

        功能getIndividualPieceOfStuff(ID){
            VAR apiUrl =的baseUrl +东西/+ ID;

            $阿贾克斯({
                网址:apiUrl,
                键入:GET,
                数据类型:JSON,
                跨域:真正的,
                成功:receivedIndividualStuffItem,
                错误:receiveError,
                beforeSend:SetHeader可以
            });
        }

        函数receivedListOfStuff(数据){
            $每个(数据,功能(键,VAL){

                VAR的listItem = $('&LT;李/&GT;)文本(val.Description)。
                listItem.data(内容,{ID:val.Id});
                $(myStuff)prePEND(的listItem)。
            });

            $(myStuff李)。点击(函数(){
                getIndividualPieceOfStuff($(本)的.d​​ata(内容)的标识。);
            });
        }

        函数receivedIndividualStuffItem(数据){
            $(#stuffDetails #ID)VAL(data.Id)。
            $(#stuffDetails #serialNumber)VAL(data.SerialNumber)。
            $(#stuffDetails #description)VAL(data.Description)。
        }

        功能receiveError(XHR,textStatus,errorThrown){
            VAR X = xhr.getResponseHeader(位置);
            变种Z = xhr.responseText;

            如果(xhr.status == 401){
                警报('终于拿到了401');
               }

            警报('错误AJAX');
        }
    &LT; / SCRIPT&GT;

&LT; /头&GT;
&LT;身体GT;
。
。
。
。
&LT; /身体GT;
&LT; / HTML&GT;
 

解决方案

我终于想通了。在认证头处理程序,当tokenProcessor.IsValid是假的,我跳到FailedResponseWithAddressToIdentityProvider,然后立即将结果和任务标记为已完成。所以,我从来没有拜访过的东西控制器并获得访问控制头说:

 如果(tokenProcessor.IsValid)
{
    base.SendAsync(请求的CancellationToken).ContinueWith(T =&GT; taskCompletionSource.SetResult(t.Result));
}
其他
{
    VAR响应= FailedResponseWithAddressToIdentityProvider(identityProviderUri);
            taskCompletionSource.SetResult(响应);
}
。
。
。
私有静态的Htt presponseMessage FailedResponseWithAddressToIdentityProvider(字符串identityProviderUri)
{
    //创建响应。
    VAR响应=新的Htt presponseMessage(的HTTPStatus code.Unauthorized);
    response.Headers.Add(位置,identityProviderUri);
    返回响应;
}
 

}

有可能是一个更好的方式来做到这一点,但我只是添加了标题为我的FailedResponseWithAddressToIdentityProvider方法响应和浏览器终于​​看到了401在Chrome,Firefox和IE8。下面是变化:

 私有静态的Htt presponseMessage FailedResponseWithAddressToIdentityProvider(字符串identityProviderUri)
{
    //创建响应。
    VAR响应=新的Htt presponseMessage(的HTTPStatus code.Unauthorized);
    response.Headers.Add(位置,identityProviderUri);
    response.Headers.Add(访问控制 - 允许 - 起源,*);
    response.Headers.Add(访问控制 - 允许 - 方法,选项,HEAD,GET,POST,PUT,DELETE);
    response.Headers.Add(访问控制 - 允许 - 头,授权);
    response.Headers.Add(访问控制 - 暴露报头,位置);
    返回响应;
}
 

Background

I have CORS working in my local dev environment when authentication (JWT) succeeds. I have the client page running off of localhost and calling into api.mycompany.com for data. My api project checks for a valid JWT, and if it passes, returns the content. It took me a while to get here, but this all works fine.

If I do not send a valid JWT, the api responds properly with a 401 (checked this in Fiddler), but the error function callback on the client reports an error code of 0 and a status of "error".

I want the ajax callback function to check the status code of the error and if it is 401, check the headers for a header named location (which will contain the uri to the authentication service).

Setup

(API Project) Visual Studio 2012 instance running MVC4 project on local IIS Express

Local host file maps 127.0.0.1 to api.mycompany.com Set Project -> Properties -> Web to IIS Express Use Local IIS Express (checked) Project Url: http://localhost:8080 Created Virtual Directory Override application root URL (checked) Override application root URL: http://api.mycompany.com:8080

In applicationhost.config under sites:

<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>

(Client Project) Separate Visual Studio Instance with ASP.net empty web application

Set Project -> Properties -> Web to IIS Express Use Local IIS Express (checked) Project Url: http://localhost:22628 Created Virtual Directory

Using Google Chrome as the test client

Using Fiddler to view traffic

Code

I think these should be the important bits from my Proof of Concept. Once again, the CORS preflight and data retrieval all work fine. It's just the unauthorized case that is not working. If you need anything else, please let me know. Thanks for the help.

API Project

Authorization Header Handler

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;
    }
}
}

Stuff Controller

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....
    }

}
}

Client Project

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>

解决方案

I finally figured it out. In the Authorization Header Handler, when tokenProcessor.IsValid is false, I jump to FailedResponseWithAddressToIdentityProvider and then immediately set the result and mark the task as complete. Therefore, I never visit the Stuff Controller and get the Access Control Headers added:

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;
}

}

There is probably a better way to do this, but I simply added the headers to my response in the FailedResponseWithAddressToIdentityProvider method and the browser finally sees the 401 in Chrome, Firefox, and IE8. Here is the change:

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;
}