国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

AngularJS 應(yīng)用身份認(rèn)證的技巧總結(jié)

2019-11-19 19:02:14
字體:
供稿:網(wǎng)友

在web中很多時候都能應(yīng)用到身份認(rèn)證,本文介紹了AngularJS 應(yīng)用身份認(rèn)證的技巧,廢話不多說了一起往下看吧。

身份認(rèn)證

最普遍的身份認(rèn)證方式就是用用戶名(或 email)和密碼做登陸操作。這就意味要實現(xiàn)一個登陸的表單,以便用戶能夠用他們個人信息登陸。這個表單看起來是這樣的:

<form name="loginForm" ng-controller="LoginController"   ng-submit="login(credentials)" novalidate> <label for="username">Username:</label> <input type="text" id="username"     ng-model="credentials.username"> <label for="password">Password:</label> <input type="password" id="password"     ng-model="credentials.password"> <button type="submit">Login</button></form>

既然這個是 Angular-powered 的表單,我們使用 ngSubmit 指令去觸發(fā)上傳表單時的函數(shù)。注意一點的是,我們把個人信息傳入到上傳表單的函數(shù),而不是直接使用 $scope.credentials 這個對象。這樣使得函數(shù)更容易進(jìn)行 unit-test 和降低這個函數(shù)與當(dāng)前 Controller 作用域的耦合。這個 Controller 看起來是這樣的:

.controller('LoginController', function ($scope, $rootScope, AUTH_EVENTS, AuthService) { $scope.credentials = {  username: '',  password: '' }; $scope.login = function (credentials) {  AuthService.login(credentials).then(function (user) {   $rootScope.$broadcast(AUTH_EVENTS.loginSuccess);   $scope.setCurrentUser(user);  }, function () {   $rootScope.$broadcast(AUTH_EVENTS.loginFailed);  }); };javascript:void(0);})

我們注意到這里是缺少實際的邏輯的。這個 Controller 被做成這樣,目的是使身份認(rèn)證的邏輯跟表單解耦。把邏輯盡可能的從我們的 Controller 里面抽離出來,把他們都放到 services 里面,這是個很好的想法。AngularJS 的 Controller 應(yīng)該只管理 $scope 里面的對象(用 watching 或者 手動操作)而不是承擔(dān)過多過分重的東西。

通知 Session 的變化

身份認(rèn)證會影響整個應(yīng)用的狀態(tài)。基于這個原因我更推薦使用事件(用 $broadcast)去通知 user session 的改變。把所有可能用到的事件代碼定義在一個中間地帶是個不錯的選擇。我喜歡用 constants 去做這個事情:

.constant('AUTH_EVENTS', { loginSuccess: 'auth-login-success', loginFailed: 'auth-login-failed', logoutSuccess: 'auth-logout-success', sessionTimeout: 'auth-session-timeout', notAuthenticated: 'auth-not-authenticated', notAuthorized: 'auth-not-authorized'})

constants 有個很好的特性就是他們能隨便注入到別的地方,就像 services 那樣。這樣使得 constants 很容易被我們的 unit-test 調(diào)用。constants 也允許你很容易地在隨后對他們重命名而不需要改一大串文件。同樣的戲法運用到了 user roles:

.constant('USER_ROLES', { all: '*', admin: 'admin', editor: 'editor', guest: 'guest'})

如果你想給予 editors 和 administrators 同樣的權(quán)限,你只需要簡單地把 ‘editor' 改成 ‘a(chǎn)dmin'。

The AuthService

與身份認(rèn)證和授權(quán)(訪問控制)相關(guān)的邏輯最好被放到同一個 service:

.factory('AuthService', function ($http, Session) { var authService = {}; authService.login = function (credentials) {  return $http   .post('/login', credentials)   .then(function (res) {    Session.create(res.data.id, res.data.user.id,            res.data.user.role);    return res.data.user;   }); }; authService.isAuthenticated = function () {  return !!Session.userId; }; authService.isAuthorized = function (authorizedRoles) {  if (!angular.isArray(authorizedRoles)) {   authorizedRoles = [authorizedRoles];  }  return (authService.isAuthenticated() &&   authorizedRoles.indexOf(Session.userRole) !== -1); }; return authService;})

為了進(jìn)一步遠(yuǎn)離身份認(rèn)證的擔(dān)憂,我使用另一個 service(一個單例對象,using the service style)去保存用戶的 session 信息。session 的信息細(xì)節(jié)是依賴于后端的實現(xiàn),但是我還是給出一個較普遍的例子吧:

.service('Session', function () { this.create = function (sessionId, userId, userRole) {  this.id = sessionId;  this.userId = userId;  this.userRole = userRole; }; this.destroy = function () {  this.id = null;  this.userId = null;  this.userRole = null; }; return this;})

一旦用戶登錄了,他的信息應(yīng)該會被展示在某些地方(比如右上角用戶頭像什么的)。為了實現(xiàn)這個,用戶對象必須要被 $scope 對象引用,更好的是一個可以被全局調(diào)用的地方。雖然 $rootScope 是顯然易見的第一個選擇,但是我嘗試克制自己,不過多地使用 $rootScope(實際上我只在全局事件廣播使用 $rootScope)。用我所喜歡的方式去做這個事情,就是在應(yīng)用的根節(jié)點,或者在別的至少高于 Dom 樹的地方,定義一個 controller 。 標(biāo)簽是個很好的選擇:

<body ng-controller="ApplicationController"> ...</body>

ApplicationController 是應(yīng)用的全局邏輯的容器和一個用于運行 Angular 的 run 方法的選擇。因此它要處于 $scope 樹的根,所有其他的 scope 會繼承它(除了隔離 scope)。這是個很好的地方去定義 currentUser 對象:

.controller('ApplicationController', function ($scope,                        USER_ROLES,                        AuthService) { $scope.currentUser = null; $scope.userRoles = USER_ROLES; $scope.isAuthorized = AuthService.isAuthorized; $scope.setCurrentUser = function (user) {  $scope.currentUser = user; };})

我們實際上不分配 currentUser 對象,我們僅僅初始化作用域上的屬性以便 currentUser 能在后面被訪問到。不幸的是,我們不能簡單地在子作用域分配一個新的值給 currentUser 因為那樣會造成 shadow property。這是用以值傳遞原始類型(strings, numbers, booleans,undefined and null)代替以引用傳遞原始類型的結(jié)果。為了防止 shadow property,我們要使用 setter 函數(shù)。如果想了解更多 Angular 作用域和原形繼承,請閱讀 Understanding Scopes。

訪問控制

身份認(rèn)證,也就是訪問控制,其實在 AngularJS 并不存在。因為我們是客戶端應(yīng)用,所有源碼都在用戶手上。沒有辦法阻止用戶篡改代碼以獲得認(rèn)證后的界面。我們能做的只是顯示控制。如果你需要真正的身份認(rèn)證,你需要在服務(wù)器端做這個事情,但是這個超出了本文范疇。

限制元素的顯示

AngularJS 擁有基于作用域或者表達(dá)式來控制顯示或者隱藏元素的指令: ngShow, ngHide, ngIf 和 ngSwitch。前兩個會使用一個 <style> 屬性去隱藏元素,但是后兩個會從 DOM 移除元素。

第一種方式,也就是隱藏元素,最好用于表達(dá)式頻繁改變并且沒有包含過多的模板邏輯和作用域引用的元素上。原因是在隱藏的元素里,這些元素的模板邏輯仍然會在每個 digest 循環(huán)里重新計算,使得應(yīng)用性能下降。第二種方式,移除元素,也會移除所有在這個元素上的 handler 和作用域綁定。改變 DOM 對于瀏覽器來說是很大工作量的(在某些場景,和 ngShow/ngHide 對比),但是在很多時候這種代價是值得的。因為用戶訪問信息不會經(jīng)常改變,使用 ngIf 或 ngShow 是最好的選擇:

<div ng-if="currentUser">Welcome, {{ currentUser.name }}</div><div ng-if="isAuthorized(userRoles.admin)">You're admin.</div><div ng-switch on="currentUser.role"> <div ng-switch-when="userRoles.admin">You're admin.</div> <div ng-switch-when="userRoles.editor">You're editor.</div> <div ng-switch-default>You're something else.</div></div>

限制路由訪問

很多時候你會想讓整個網(wǎng)頁都不能被訪問,而不是僅僅隱藏一個元素。如果可以再路由(在UI Router 里,路由也叫狀態(tài))使用一種自定義的數(shù)據(jù)結(jié)構(gòu),我們就可以明確哪些用戶角色可以被允許訪問哪些內(nèi)容。下面這個例子使用 UI Router 的風(fēng)格,但是這些同樣適用于 ngRoute。

.config(function ($stateProvider, USER_ROLES) { $stateProvider.state('dashboard', {  url: '/dashboard',  templateUrl: 'dashboard/index.html',  data: {   authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor]  } });})

下一步,我們需要檢查每次路由變化(就是用戶跳轉(zhuǎn)到其他頁面的時候)。這需要監(jiān)聽 $routeChangStart(ngRoute 里的)或者 $stateChangeStart(UI Router 里的)事件:

.run(function ($rootScope, AUTH_EVENTS, AuthService) { $rootScope.$on('$stateChangeStart', function (event, next) {  var authorizedRoles = next.data.authorizedRoles;  if (!AuthService.isAuthorized(authorizedRoles)) {   event.preventDefault();   if (AuthService.isAuthenticated()) {    // user is not allowed    $rootScope.$broadcast(AUTH_EVENTS.notAuthorized);   } else {    // user is not logged in    $rootScope.$broadcast(AUTH_EVENTS.notAuthenticated);   }  } });})

Session 時效

身份認(rèn)證多半是服務(wù)器端的事情。無論你用什么實現(xiàn)方式,你的后端會對用戶信息做真正的驗證和處理諸如 Session 時效和訪問控制的處理。這意味著你的 API 會有時返回一些認(rèn)證錯誤。標(biāo)準(zhǔn)的錯誤碼就是 HTTP 狀態(tài)嗎。普遍使用這些錯誤碼:

  • 401 Unauthorized ― The user is not logged in
  • 403 Forbidden ― The user is logged in but isn't allowed access
  • 419 Authentication Timeout (non standard) ― Session has expired
  • 440 Login Timeout (Microsoft only) ― Session has expired

后兩種不是標(biāo)準(zhǔn)內(nèi)容,但是可能廣泛應(yīng)用。最好的官方的判斷 session 過期的錯誤碼是 401。無論怎樣,你的登陸對話框都應(yīng)該在 API 返回 401, 419, 440 或者 403 的時候馬上顯示出來??偟膩碚f,我們想廣播和基于這些 HTTP 返回碼的時間,為此我們在 $httpProvider 增加一個攔截器:

.config(function ($httpProvider) { $httpProvider.interceptors.push([  '$injector',  function ($injector) {   return $injector.get('AuthInterceptor');  } ]);}).factory('AuthInterceptor', function ($rootScope, $q,                   AUTH_EVENTS) { return {  responseError: function (response) {    $rootScope.$broadcast({    401: AUTH_EVENTS.notAuthenticated,    403: AUTH_EVENTS.notAuthorized,    419: AUTH_EVENTS.sessionTimeout,    440: AUTH_EVENTS.sessionTimeout   }[response.status], response);   return $q.reject(response);  } };})

這只是一個認(rèn)證攔截器的簡單實現(xiàn)。有個很棒的項目在 Github ,它做了相同的事情,并且使用了 httpBuffer 服務(wù)。當(dāng)返回 HTTP 錯誤碼時,它會阻止用戶進(jìn)一步的請求,直到用戶再次登錄,然后繼續(xù)這個請求。

登錄對話框指令

當(dāng)一個 session 過期了,我們需要用戶重新進(jìn)入他的賬號。為了防止他丟失他當(dāng)前的工作,最好的方法就是彈出登錄登錄對話框,而不是跳轉(zhuǎn)到登錄頁面。這個對話框需要監(jiān)聽 notAuthenticated 和 sessionTimeout 事件,所以當(dāng)其中一個事件被觸發(fā)了,對話框就要打開:

.directive('loginDialog', function (AUTH_EVENTS) { return {  restrict: 'A',  template: '<div ng-if="visible"          ng-include="/'login-form.html/'">',  link: function (scope) {   var showDialog = function () {    scope.visible = true;   };   scope.visible = false;   scope.$on(AUTH_EVENTS.notAuthenticated, showDialog);   scope.$on(AUTH_EVENTS.sessionTimeout, showDialog)  } };})

只要你喜歡,這個對話框可以隨便擴展。主要的思想是重用已存在的登陸表單模板和 LoginController。你需要在每個頁面寫上如下的代碼:

<div login-dialog ng-if="!isLoginPage"></div>

注意 isLoginPage 檢查。一個失敗了的登陸會觸發(fā) notAuthenticated 時間,但我們不想在登陸頁面顯示這個對話框,因為這很多余和奇怪。這就是為什么我們不把登陸對話框也放在登陸頁面的原因。所以在 ApplicationController 里定義一個 $scope.isLoginPage 是合理的。

保存用戶狀態(tài)

在用戶刷新他們的頁面,依舊保存已登陸的用戶信息是單頁應(yīng)用認(rèn)證里面狡猾的一個環(huán)節(jié)。因為所有狀態(tài)都存在客戶端,刷新會清空用戶信息。為了修復(fù)這個問題,我通常實現(xiàn)一個會返回已登陸的當(dāng)前用戶的數(shù)據(jù)的 API (比如 /profile),這個 API 會在 AngularJS 應(yīng)用啟動(比如在 “run” 函數(shù))。然后用戶數(shù)據(jù)會被保存在 Session 服務(wù)或者 $rootScope,就像用戶已經(jīng)登陸后的狀態(tài)。或者,你可以把用戶數(shù)據(jù)直接嵌入到 index.html,這樣就不用額外的請求了。第三種方式就是把用戶數(shù)據(jù)存在 cookie 或者 LocalStorage,但這會使得登出或者清空用戶數(shù)據(jù)變得困難一點。

最后……

鄙人才疏學(xué)淺,一點點經(jīng)驗,這是一篇翻譯的文章,如有謬誤,歡迎指正。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 富源县| 洛浦县| 池州市| 莱西市| 义马市| 天峨县| 台北县| 双牌县| 怀安县| 咸丰县| 阜平县| 赤壁市| 阜平县| 连云港市| 花莲县| 西华县| 台北市| 离岛区| 五峰| 石嘴山市| 湟源县| 仁寿县| 微山县| 敖汉旗| 炉霍县| 仁寿县| 榆树市| 永丰县| 阿巴嘎旗| 本溪| 枞阳县| 开封县| 府谷县| 佛山市| 邢台县| 门源| 湘乡市| 磐石市| 电白县| 原平市| 景谷|