AngularJs登陸

AngularJs 登陸的簡單實現

多數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

將登陸實現以及用戶權限管理統一交給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

用戶登陸後,將服務器中關於用戶的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).

AngularJs隱藏信息

ng-showng-hide是對DOM進行操做,會增長瀏覽器負擔;這裏選擇使用ng-ifng-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);
      }
    }
  });
})

若是用戶 未登陸/無權限,將被限制在當前頁面,發出 認證失敗/受權失敗 的廣播;
以後,須要有相應的交互,如彈出登陸框,提醒用戶完成登陸操做,或者彈出錯誤提示,告訴用戶無權限訪問相應的頁面。

會話過時(Session expiration)

向服務器發送請求,若是出現非法訪問等狀況,服務端將返回HTTP response會包含相應的錯誤信息,例如:

  • 401 Unauthorized — 用戶未登陸
  • 403 Forbidden — 已登陸,但無權限訪問
  • 419 Authentication Timeout (non standard) — 會話過時
  • 440 Login Timeout (Microsoft only) — 會話過時

返回40一、41九、440時,須要彈出登陸框讓用戶登陸;
返回403時,須要彈出錯誤信息;
爲了方便,這裏的登陸框使用Angulardirective封裝,提供一個叫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.notAuthenticatedAUTH_EVENTS.sessionTimeout,當用戶 未登陸/會話過時 時,將loginDialogvisible設爲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>
...

到這裏,登陸涉及的主要模塊已經完成。

本文主要參考:https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec#.kr5puik92

相關文章
相關標籤/搜索