65.9K
CodeProject 正在变化。 阅读更多。
Home

AngularJS 和 ASP.NET MVC 入门 - 期待已久!第三部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (59投票s)

2014 年 11 月 17 日

CPOL

12分钟阅读

viewsIcon

209135

如何在 ASP.NET MVC 上快速上手 AngularJS:第三部分

系列文章

引言

我早在八月份就写了本系列的第一部分和第二部分,并一直打算写第三部分,但总是因为这样那样的原因(闪电!)而分心,但现在终于开始了。

第三部分我们将要做什么

  • 使用 ASP.Net Web API 构建 RESTful API 的基础,包括身份验证、角色等,然后创建一个连接到此 API 的新 Angular 应用。

源代码

本文配套的源代码可以在 这里找到。

RESTful API

好的,让我们开始构建 RESTful API。在 Visual Studio 中,选择 **新建项目...** => **Web** => **ASP.Net Web Application**(命名为 *Awesome Angular Web App 2.0*)=> Web API。身份验证保持 **Individual User Accounts**,然后单击“确定”。

现在,如果您还没有安装,我想让您安装三个应用程序

安装好 Postman 后,在 Chrome 中浏览以下 URL

chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm/index.html

如果您熟悉 ASP.Net MVC,Web API 在某些方面会相对容易上手,因为它基于相同的技术,并且也是基于 MVC 等,但在其他方面(从需要学习 HTTP 和 RESTful API 是什么以及如何构建它们的角度来看)的学习曲线会更陡峭。这不是本篇文章的主题,但我建议阅读一本关于此主题的好书。

按 F5(或您调试快捷键)启动我们的 Web API 项目。您应该会看到类似以下内容

单击 API。当我们构建 API 时,此页面会自动更新有关我们所有端点以及相关 HTTP 请求类型的信息。单击此处的方法将为您提供有关该特定端点的详细信息,包括参数等。在某种程度上,它是自文档化的。

在网站仍然运行时,启动 Postman。我们将创建一个用户并登录。Web API 使用令牌(token)而不是 cookie 进行身份验证。它的工作原理很简单:如果您使用有效的用户名和密码登录,API 将返回一个令牌,以及该令牌的有效期限信息。您将此令牌作为标头添加到您所有后续的 HTTP 请求中,服务器便会知道您是谁。

就像普通的 ASP.Net 身份验证一样,您可以使用 **[Authenticate]** 属性来限制对控制器或方法的访问。您还可以创建角色,并允许不同的用户根据其角色访问 API 的不同部分。

让我们创建一个用户。查看 URL 来确定您的 API 正在运行的端口(它会显示 localhost,冒号,端口号),然后在 Postman 的 URL 栏中输入以下内容,将 8888 替换为您的端口号

https://:8888/api/Account/register

在下面的栏中,单击 **raw**,然后在下面的文本框中粘贴以下 JSON(别担心,AngularJS 的内容很快就会来了,但我们需要先完成这个部分)

{
  "Email": "email@email.com",
  "Password": "Password1!",
  "ConfirmPassword": "Password1!"
}

我们还需要将此请求的内容类型设置为 json,因此请单击 **Headers**,然后添加一个名为 **Content-Type** 的新标头,其值为 **application/json**。最后,在下拉列表中将请求类型更改为 POST。

Postman 应该看起来像这样

发送。这需要几秒钟,因为 ASP.Net 正在后台创建数据库,但一旦完成,您应该会收到 200 OK 的响应,如下所示

如果您在 fiddler 中查看,您应该也能找到此请求。查看这里的不同选项卡/按钮来探索请求的详细信息。

现在我们已经创建了一个用户,让我们继续登录。将 URL 更改为

https://:8888/token

这仍然是一个 POST 请求,但现在我们想将数据模式更改为 **x-www-form-urlencoded**,所以单击此按钮。添加以下字段

  • grant_type: password
  • userName: email@email.com(或您注册的任何内容)
  • password: Password1!(或注册时设置的任何密码)

Postman 现在应该看起来像这样

发送,然后会返回类似以下内容的内容

{
    "access_token": "R-AejC88wImTKUulwlZBRsR620zXuHcrjV26UGObVjl5s9aqJIhs2hzt60CdLhL0hXNR-kyLTgrTfiMDV4JZJsmC1jV3MQHKcScsW6lYAMz1kegSyQiSfRHVj8W1E76x9uiHYJVIWhwA_RH7GkTn3K_Z0ugV_0qsSd1cWZ5qpqRedrS1vbHNIr7PR-FvAcKGA5c0S7ffadD8TP6N8OX8AyEg2t5rxppAeT2AlqlY3G5HdJqDkPgXQx5pL_xXRWkQCuOhIgUCm-6TDAksNf-EJ7HzPKD7nl7KU8Pd66rQO56p_vtq6eOO9OtgAmN8FviR-gNKGHCsz4udPrAKTExF_Ht4hBpbLoiGIXIbVUpzTeB-RMZUMMcRgByo4tCELjd41pV0mjaXHS6s7mTuwlgGmxiAU5AoYgNTXVOe9YegZMvjW_lAIUw0YlZ0m7RAiPOTTDlRzmV1ntm3YGvAN9h9_m027twqfGz5YsHsbh3RYW8",
    "token_type": "bearer",
    "expires_in": 1209599,
    "userName": "email@email.com",
    ".issued": "Sun, 16 Nov 2014 22:55:45 GMT",
    ".expires": "Sun, 30 Nov 2014 22:55:45 GMT"
}

RESTful API 和 Web API,一个非常有限的入门指南

当我们创建 Web API 项目时,它为我们创建了一个名为 **ValuesController** 的控制器,其中包含五个方法

  • Get(重载了一个接受参数的版本)
  • Post
  • 删除

控制器的名称对应于 URL(就像在 ASP.Net MVC 中一样),方法的名称对应于请求的 HTTP 动词。因此,如果您想编写一个 HTTP GET 请求的端点,您就创建一个名为 Get 的方法,例如。

如何构建 RESTful API 的架构超出了本文的范围,但通常您会使用

  • GET 请求来获取某些内容
  • POST 来做某事(执行操作)
  • PUT 来将某物放在某处(例如数据库)
  • DELETE 来删除

您总是会使用一个表示结果的状态码来响应 HTTP 请求。当我们注册和登录时,每次都得到了 200 的响应。请随时在 Postman 中尝试这些请求(所有内容都保存在历史记录中),提供不同的值,删除/添加值等,以查看在不同场景下返回的不同状态码。例如,如果您在登录时提供了错误的密码,您会得到 **400 Bad Request** 的响应。

维基百科上关于此的页面很好地解释了各种状态码。阅读完之后,我建议您 到这里阅读关于 7xx 系列,您会笑的。在阅读完维基百科文章后,您会需要它。

所有 HTTP 请求都可以包含标头,无论其 HTTP 动词(Get、Post 等)是什么,有些还可以包含正文,而有些则不能。例如,POST 可以包含正文,而 GET 不能。在 Web API 中,此正文使用强类型 C# 对象构建并从方法返回。

让我们尝试调用此 values 控制器的 Get 方法。在 Postman 中,将 HTTP 操作类型更改为下拉列表中的 GET,并将 URL 更改为如下所示

https://:8888/api/values

暂时将 Content-Type 保留为 **application/json**。Postman 应该看起来像这样

发送,然后我们从 API 收到 401 Unauthorized 响应。这是因为 Values 控制器受到 **[Authorize]** 属性的保护!哎呀!

在 Postman 中,添加一个名为 **Authorization** 的新标头,其值为 **Bearer**,后跟一个空格,然后跟我们之前收到的令牌。Postman 现在应该看起来像这样

再次发送,成功!我们从 API 收到了以下 JSON

[
    "value1",
    "value2"
]

尝试在 Postman 中调用 values 控制器中的每个方法,并修改方法以返回不同类型的对象。您也可以在 Postman 中看到所有这些操作,以及有关每个请求的详细信息。是不是很酷?

这原本是一篇关于 AngularJS 的文章,但我们还没有写任何 AngularJS 的内容!快点开始写 AngularJS 吧!

好吧,好吧。这次,我们不创建 ASP.Net MVC 应用程序来托管我们的网站,我们将使用纯粹的 CSS、HTML 和 Javascript 以及一点 Node.JS 来创建一切。

我之前让您安装 Node.JS。由于当前版本的 Node.JS 中存在一个 bug,您还需要在以下位置创建一个名为 **npm** 的目录

C:\Users\{{your user name}}\AppData\Roaming

当您阅读本文时,这可能已经修复,如果是这样,请忽略(并可以留下评论)。

创建一个目录用于您的网站,并使用您喜欢的文本编辑器(我使用的是 Atom)创建一个包含以下内容的 **index.html** 文件

<!DOCTYPE html>
<html>

<head>
<title>Awesome Angular Web App 2.0</title>
</head>

<body>
Hello Again!
</body>

</html>

现在打开 **Node.js command prompt**,然后运行以下命令

npm install http-server -g

这将安装一个名为 **http-server** 的轻量级 Web 服务器,它内置于 Javascript 中,我们可以用它来运行我们的 Angular 应用。**-g** 标志表示全局安装。这意味着服务器被安装到您的 PC 上,并且可以在任何地方运行。如果您省略此选项,服务器将被安装到您当前所在的目录。

现在导航到存放您网站的目录,并运行 http-server。您的窗口应该看起来像这样

在您的浏览器中,浏览到 **https://:8080/**。

难道这不很棒吗?

添加一个名为 **Scripts** 的子目录,以及一个名为 **Styles** 的子目录。我们在前两部分已经讲过这些内容了,所以我不会在这里做太多解释,只需复制粘贴我创建的每个文件的内容即可。

index.html

<!DOCTYPE html>
<html>

<head>
<title>Awesome Angular Web App 2.0</title>
</head>

<body ng-app="AwesomeAngularWebApp" ng-controller="BaseController">
{{helloAgain}}

<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.min.js"></script>

<script src="/Scripts/Controllers/BaseController.js"></script>
<script src="/Scripts/AwesomeAngularWebApp.js"></script>
</body>

</html>

Scripts/AwesomeAngularWebApp.js

var AwesomeAngularWebApp = angular.module('AwesomeAngularWebApp', []);
AwesomeAngularWebApp.controller('BaseController', BaseController);

Scripts/Controllers/BaseController.js

var BaseController = function($scope){
    $scope.helloAgain = 'Hello Angular 1.3!';
}
BaseController.$inject = ['$scope'];

https://:8080/ 现在应该看起来像这样

我们需要一个地方来存储从 API 返回的身份验证令牌。我们可以将其存储在服务中。AngularJS 中的服务是单例的,当应用启动时会创建它们的一个实例,并且您可以通过依赖注入将此实例传递给其他地方。它们是存储应用多个部分所需值的绝佳位置。

Scripts/Services/SessionService.js

var AuthenticationService = function(){
    this.token = undefined;
}

Scripts/AwesomeAngularWebApp.js

var AwesomeAngularWebApp = angular.module('AwesomeAngularWebApp', []);
AwesomeAngularWebApp.controller('BaseController', BaseController);
AwesomeAngularWebApp.service('SessionService', SessionService);

让我们创建工厂来进行登录和注册

Scripts/Factories/LoginFactory.js

var LoginFactory = function($http, $q, SessionService){
    return function(username, password){    
        var result = $q.defer();
    
        var params = {grant_type: "password", userName: username, password: password};
    
        $http({
            method: 'POST',
            url: SessionService.apiUrl + '/token',
            transformRequest: function(obj) {
                var str = [];
                for(var p in obj)
                str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
                return str.join("&");
            },
            data: params,
            headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8;'}
        })
        .success(function(response){
            result.resolve(response);
        })
        .error(function(response){
            result.reject(response);
        });
        
        return result.promise; 
    }
}

LoginFactory.$inject = ['$http', '$q', 'SessionService'];

Scripts/Factories/RegisterFactory.js

var RegisterFactory = function($http, $q, SessionService){
    return function(email, password, confirmPassword){    
        var result = $q.defer();
    
        $http({
            method: 'POST',
            url: SessionService.apiUrl + '/api/Account/register',
            data: {Email: email, Password: password, ConfirmPassword: confirmPassword},
            headers: {'Content-Type': 'application/json'}
        })
        .success(function(response){
            result.resolve(response);
        })
        .error(function(response){
            result.reject(response);
        });
        
        return result.promise; 
    }

}

RegisterFactory.$inject = ['$http', '$q', 'SessionService'];

Scripts/AwesomeAngularWebApp.js

var AwesomeAngularWebApp = angular.module('AwesomeAngularWebApp', []);
AwesomeAngularWebApp.controller('BaseController', BaseController);
AwesomeAngularWebApp.service('SessionService', SessionService);
AwesomeAngularWebApp.factory('LoginFactory', LoginFactory);
AwesomeAngularWebApp.factory('RegisterFactory', RegisterFactory);

现在我将为登录和注册视图(我们稍后会创建)创建控制器。

Scripts/Controllers/LoginController.js

var LoginController = function($scope, $location, LoginFactory, SessionService){
    $scope.loginForm = {
        username: undefined,
        password: undefined, 
        errorMessage: undefined
    };
    
    $scope.login = function(){
        LoginFactory($scope.loginForm.username, $scope.loginForm.password)
        .then(function(response){
            SessionService.token = response.access_token;
            $location.path('/');
        }, function(response){
            $scope.loginForm.errorMessage = response.error_description;
        });
    }
}
LoginController.$inject = ['$scope', '$location', 'LoginFactory', 'SessionService'];

Scripts/Controllers/RegisterController.js

var RegisterController = function($scope, LoginFactory, RegisterFactory, SessionService){
    $scope.registerForm = {
        username: undefined,
        password: undefined, 
        confirmPassword: undefined,
        errorMessage: undefined
    };
    
    $scope.register = function(){
        RegisterFactory($scope.registerForm.username, $scope.registerForm.password, $scope.registerForm.confirmPassword)
        .then(function(){
            LoginFactory($scope.registerForm.username, $scope.registerForm.password)
            .then(function(response){
                SessionService.token = response.access_token;
            }, function(response){
                $scope.registerForm.errorMessage = response;
            });
        }, function(response){
            $scope.registerForm.errorMessage = response;
        });
    }
}
RegisterController.$inject = ['$scope', 'LoginFactory', 'RegisterFactory', 'SessionService'];

Scripts/AwesomeAngularWebApp.js

var AwesomeAngularWebApp = angular.module('AwesomeAngularWebApp', []);
AwesomeAngularWebApp.controller('BaseController', BaseController);
AwesomeAngularWebApp.controller('LoginController', LoginController);
AwesomeAngularWebApp.controller('RegisterController', RegisterController);
AwesomeAngularWebApp.service('SessionService', SessionService);
AwesomeAngularWebApp.factory('LoginFactory', LoginFactory);
AwesomeAngularWebApp.factory('RegisterFactory', RegisterFactory);

以及这些控制器对应的视图,并更新路由代码。

Views/Login.html

<form role="form" ng-submit="login()">
  <div class="form-group">
    <label for="emailAddress">Email address</label>
    <input type="email" class="form-control" id="emailAddress" placeholder="Enter email" ng-model="loginForm.username">
  </div>
  <div class="form-group">
    <label for="password">Password</label>
    <input type="password" class="form-control" id="password" placeholder="Password" ng-model="loginForm.password">
  </div>
    <p class="help-block" ng-if="loginForm.errorMessage">{{loginForm.errorMessage}}</p>
  </div>
  <button type="submit" class="btn btn-default">Login</button>
</form>

Views/Register.html

<form role="form" ng-submit="register()">
  <div class="form-group">
    <label for="emailAddress">Email address</label>
    <input type="email" class="form-control" id="emailAddress" placeholder="Enter email" ng-model="registerForm.username">
  </div>
  <div class="form-group">
    <label for="password">Password</label>
    <input type="password" class="form-control" id="password" placeholder="Password" ng-model="registerForm.password">
  </div>
  <div class="form-group">
    <label for="password">Confirm Password</label>
    <input type="password" class="form-control" id="password" placeholder="Confirm Password" ng-model="registerForm.confirmPassword">
  </div>
    <p class="help-block" ng-if="registerForm.errorMessage">{{registerForm.errorMessage}}</p>
  </div>
  <button type="submit" class="btn btn-default">Register</button>
</form>

Scripts/AwesomeAngularWebApp.js

var AwesomeAngularWebApp = angular.module('AwesomeAngularWebApp', ['ngRoute']);
AwesomeAngularWebApp.controller('BaseController', BaseController);
AwesomeAngularWebApp.controller('LoginController', LoginController);
AwesomeAngularWebApp.controller('RegisterController', RegisterController);
AwesomeAngularWebApp.service('SessionService', SessionService);
AwesomeAngularWebApp.factory('LoginFactory', LoginFactory);
AwesomeAngularWebApp.factory('RegisterFactory', RegisterFactory);

var ConfigFunction = function($routeProvider) {
  $routeProvider
   .when('/login', {
    templateUrl: 'views/login.html',
    controller: 'LoginController'
  })
  .when('/register', {
    templateUrl: 'views/register.html',
    controller: 'RegisterController'
  });
};
ConfigFunction.$inject = ['$routeProvider'];
AwesomeAngularWebApp.config(ConfigFunction);

index.html

<!DOCTYPE html>
<html>

<head>
<title>Awesome Angular Web App 2.0</title>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css">
</head>

<body ng-app="AwesomeAngularWebApp" ng-controller="BaseController">
{{helloAgain}}

<a href="/#/login" ng-if="!loggedIn()">Login</a>
<a href="/#/register" ng-if="!loggedIn()">Register</a>

<span class="label label-success" ng-if="loggedIn()">Logged In</span>

<div ng-view></div>

<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular-route.min.js"></script>

<script src="/Scripts/Controllers/BaseController.js"></script>
<script src="/Scripts/Controllers/LoginController.js"></script>
<script src="/Scripts/Controllers/RegisterController.js"></script>
<script src="/Scripts/Services/SessionService.js"></script>
<script src="/Scripts/Factories/LoginFactory.js"></script>
<script src="/Scripts/Factories/RegisterFactory.js"></script>
<script src="/Scripts/AwesomeAngularWebApp.js"></script>
</body>

</html>

Scripts/BaseController.js

var BaseController = function($scope, SessionService){
    $scope.helloAgain = 'Hello Angular 1.3!';
    
    $scope.loggedIn = function(){
        return SessionService.token !== undefined;
    }
}
BaseController.$inject = ['$scope', 'SessionService'];

好的,让我们回到 **https://:8080/**,确保一切都已正确连接。您应该能够浏览到登录和注册视图。检查您的浏览器控制台,如果有错误,您可能错过了一个步骤(或者我出错了,如果是这样,请在评论中告诉我)。

让我们尝试登录

为了避免您向上滚动,这里是之前创建的用户(除非您更改了它们)的用户名和密码(否则向上滚动也无济于事)。

  • userName: email@email.com
  • password: Password1!

让我们浏览到登录表单,并尝试登录。哦不

我们的 API 在一个服务器上,而我们的网站在另一个服务器上,因此登录请求因安全原因被浏览器阻止了。我们可以通过 CORS 来解决这个问题。

回到 Visual Studio,从 **View** => **Other Windows** => **Package Manager Console** 打开 nuget 包管理器控制台。运行以下命令

Install-Package Microsoft.Owin.Cors

Startup.cs 中,修改 Configuration 使其包含以下内容:

        public void Configuration(IAppBuilder app)
        {
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            ConfigureAuth(app);
        }

让我们再次尝试登录,首先尝试一个故意错误的用户名/密码组合。

很好,我们从服务器收到了 400 响应,并在屏幕上显示了错误消息。现在让我们正确登录。

太棒了!我们回到了主页,令牌已存储在服务中,登录和注册链接也消失了。

现在我们将编写一个工厂来从我们 API 中的 ValuesController 类 **Get** **Values**。

Scripts/Factories/GetValuesFactory.js

var GetValuesFactory = function($http, $q, SessionService){
    return function(){    
        var result = $q.defer();
    
        $http({
            method: 'GET',
            url: SessionService.apiUrl + '/api/Values',
            headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + SessionService.token}
        })
        .success(function(response){
            result.resolve(response);
        })
        .error(function(response){
            result.reject(response);
        });
        
        return result.promise; 
    }
}

GetValuesFactory.$inject = ['$http', '$q', 'SessionService'];

一个用于我们稍后将创建的视图的控制器。

Scripts/Controllers/ValuesController.js

var ValuesController = function($scope, GetValuesFactory, SessionService){
    $scope.values = [];
    $scope.error = {
        message: undefined
    };
    
    $scope.getValues = function(){
        GetValuesFactory()
        .then(function(response){
            $scope.values = response;
        }, function(response){
            $scope.error.message = response.Message;
        });
    }
}
ValuesController.$inject = ['$scope', 'GetValuesFactory', 'SessionService'];

一个视图。

Views/values.html

<button ng-click="getValues()">Get Values</button>

<ul class="nav nav-pills" ng-if="values.length > 0">
    <li ng-repeat="value in values">{{value}}<li>
</ul>

<p class="help-block" ng-if="error.message">{{error.message}}</p>

更新的路由和新内容的注册。

Scripts/AwesomeAngularWebApp.js

var AwesomeAngularWebApp = angular.module('AwesomeAngularWebApp', ['ngRoute']);
AwesomeAngularWebApp.controller('BaseController', BaseController);
AwesomeAngularWebApp.controller('LoginController', LoginController);
AwesomeAngularWebApp.controller('RegisterController', RegisterController);
AwesomeAngularWebApp.controller('ValuesController', ValuesController);
AwesomeAngularWebApp.service('SessionService', SessionService);
AwesomeAngularWebApp.factory('LoginFactory', LoginFactory);
AwesomeAngularWebApp.factory('RegisterFactory', RegisterFactory);
AwesomeAngularWebApp.factory('GetValuesFactory', GetValuesFactory);

var ConfigFunction = function($routeProvider) {
  $routeProvider
   .when('/login', {
    templateUrl: 'views/login.html',
    controller: 'LoginController'
  })
  .when('/register', {
    templateUrl: 'views/register.html',
    controller: 'RegisterController'
  })
  .when('/values', {
    templateUrl: 'views/values.html',
    controller: 'ValuesController'
  });
};
ConfigFunction.$inject = ['$routeProvider'];
AwesomeAngularWebApp.config(ConfigFunction);

一个链接,以及用于新控制器/工厂的脚本标签。

<!DOCTYPE html>
<html>

<head>
<title>Awesome Angular Web App 2.0</title>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css">
</head>

<body ng-app="AwesomeAngularWebApp" ng-controller="BaseController">
{{helloAgain}}

<a href="/#/login" ng-if="!loggedIn()">Login</a>
<a href="/#/register" ng-if="!loggedIn()">Register</a>
<a href="/#/values">Values</a>

<span class="label label-success" ng-if="loggedIn()">Logged In</span>

<div ng-view></div>

<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular-route.min.js"></script>

<script src="/Scripts/Controllers/BaseController.js"></script>
<script src="/Scripts/Controllers/LoginController.js"></script>
<script src="/Scripts/Controllers/RegisterController.js"></script>
<script src="/Scripts/Controllers/ValuesController.js"></script>
<script src="/Scripts/Services/SessionService.js"></script>
<script src="/Scripts/Factories/LoginFactory.js"></script>
<script src="/Scripts/Factories/RegisterFactory.js"></script>
<script src="/Scripts/Factories/GetValuesFactory.js"></script>
<script src="/Scripts/AwesomeAngularWebApp.js"></script>
</body>

</html>

好的,让我们启动一切。首先,导航到我们新的 values 视图并单击按钮。Web API 返回一个 401(未授权)HTTP 错误,该错误显示在屏幕上。登录并再次单击按钮,一切都会按预期工作。

别忘了我

目前,如果您登录,然后刷新页面,您将被踢出,需要重新进行身份验证。我们可以通过使用 AngularJS 的 **ng-cookies** 模块将令牌存储在 cookie 中来解决这个问题。更新 index.html 以从 Cloudflare 拉取(在 **angular-route** 标签之后添加)。

<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular-cookies.min.js"></script>

更新 AwesomeAngularWebApp.js 以将此模块注册到我们的应用中。

var AwesomeAngularWebApp = angular.module('AwesomeAngularWebApp', ['ngRoute', 'ngCookies']);

更新 SessionService 以获取令牌并将其存储在 cookie 中。

var SessionService = function($cookies){
    this.token = undefined;
    
    this.getToken = function(){
        if(!$cookies.awesomeAngularWebAppToken){
            if(!this.token){
                return undefined;
            }
            this.setToken(this.token);            
        }
        return $cookies.awesomeAngularWebAppToken;
    }
    
    this.setToken = function(token){
        this.token = token;
        $cookies.awesomeAngularWebAppToken = token;
    }
    
    this.apiUrl = 'https://:5586';
}

SessionService.$inject = ['$cookies'];

LoginController、RegisterController、BaseController 和 GetValuesFactory 调用新的 getter 和 setter 方法。

Scripts/Controllers/LoginController.js

var LoginController = function($scope, $location, LoginFactory, SessionService){
    $scope.loginForm = {
        username: undefined,
        password: undefined, 
        errorMessage: undefined
    };
    
    $scope.login = function(){
        LoginFactory($scope.loginForm.username, $scope.loginForm.password)
        .then(function(response){
            SessionService.setToken(response.access_token);
            $location.path('/');
        }, function(response){
            $scope.loginForm.errorMessage = response.error_description;
        });
    }
}
LoginController.$inject = ['$scope', '$location', 'LoginFactory', 'SessionService'];

Scripts/Controllers/RegisterController.js

var RegisterController = function($scope, LoginFactory, RegisterFactory, SessionService){
    $scope.registerForm = {
        username: undefined,
        password: undefined, 
        confirmPassword: undefined,
        errorMessage: undefined
    };
    
    $scope.register = function(){
        RegisterFactory($scope.registerForm.username, $scope.registerForm.password, $scope.registerForm.confirmPassword)
        .then(function(){
            LoginFactory($scope.registerForm.username, $scope.registerForm.password)
            .then(function(response){
                SessionService.setToken(response.access_token);
            }, function(response){
                $scope.loginForm.errorMessage = response;
            });
        }, function(response){
            $scope.registerForm.errorMessage = response;
        });
    }
}
RegisterController.$inject = ['$scope', 'LoginFactory', 'RegisterFactory', 'SessionService'];

Scripts/Controllers/BaseController.js

var BaseController = function($scope, SessionService){
    $scope.helloAgain = 'Hello Angular 1.3!';
    
    $scope.loggedIn = function(){
        return SessionService.getToken() !== undefined;
    }
}
BaseController.$inject = ['$scope', 'SessionService'];

Scripts/Factories/GetValuesFactory.js

var GetValuesFactory = function($http, $q, SessionService){
    return function(){    
        var result = $q.defer();
    
        $http({
            method: 'GET',
            url: SessionService.apiUrl + '/api/Values',
            headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + SessionService.getToken()}
        })
        .success(function(response){
            result.resolve(response);
        })
        .error(function(response){
            result.reject(response);
        });
        
        return result.promise; 
    }
}

GetValuesFactory.$inject = ['$http', '$q', 'SessionService'];

现在,当您登录并刷新页面时,您仍然登录着!万岁!

角色

正如我之前提到的,不同的用户可以拥有不同的角色。我发现使用 SQL 设置角色是最简单的方法。我们现在就来做。

打开 SQL Server Management Studio,然后将 **Server name** 输入为 **(localdb)\v11.0**,如下所示,然后单击连接。

您应该会看到一个名为 **aspnet-Awesome Angular Web App 2.0-20141116095710** 的数据库,最后的数字只是创建此数据库的时间戳。

让我们创建一个名为 SuperAdmin 的角色,并将我们创建的用户分配给此角色。使用以下 SQL 更新 AspNetRoles 表。

INSERT INTO [aspnet-Awesome Angular Web App 2.0-20141116095710].[dbo].[AspNetRoles] VALUES('8138aad6-dfdb-422d-862d-8d5acab5c8a1', 'SuperAdmin')

现在让我们更新我们的 ValuesController,使其只允许 SuperAdmin **Get** 值。像这样修改控制器上的 [Authorize] 属性。

[Authorize(Roles = "SuperAdmin")]
重要提示:当我们停止并重新启动 API 时,我们所有的令牌都将失效,因为它们存储在内存中,所以您需要同时清除浏览器的缓存。

现在,当我们访问我们的站点并登录时(使用以下凭据,方便懒人),

  • userName: email@email.com
  • password: Password1!

我们得到了 401 未授权响应。这是因为我们不是 SuperAdmin 组的成员。让我们修复这个问题,首先针对 [AspNetUsers] 运行以下 SQL 来查看我们的 ID 是什么。

SELECT [Id] FROM [aspnet-Awesome Angular Web App 2.0-20141116095710].[dbo].[AspNetUsers] WHERE [Email] = 'email@email.com'

对我来说,ID 是 **e79c2336-265e-42d0-a62c-df44a3dfa0f4**,您的 ID 会不同,所以请用您的 ID 替换下面的 SQL 中的 ID。下面的 SQL 将用户添加到该角色。

INSERT INTO [aspnet-Awesome Angular Web App 2.0-20141116095710].[dbo].[AspNetUserRoles] VALUES('e79c2336-265e-42d0-a62c-df44a3dfa0f4', '8138aad6-dfdb-422d-862d-8d5acab5c8a1')

您现在是超级管理员了,但登录时不是,所以请清除浏览器缓存,重新登录,然后重试。如果一切顺利,您将能够成功地对 Values 控制器执行 HTTP 请求,但其他非 SuperAdmin 角色的用户将无法执行。

回顾

今天,我们使用 ASP.Net Web API 创建了一个 RESTful API,一个运行在 node.js 服务器上的 AngularJS 应用程序,并成功配置了应用程序以对 API 进行身份验证,将令牌存储在 cookie 中。我们还创建了一个角色,并将 API 的某个区域限制给该角色内的用户。

第四部分预告

您想看到什么?请留言。

安全

Microsoft 的 Troy Hunt 使用本文提供的代码作为 Pluralsight 上客户端框架安全课程的基础,该课程可在 此处找到。我强烈推荐您查看一下。

评论/批评等

欢迎在文章下方留下您的评论/批评/问题等,我都会回复。感谢您的阅读 Smile | :)

© . All rights reserved.