關鍵要點
身份驗證和授權是幾乎所有嚴肅應用程序的重要組成部分。單頁應用程序 (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中文網其他相關文章!