9

背景

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

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

我希望 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
    • 在站点下的 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>
      
  • (客户端项目)使用 ASP.net 空 Web 应用程序分离 Visual Studio 实例

    • 将项目 -> 属性 -> Web 设置为 IIS Express
      • 使用本地 IIS Express(选中)
      • 项目网址:http://localhost:22628
      • 创建虚拟目录
  • 使用谷歌浏览器作为测试客户端

  • 使用 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>
4

2 回答 2

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;
}
于 2013-01-25T15:57:43.967 回答
0

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

> $.ajaxSetup({
>     error: function (x) {     
>         if (x.status == 401) {
>             alert("401");
>         }
>     } });
于 2013-01-22T05:12:32.237 回答