当身份验证(JWT)成功时,我在本地开发环境中使用CORS.我的客户端页面运行localhost并调用api.mycompany.com获取数据.我的api项目检查有效的JWT,如果通过,则返回内容.我花了一段时间才到这里,但这一切都很好.
如果我没有发送有效的JWT,则api会正确响应401(在Fiddler中检查),但客户端上的错误函数回调报告错误代码为0且状态为"错误".
我希望ajax回调函数检查错误的状态代码,如果是401,请检查标题名为location的标题(将包含uri到身份验证服务).
(API项目)在本地IIS Express上运行MVC4项目的Visual Studio 2012实例
http://localhost:8080
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>
Run Code Online (Sandbox Code Playgroud)(客户端项目)将Visual Studio实例与ASP.net空Web应用程序分开
http://localhost:22628
使用Google Chrome作为测试客户端
使用Fiddler查看流量
我认为这些应该是我的概念验证中的重要部分.再次,CORS预检和数据检索都可以正常工作.这只是未经授权的案例无效.如果您还有其他需要,请告诉我.谢谢您的帮助.
授权标题处理程序
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
东西控制器
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....
}
}
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
我终于弄明白了。在授权标头处理程序中,当 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;
}
Run Code Online (Sandbox Code Playgroud)
}
可能有更好的方法来做到这一点,但我只是在 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;
}
Run Code Online (Sandbox Code Playgroud)