关键要点
身份验证和授权是几乎所有严肃应用程序的重要组成部分。单页应用程序 (SPA) 也不例外。应用程序可能不会将其所有数据和功能都公开给任何用户。用户可能必须进行身份验证才能查看应用程序的某些部分,或在应用程序上执行某些操作。为了识别应用程序中的用户,我们需要让用户登录。
在传统的服务器驱动应用程序和单页应用程序中,用户管理的实现方式有所不同。SPA 与其服务器组件交互的唯一方式是通过 AJAX。即使对于登录和注销也是如此。
负责识别用户的服务器必须公开身份验证端点。SPA 将把用户输入的凭据发送到此端点进行验证。在典型的基于令牌的身份验证系统中,服务在验证凭据后可能会返回访问令牌或包含已登录用户的名称和角色的对象。客户端必须在对服务器进行的所有安全 API 请求中使用此访问令牌。
由于访问令牌将被多次使用,因此最好将其存储在客户端。在 Angular 中,我们可以将值存储在服务或值中,因为它们是客户端上的单例对象。但是,如果用户刷新页面,服务或值中的值将丢失。在这种情况下,最好使用浏览器提供的持久性机制之一来存储令牌;最好是 sessionStorage,因为它在浏览器关闭后会被清除。
实现登录
现在让我们来看一些代码。假设我们已经实现了所有服务器端逻辑,并且服务在 /api/login 处公开了一个 REST 端点来检查登录凭据并返回访问令牌。让我们编写一个简单的服务,通过访问身份验证端点来执行登录操作。稍后我们将向此服务添加更多功能:
app.factory("authenticationSvc", function($http, $q, $window) { var userInfo; function login(userName, password) { var deferred = $q.defer(); $http.post("/api/login", { userName: userName, password: password }).then(function(result) { userInfo = { accessToken: result.data.access_token, userName: result.data.userName }; $window.sessionStorage["userInfo"] = JSON.stringify(userInfo); deferred.resolve(userInfo); }, function(error) { deferred.reject(error); }); return deferred.promise; } return { login: login }; });
在实际代码中,您可能希望将存储数据到 sessionStorage 的语句重构到单独的服务中,因为如果我们这样做,此服务将承担多个责任。为了使演示保持简单,我将其保留在同一个服务中。此服务可以由处理应用程序登录功能的控制器使用。
保护路由
我们可能在应用程序中有一组受保护的路由。如果用户未登录并尝试进入这些路由之一,则应将用户定向到登录页面。这可以使用路由选项中的 resolve 块来实现。以下代码片段说明了实现方法:
$routeProvider.when("/", { templateUrl: "templates/home.html", controller: "HomeController", resolve: { auth: ["$q", "authenticationSvc", function($q, authenticationSvc) { var userInfo = authenticationSvc.getUserInfo(); if (userInfo) { return $q.when(userInfo); } else { return $q.reject({ authenticated: false }); } }] } });
resolve 块可以包含多个语句块,这些语句块必须在完成时返回 promise 对象。只是为了澄清,上面定义的名称 auth 不是由框架定义的;我定义了它。您可以根据用例将名称更改为任何名称。
有多种原因会导致传递或拒绝路由。根据场景,您可以在解析/拒绝 promise 时传递对象。我们尚未在服务中实现 getLoggedInUser() 方法。这是一个简单的方法,它从服务返回 loggedInUser 对象。
app.factory("authenticationSvc", function() { var userInfo; function getUserInfo() { return userInfo; } });
通过上述代码片段中 promise 发送的对象通过 $rootScope 广播。如果路由已解析,则会广播事件 $routeChangeSuccess。但是,如果路由失败,则会广播事件 $routeChangeError。我们可以监听 $routeChangeError 事件并将用户重定向到登录页面。由于事件位于 $rootScope 级别,因此最好在运行块中附加事件处理程序。
app.run(["$rootScope", "$location", function($rootScope, $location) { $rootScope.$on("$routeChangeSuccess", function(userInfo) { console.log(userInfo); }); $rootScope.$on("$routeChangeError", function(event, current, previous, eventObj) { if (eventObj.authenticated === false) { $location.path("/login"); } }); }]);
处理页面刷新
当用户点击页面的刷新按钮时,服务会丢失其状态。我们必须从浏览器的 sessionStorage 获取数据并将其分配给变量 loggedInUser。由于工厂只调用一次,因此我们可以在初始化函数中设置此变量,如下所示。
function init() { if ($window.sessionStorage["userInfo"]) { userInfo = JSON.parse($window.sessionStorage["userInfo"]); } } init();
注销
当用户从应用程序注销时,必须调用相应的 API,并在请求标头中包含访问令牌。用户注销后,我们也应该清除 sessionStorage 中的数据。以下示例包含必须添加到身份验证服务的注销函数。
function logout() { var deferred = $q.defer(); $http({ method: "POST", url: logoutUrl, headers: { "access_token": userInfo.accessToken } }).then(function(result) { $window.sessionStorage["userInfo"] = null; userInfo = null; deferred.resolve(result); }, function(error) { deferred.reject(error); }); return deferred.promise; }
结论
在单页应用程序中实现身份验证的方法与传统 Web 应用程序的方法大相径庭。由于大部分工作是在客户端进行的,因此用户的状态也必须存储在客户端的某个位置。重要的是要记住,也必须在服务器端维护和验证状态,因为黑客可能会窃取存储在客户端系统上的数据。
本文中的源代码可在 GitHub 上下载。
关于在 Angular 应用程序中实现身份验证的常见问题解答
withCredentials 属性用于在 HTTP 请求中包含身份验证 cookie。在 Angular 中,您可以在 HttpClient 模块中使用它。发出请求时,您可以将 withCredentials 属性设置为 true。这是一个示例:
this.http.get(url, { withCredentials: true }).subscribe(...);
这将包含服务器之前可能已发送的任何 cookie。
HttpInterceptor 是 Angular 中的一项功能,允许您在将 HTTP 请求发送到服务器之前全局拦截和修改它们。它对于各种任务非常有用,例如向所有请求添加身份验证令牌或全局处理错误。
要创建自定义 HttpInterceptor,您需要创建一个实现 HttpInterceptor 接口的服务。这是一个示例:
app.factory("authenticationSvc", function($http, $q, $window) { var userInfo; function login(userName, password) { var deferred = $q.defer(); $http.post("/api/login", { userName: userName, password: password }).then(function(result) { userInfo = { accessToken: result.data.access_token, userName: result.data.userName }; $window.sessionStorage["userInfo"] = JSON.stringify(userInfo); deferred.resolve(userInfo); }, function(error) { deferred.reject(error); }); return deferred.promise; } return { login: login }; });
此拦截器将克隆每个请求并将 withCredentials 属性设置为 true。
CORS(跨源资源共享)是一项安全功能,它限制了跨域共享资源的方式。如果您收到 CORS 错误,则表示服务器未配置为接受来自您域的请求。要解决此问题,您需要将服务器配置为在“Access-Control-Allow-Origin”标头中包含您的域,并将“Access-Control-Allow-Credentials”设置为 true。
XMLHttpRequest withCredentials 属性的工作方式与 Angular HttpClient withCredentials 属性类似。它用于在请求中包含 cookie。这是一个示例:
$routeProvider.when("/", { templateUrl: "templates/home.html", controller: "HomeController", resolve: { auth: ["$q", "authenticationSvc", function($q, authenticationSvc) { var userInfo = authenticationSvc.getUserInfo(); if (userInfo) { return $q.when(userInfo); } else { return $q.reject({ authenticated: false }); } }] } });
这将发送包含 cookie 的 GET 请求到指定的 URL。
其余的常见问题解答与HttpClientModule 的使用有关,与文章的核心主题(Angular 中的身份验证实现)关系不大,因此在此处省略。 这些问题可以很容易地通过搜索引擎找到答案。
以上是在角度应用中实施身份验证的详细内容。更多信息请关注PHP中文网其他相关文章!