【发布时间】:2014-08-17 14:28:07
【问题描述】:
我正在创建一个带有 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 方法收到一个空请求。这是错误堆栈跟踪的开头:
{"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
谁能解释一下?
【问题讨论】:
-
你确定 User.Identity.GetUserId() 有值吗?
-
我无法在此处设置断点,异常在我的代码之外和之前由管道中的 OWIN 内容引发。我也这么认为,但后来我看到断点永远不会被命中,并且堆栈跟踪指向参数“request”为空的那个 OWIN 方法。
-
我必须补充一点,我尝试了以下操作:(a) 按照stackoverflow.com/questions/13624386/… 中的建议,我添加了帖子中指定的 Application_BeginRequest,但这会触发另一个异常:System.Web.HttpException: Server cannot在发送 HTTP 标头后设置状态; (b)按照文档中的建议,我用 [AcceptVerbs(new[] { "GET"})] 修饰了控制器操作,没有 OPTION 以避免 MVC 与 OPTIONS 请求混淆,但这似乎没有任何效果。还有其他想法吗?
-
您是否确保在 web.config 中已将允许的动词设置为包含选项?
-
谢谢,我的 web.config 基本上没有受到 webapi 标准模板的影响。我能找到的对 OPTIONS 的唯一引用是在 system.webServer/handlers 下:
。我尝试删除它,但没有任何改变。
标签: angularjs cors owin asp.net-web-api2