我正在使用WebAPI2后端创建一个AngularJS(Typescript)SPA,需要API的身份验证和授权。 API托管在不同的服务器上,所以我使用的是CORS,主要是遵循http://www.codeproject.com/Articles/742532/Using-Web-API-Individual-User-Account-plus-CORS-En的指导,因为我是该领域的新手。
一切正常,我可以注册并登录,然后通过在客户端传递接收到的访问令牌,向限制访问控制器操作(此处为默认VS WebAPI 2模板中的虚拟“值”控制器)发出请求 - 使用此相关代码的辅助服务:
private buildHeaders() {
if (this.settings.token) {
return { "Authorization": "Bearer " + this.settings.token };
}
return undefined;
}
public getValues(): ng.IPromise<string[]> {
var deferred = this.$q.defer();
this.$http({
url: this.config.rootUrl + "api/values",
method: "GET",
headers: this.buildHeaders(),
}).success((data: string[]) => {
deferred.resolve(data);
}).error((data: any, status: any) => {
deferred.reject(status.toString() + " " +
data.Message + ": " +
data.ExceptionMessage);
});
return deferred.promise;
}
现在,我想在登录后检索用户的角色,以便AngularJS应用程序可以相应地运行。因此,我在帐户API中添加了此方法(在类级别具有属性[Authorize]
,[RoutePrefix("api/Account")]
,[EnableCors(origins: "*", headers: "*", methods: "*")]
(*
用于测试目的):
[Route("UserRoles")]
public string[] GetUserRoles()
{
return UserManager.GetRoles(User.Identity.GetUserId()).ToArray();
}
然后我将此代码添加到我的登录控制器:
private loadUserRoles() {
this.accountService.getUserRoles()
.then((data: string[]) => {
// store roles in an app-settings service
this.settings.roles = data;
}, (reason) => {
this.settings.roles = [];
});
}
public login() {
if ((!this.$scope.name) || (!this.$scope.password)) return;
this.accountService.loginUser(this.$scope.name,
this.$scope.password)
.then((data: ILoginResponseModel) => {
this.settings.token = data.access_token;
// LOAD ROLES HERE
this.loadUserRoles();
}, (reason) => {
this.settings.token = null;
this.settings.roles = [];
});
}
帐户控制器的方法是:
public getUserRoles() : ng.IPromise<string[]> {
var deferred = this.$q.defer();
this.$http({
url: this.config.rootUrl + "api/account/userroles",
method: "GET",
headers: this.buildHeaders()
}).success((data: string[]) => {
deferred.resolve(data);
}).error((data: any, status: any) => {
deferred.reject(status.toString() + ": " +
data.error + ": " +
data.error_description);
});
return deferred.promise;
}
无论如何,这会触发OPTIONS预检请求,从而导致500错误。如果我检查响应,我可以看到GetOwinContext方法获得一个null请求。这是错误堆栈跟踪的开始:
{"message":"An error has occurred.","exceptionMessage":"Value cannot be null.\r\nParameter name: request","exceptionType":"System.ArgumentNullException","stackTrace":" at System.Net.Http.OwinHttpRequestMessageExtensions.GetOwinContext(HttpRequestMessage request)\r\n at Accounts.Web.Controllers.AccountController.get_UserManager() ...}
然而,我用于获取角色的代码与我用于从WebAPI测试控制器获取虚拟“值”的代码没有什么不同。我不能确切地看到为什么这里需要预检的原因,但无论如何我在OWIN代码中得到了这个令人讨厌的异常。
我的请求标头是(API位于端口49592):
OPTIONS /api/account/userroles HTTP/1.1
Host: localhost:49592
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:64036
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Access-Control-Request-Headers: accept, authorization
Accept: */*
Referer: http://localhost:64036/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,it;q=-5.4
有人可以解释一下吗?
答案 0 :(得分:0)
我认为即使看起来有点脏,我也找到了某种有效的解决方案,但至少它有效。我在这里发布,所以其他人最终可以利用它,但我愿意接受建议。 (抱歉格式错误,但我尝试了几次,编辑器不允许我正确标记代码。)
基本上,这篇文章的答案提出了解决方案:Handling CORS Preflight requests to ASP.NET MVC actions,但我更改了对我不起作用的代码(WebAPI 2和.NET 4.5.1)。这是它:
在Global.asax
,方法Application_Start
中添加BeginRequest += Application_BeginRequest;
。
添加覆盖,它只是通过允许所有内容响应OPTIONS
请求(在我的测试环境中这没问题):
protected void Application_BeginRequest(object sender,EventArgs e) { if((Request.Headers.AllKeys.Contains(“Origin”))&amp;&amp; (Request.HttpMethod ==“OPTIONS”)) { Response.StatusCode = 200; Response.Headers.Add(“Access-Control-Allow-Origin”,“*”); Response.Headers.Add(“Access-Control-Allow-Methods”,“GET,PUT,POST,DELETE”);
string sRequestedHeaders = String.Join(", ",
Request.Headers.GetValues("Access-Control-Request-Headers") ?? new string[0]);
if (!String.IsNullOrEmpty(sRequestedHeaders))
Response.Headers.Add("Access-Control-Allow-Headers", sRequestedHeaders);
Response.End();
} }
装饰帐户控制器方法的属性只是RouteAttribute
:
〔路线( “的UserRole”)] public string [] GetUserRoles() { string id = User.Identity.GetUserId(); Debug.Assert(id!= null); string [] aRoles = UserManager.GetRoles(id).ToArray(); 返回aRoles; }
这样OPTIONS
请求得到了正确的响应,连续的GET成功。
<强> ADDITION 强>
我还必须补充一点,EnableCors属性是不够的,因为我们不仅要处理OPTIONS
动词,还要确保任何CORS请求都获得Access-Control-Allow-Origin
标头。否则,您可能会观察到明显正确的响应(代码200等),但看到$http
调用失败。在我的情况下,我添加到global.asax
这一行:
GlobalConfiguration.Configuration.MessageHandlers.Add(new CorsAllowOriginHandler());
我的CorsAllowOriginHandler
是DelegatingHandler
,只是确保在请求包含*
标头的每个响应中都包含值为Origin
的标头:
public sealed class CorsAllowOriginHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync
(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// all CORS-related headers must contain the Access-Control-Allow-Origin header,
// or the request will fail. The value may echo the Origin request header,
// or just be `*`.
if ((request.Headers.Any(h => h.Key == "Origin")) &&
(response.Headers.All(h => h.Key != "Access-Control-Allow-Origin")))
{
response.Headers.Add("Access-Control-Allow-Origin", "*");
}
return response;
}
}