从 AngularJS 客户端使用 Web API 2(独立用户帐户 + CORS 已启用)






4.92/5 (37投票s)
如何从启用了 CORS 的 AngularJS 客户端使用 ASP.NET Web API 2(独立用户帐户)来颁发 Bearer 令牌。
未使用。
我已将代码升级为使用 VS 2013 更新 3 的新模板和“Microsoft.AspNet.Identity.Core”v2.1 DLL,它具有许多相对于 v1 的新功能和增强功能,例如将用户表 ID 从字符串更改为整数的灵活性,我已完成此操作,您可以在项目代码“WebApiAngularJs(Identity.Core v2).zip”中找到它。
引言
本教程将展示如何使用 AngularJS 客户端来调用启用了 CORS 的 Web API 2(独立用户帐户身份验证模式),并从使用Authorize 属性的 API Controller 请求数据。
为实现此目的,我们将面临的主要问题是:
- 如何配置 Angular $http 服务,将请求数据序列化为 URL 编码。
- 如何在服务器端启用跨域资源共享 (CORS),以允许来自不同域(来源)的请求。
我将逐步介绍项目设置,或者您可以跳过此步骤并下载源代码。
本教程包含以下部分:
必备组件
创建 Web API 项目
我将使用新的VS 2013 Web Api 模板来引导我们的项目。
启动 Visual Studio 2013。从文件菜单中,单击新建项目。在新建项目对话框中,单击左侧窗格中的 Web,然后在中间窗格中单击 ASP.NET Web 应用程序。将项目名称输入为WebApi2Service,然后单击“确定”。
在新建 ASP.NET 项目对话框中,选择“Web API”模板。
单击更改身份验证。在更改身份验证对话框中,选择个人用户帐户。单击“确定”。
在新建 ASP.NET 项目对话框中单击“确定”以创建项目。
此时,如果我们尝试运行应用程序并从 /api/values 控制器请求数据,我们将收到错误 Authorization has been denied for this request,这是因为 ValuesController 被Authorize 属性装饰,这强制该控制器的用户进行身份验证。
因此,下一步是创建一个 Web 客户端,用于注册/登录用户并从 ValuesController 请求数据。
创建 AngularJS 客户端应用程序
我们的 AngularJS 客户端项目 UI 将非常基础,并将使用最少的代码来实现以下目标:
- 注册新用户。
- 登录并身份验证已注册用户,并检索 bearer 令牌。
- 使用颁发的令牌请求需要用户身份验证的受保护 Web API 控制器的数据。
我们将创建一个单独的项目来处理 CORS 问题,并查看如何解决它。.
在解决方案资源管理器中,右键单击解决方案。选择添加,然后选择新建项目。
在添加新项目对话框中,与之前一样,选择ASP.NET Web 应用程序。将项目命名为AngularJsClient。单击“确定”。
在新建 ASP.NET 项目对话框中。单击“确定”。
现在我们需要安装 AngularJs 包,所以在 Visual Studio 中,从工具菜单中,选择库包管理器,然后选择程序包管理器控制台。在程序包管理器控制台中,键入以下命令:
Install-Package AngularJs.Core -ProjectName AngularJsClient
在解决方案资源管理器中,右键单击 AngularJsClient 项目。选择添加,然后选择JavaScript 文件。
在为项指定名称对话框中,将文件命名为 app.js。单击“确定”。
我们需要考虑的第一件重要事情是,新的 Web API 2 要求请求数据进行 URL 编码,这意味着我们必须将 content-type 标头设置为“application/x-www-form-urlencoded;charset=utf-8
”,并且我们还需要序列化发送的数据,以便服务器能够正确接收数据,因此我们将配置 $httpProvider
服务 post 方法来执行此操作,因此我们的 app.js 文件将如下所示:
(function () { 'use strict'; // Module name is handy for logging var id = 'app'; // Create the module and define its dependencies. var app = angular.module('app', [ ]); app.config(['$httpProvider', function ($httpProvider) { // Use x-www-form-urlencoded Content-Type $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'; // Override $http service's default transformRequest $httpProvider.defaults.transformRequest = [function (data) { /** * The workhorse; converts an object to x-www-form-urlencoded serialization. * @param {Object} obj * @return {String} */ var param = function (obj) { var query = ''; var name, value, fullSubName, subName, subValue, innerObj, i; for (name in obj) { value = obj[name]; if (value instanceof Array) { for (i = 0; i < value.length; ++i) { subValue = value[i]; fullSubName = name + '[' + i + ']'; innerObj = {}; innerObj[fullSubName] = subValue; query += param(innerObj) + '&'; } } else if (value instanceof Object) { for (subName in value) { subValue = value[subName]; fullSubName = name + '[' + subName + ']'; innerObj = {}; innerObj[fullSubName] = subValue; query += param(innerObj) + '&'; } } else if (value !== undefined && value !== null) { query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&'; } } return query.length ? query.substr(0, query.length - 1) : query; }; return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data; }]; }]); // Execute bootstrapping code and any dependencies. app.run(['$q', '$rootScope', function ($q, $rootScope) { }]); })();
有关 angularjs $http.post
与 Ajax post 方法的更多信息,您可以查看make-angularjs-http-service-behave-like-jquery-ajax。
重复上一步,再添加两个名为 mainCtrl.js 和 userAccountService.js 的 JavaScript 文件。
第一个文件 mainCtrl.js 将是我们用于视图(网页)的控制器代码,代码如下:
(function () { 'use strict'; var controllerId = 'mainCtrl'; angular.module('app').controller(controllerId, ['userAccountService', mainCtrl]); function mainCtrl(userAccountService) { // Using 'Controller As' syntax, so we assign this to the vm variable (for viewmodel). var vm = this; // Bindable properties and functions are placed on vm. vm.title = 'mainCtrl'; vm.isRegistered = false; vm.isLoggedIn = false; vm.userData = { userName: "", password: "", confirmPassword : "", }; vm.registerUser = registerUser; vm.loginUser = loginUser; vm.getValues = getValues; function registerUser() { userAccountService.registerUser(vm.userData).then(function (data) { vm.isRegistered = true; }, function (error, status) { vm.isRegistered = false; console.log(status); }); } function loginUser() { userAccountService.loginUser(vm.userData).then(function (data) { vm.isLoggedIn = true; vm.userName = data.userName; vm.bearerToken = data.access_token; }, function (error, status) { vm.isLoggedIn = false; console.log(status); }); } function getValues() { userAccountService.getValues().then(function (data) { vm.values = data; console.log('back... with success'); }); } } })();
对于文件 userAccountService.js,它将是我们用于调用服务器 Web API 以注册和登录用户,以及登录后获取 ValuesController
数据的服务,代码如下:
(function () { 'use strict'; var serviceId = 'userAccountService'; angular.module('app').factory(serviceId, ['$http', '$q', userAccountService]); function userAccountService($http, $q) { // Define the functions and properties to reveal. var service = { registerUser: registerUser, loginUser: loginUser, getValues: getValues, }; var serverBaseUrl = "https://:60737"; return service; var accessToken = ""; function registerUser(userData) { var accountUrl = serverBaseUrl + "/api/Account/Register"; var deferred = $q.defer(); $http({ method: 'POST', url: accountUrl, data: userData, }).success(function (data, status, headers, cfg) { console.log(data); deferred.resolve(data); }).error(function (err, status) { console.log(err); deferred.reject(status); }); return deferred.promise; } function loginUser(userData) { var tokenUrl = serverBaseUrl + "/Token"; if (!userData.grant_type) { userData.grant_type = "password"; } var deferred = $q.defer(); $http({ method: 'POST', url: tokenUrl, data: userData, }).success(function (data, status, headers, cfg) { // save the access_token as this is required for each API call. accessToken = data.access_token; // check the log screen to know currently back from the server when a user log in successfully. console.log(data); deferred.resolve(data); }).error(function (err, status) { console.log(err); deferred.reject(status); }); return deferred.promise; } function getValues() { var url = serverBaseUrl + "/api/values/"; var deferred = $q.defer(); $http({ method: 'GET', url: url, headers: getHeaders(), }).success(function (data, status, headers, cfg) { console.log(data); deferred.resolve(data); }).error(function (err, status) { console.log(err); deferred.reject(status); }); return deferred.promise; } // we have to include the Bearer token with each call to the Web API controllers. function getHeaders() { if (accessToken) { return { "Authorization": "Bearer " + accessToken }; } } } })();
最后,我们需要添加包含 UI 页面的 html 文件,与之前一样,在解决方案资源管理器中,右键单击 AngularJsClient 项目。选择添加,然后选择HTML 页。
在为项指定名称对话框中,将文件命名为 Index.html。单击“确定”。
正如我提到的,我们的 html 将是原始的,所以类似这样的内容应该足够了:
<xmp> <!DOCTYPE HTML> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <body> <div data-ng-app="app" data-ng-controller="mainCtrl as vm"> <h3> Test Values Controller</h3> <div> <span data-ng-show="vm.isRegistered">User Registered Successfully</span><br /> <span data-ng-show="vm.isLoggedIn">Hello : {{vm.userName}}</span><br /> <span data-ng-show="vm.isLoggedIn">Your Bearer Token is : {{vm.bearerToken}}</span> </div> <br /> <div> <input type="text" name="userName" placeholder="Name" data-ng-model="vm.userData.userName" /> <input name="password" placeholder="Password" data-ng-model="vm.userData.password" /> <input name="confirmPassword" placeholder="Password" data-ng-model="vm.userData.confirmPassword" /> <input type="button" id="registerUser" value="Register" data-ng-click="vm.registerUser()" /> <input type="button" id="logniUser" value="Login" data-ng-click="vm.loginUser()" /> </div> <button id="getValues" data-ng-click="vm.getValues()">Get Values</button> <ul> <li data-ng-repeat="v in vm.values"> <div> <strong>{{v}}</strong> </div> </li> </ul> </div> <script src="Scripts/angular.js"></script> <script> </script> <script src="app.js"></script> <script src="mainCtrl.js"></script> <script src="userAccountService.js"></script> </body> </html> </xmp>
在 Web API 中启用跨域资源共享 (CORS)
默认情况下,浏览器安全设置会阻止网页向另一个域发出 AJAX 请求。此限制称为同源策略,但是,我们可以通过在 Web API 服务器上启用 CORS 来允许跨域请求。
要启用 Web API 中的 CORS,请使用 Microsoft.AspNet.WebApi.Cors
包,该包可在 NuGet 上找到。
在 Visual Studio 中,从工具菜单中,选择库包管理器,然后选择程序包管理器控制台。在程序包管理器控制台中,键入以下命令:
Install-Package Microsoft.AspNet.WebApi.Cors -ProjectName WebApi2Service
这将把 CORS 包及其所有依赖项安装到 WebApi2Service 项目中。
在解决方案资源管理器中,展开 WebApi2Service 项目。打开 App_Start/WebApiConfig.cs 文件。将以下代码添加到 WebApiConfig.Register
方法中。
现在运行这两个项目(WebApi2Service 和 AngularJsClient),并尝试注册用户,输入用户名、密码和确认密码,然后单击注册按钮,您将注意到我们已成功注册用户。
现在尝试按登录按钮,您将惊喜地发现,您会收到一个与 CORS 未为 /Token 启用的相关错误!!!
但是我们已经启用了 Web API 的 CORS,那么我们错过了什么?
此错误的基本原因是 CORS 仅为 Web API 启用,而不是为客户端请求颁发令牌的令牌中间件提供程序启用。因此,要解决此问题,我们需要在提供程序颁发令牌时添加“Access-Control-Allow-Origin”标头,因此:
在解决方案资源管理器中,展开 WebApi2Service 项目。打开 Providers/ApplicationOAuthProvider.cs 文件,并将此行代码添加到 ApplicationOAuthProvider.GrantResourceOwnerCredentials
方法中。
现在再次尝试,并登录您之前注册的用户,登录应该会成功,您可以看到我们在登录后从服务器获得的令牌。
最后,单击 GetValues 按钮,您也应该能够成功从 Values Controller 获取值。
摘要
在本教程中,我们解决了使用来自 AngularJs 客户端的 Web API 2 bearer 令牌功能时遇到的大部分问题,希望它能帮助您更多地了解 Web API 2 和 AngularJs。