多數AngularJs應用離不開登陸操做,最近閱讀了一篇關於AngularJs登陸的博客,博客中實現的登陸系統demo可以應用於多數小型AngularJs應用,實現也並不困難,這裏講講如何實現這個簡單的登陸系統。css
這裏使用的種子項目是 angular-seed
,登陸系統會在這個種子項目的基礎上完成
,github地址:https://github.com/angular/angular-seed/。按照github上`README.md`配置後即可在上面添加咱們本身的登陸系統。html
angular-seed文件目錄:ios
app/ --> all of the source files for the application app.css --> default stylesheet components/ --> all app specific modules version/ --> version related components version.js --> version module declaration and basic "version" value service version_test.js --> "version" value service tests version-directive.js --> custom directive that returns the current app version version-directive_test.js --> version directive tests interpolate-filter.js --> custom interpolation filter interpolate-filter_test.js --> interpolate filter tests view1/ --> the view1 view template and logic view1.html --> the partial template view1.js --> the controller logic view1_test.js --> tests of the controller view2/ --> the view2 view template and logic view2.html --> the partial template view2.js --> the controller logic view2_test.js --> tests of the controller app.js --> main application module index.html --> app layout file (the main html template file of the app) index-async.html --> just like index.html, but loads js files asynchronously karma.conf.js --> config file for running unit tests with Karma e2e-tests/ --> end-to-end tests protractor-conf.js --> Protractor config file scenarios.js --> end-to-end scenarios to be run by Protractor
這裏,主要修改app.js以及view1文件夾相關文件,其中,view1將做爲登陸界面。git
一個簡單實用的登陸表單的html文件:angularjs
<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>
將該表單代碼放入view1.html
中,而且修改view1.js
爲該表單添加對應的controller,即LoginController
.以下:github
// controller .controller('LoginController', function($scope, $rootScope, AUTH_EVENTS, AuthService) { $scope.credentials = { username : '', password : '' }; $scope.login = function(credentials) { console.log('login', credentials); AuthService.login(credentials).then(function(user) { $rootScope.$broadcast(AUTH_EVENTS.loginSuccess); $scope.$parent.setCurrentUser(user); }, function() { $rootScope.$broadcast(AUTH_EVENTS.loginFailed); }); }; })
這裏的credentials存放用戶信息,值得注意的是:這裏$scope.login
僅完成抽象邏輯,具體的邏輯實現依靠AuthService
這樣的service,在controller
裏面建議多使用抽象邏輯,而非具體的實現。c#
一般,用戶的登陸狀況會放置在服務器端的Session中,當用戶在應用內跳轉頁面時,相應的狀態會保留在Session中。這裏先定義__用戶登陸的狀態__和__用戶權限__,這裏使用constants定義:api
//用戶登陸狀態 .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' })
從LoginController
能夠看出,constants
能夠像service同樣方便注入;promise
//用戶權限 .constant('USER_ROLES', { all: '*', admin: 'admin', editor: 'editor', guest: 'guest' })
用戶登陸狀態和用戶權限將保存在Session中。瀏覽器
將登陸實現以及用戶權限管理統一交給AuthService
,可在頂層模塊中註冊該服務,這裏是app.js
中的myApp
模塊。
.factory('AuthService', function ($http, Session) { var authService = {}; authService.login = function (credentials) { //本地提供的服務,可用loopback快速搭建 var api = $resource('http://localhost:3000/api/user_tests'); //由於沒有寫服務端驗證用戶密碼,使用save是爲了方便; //這裏,若是服務端已存在該credentials,返回的response會包含錯誤信息,可用來替代40一、403等; return api.save(credentials) .$promise .then(function(res) { Session.create(res.id, res.id, res.Role); return res; }); }; 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; })
用戶登陸後,將服務器中關於用戶的Session存儲起來。
在myApp
模塊中註冊一個服務Session
,用於存儲服務端用戶的Session。
.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; }; })
當用戶登陸以後,用戶的信息(用戶名、id等)應該保存在哪裏?
這裏的作法是將用戶對象currentUser
保存在應用頂層模塊myApp
的$scope
中,因爲它位於$scope
根部,應用中任何$scope
都繼承它,子代$scope
能夠很方便地使用根的變量和方法。
.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
以便在子代$scope
中使用;由於在子代$scope
中直接給currentUser
賦值不會更新根部的currentUser
,而是在當前$scope
中新建一個currentUser
(詳細查詢scope的繼承),因此用setCurrentUser
給根'$scope'的currentUser
變量賦值。
客戶端不存在真正意義的訪問控制,畢竟代碼在客戶端手中,這種工做一般是在服務端完成的,這裏說的其實是顯示控制(visibility control).
ng-show
和ng-hide
是對DOM進行操做,會增長瀏覽器負擔;這裏選擇使用ng-if
和ng-switch
。
在view2.html
中插入:
<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>
有些頁面僅容許具備權限的用戶訪問,這裏須要限制其餘用戶的訪問,在ui-router
下能夠經過傳參進行限制,規定頁面容許訪問的角色:
.config(function ($stateProvider, USER_ROLES) { $stateProvider.state('dashboard', { url: '/dashboard', templateUrl: 'dashboard/index.html', data: { authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor] } }); })
接下來,須要在每次頁面改變前判斷用戶是否有權限訪問,經過監聽$stateChangeStart
來實現:
.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); } } }); })
若是用戶 未登陸/無權限,將被限制在當前頁面,發出 認證失敗/受權失敗 的廣播;
以後,須要有相應的交互,如彈出登陸框,提醒用戶完成登陸操做,或者彈出錯誤提示,告訴用戶無權限訪問相應的頁面。
向服務器發送請求,若是出現非法訪問等狀況,服務端將返回HTTP response會包含相應的錯誤信息,例如:
返回40一、41九、440時,須要彈出登陸框讓用戶登陸;
返回403時,須要彈出錯誤信息;
爲了方便,這裏的登陸框使用Angular
的directive
封裝,提供一個叫LoginDialog
的標籤。
.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); } }; })
loginDialog
的實現以下,經過監聽AUTH_EVENTS.notAuthenticated
和AUTH_EVENTS.sessionTimeout
,當用戶 未登陸/會話過時 時,將loginDialog
的visible
設爲true
,顯示登陸框:
.directive('loginDialog', function (AUTH_EVENTS) { return { restrict: 'A', template: '<div ng-if="visible" ng-include="\'view1/view1.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) } }; })
爲方便測試,將其放入index.html
中:
<body ng-controller='ApplicationController'> <div login-dialog ng-if="NotLoginPage"></div> <ul class="menu"> <li><a href="#!/view1">view1</a></li> <li><a href="#!/view2">view2</a></li> </ul> ...
到這裏,登陸涉及的主要模塊已經完成。