分类

链接

2017 年 8 月
 123456
78910111213
14151617181920
21222324252627
28293031  

近期文章

热门标签

新人福利,免费薅羊毛

现在位置:    首页 > 前端 > 正文
共享办公室出租
AngularJs 登录的简单实现
前端 暂无评论 阅读(711)

AngularJs 登录的简单实现

多数AngularJs应用离不开登录操作,最近阅读了一篇关于AngularJs登录的博客,博客中实现的登录系统demo能够应用于多数小型AngularJs应用,实现也并不困难,这里讲讲如何实现这个简单的登录系统。

种子项目

这里使用的种子项目是 angular-seed,登录系统会在这个种子项目的基础上完成
,github地址:https://github.com/angular/angular-seed/。按照github上`README.md`配置后便可在上面添加我们自己的登录系统。

angular-seed文件目录:

app/                    <span class="hljs-comment">--&gt; all of the source files for the application</span>
  app.css               <span class="hljs-comment">--&gt; default stylesheet</span>
    components/           <span class="hljs-comment">--&gt; all app specific modules</span>
  version/              <span class="hljs-comment">--&gt; version related components</span>
    version.js                 <span class="hljs-comment">--&gt; version module declaration and basic "version" value service</span>
    version_test.js            <span class="hljs-comment">--&gt; "version" value service tests</span>
    version-directive.js       <span class="hljs-comment">--&gt; custom directive that returns the current app version</span>
    version-directive_test.js  <span class="hljs-comment">--&gt; version directive tests</span>
    interpolate-filter.js      <span class="hljs-comment">--&gt; custom interpolation filter      interpolate-filter_test.js --&gt; interpolate filter tests</span>
  view1/                <span class="hljs-comment">--&gt; the view1 view template and logic</span>
    view1.html            <span class="hljs-comment">--&gt; the partial template</span>
    view1.js              <span class="hljs-comment">--&gt; the controller logic</span>
    view1_test.js         <span class="hljs-comment">--&gt; tests of the controller</span>
  view2/                <span class="hljs-comment">--&gt; the view2 view template and logic</span>
    view2.html            <span class="hljs-comment">--&gt; the partial template</span>
    view2.js              <span class="hljs-comment">--&gt; the controller logic</span>
    view2_test.js         <span class="hljs-comment">--&gt; tests of the controller</span>
  app.js                <span class="hljs-comment">--&gt; main application module</span>
  index.html            <span class="hljs-comment">--&gt; app layout file (the main html template file of the app)</span>
  index-async.html      <span class="hljs-comment">--&gt; just like index.html, but loads js files asynchronously</span>
karma.conf.js         <span class="hljs-comment">--&gt; config file for running unit tests with Karma</span>
e2e-tests/            <span class="hljs-comment">--&gt; end-to-end tests</span>
  protractor-conf.js    <span class="hljs-comment">--&gt; Protractor config file</span>
  scenarios.js          <span class="hljs-comment">--&gt; end-to-end scenarios to be run by Protractor</span>

这里,主要修改app.js以及view1文件夹相关文件,其中,view1将作为登录界面。

具体实现

实现登录表单

一个简单实用的登录表单的html文件:

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"loginForm"</span> <span class="hljs-attr">ng-controller</span>=<span class="hljs-string">"LoginController"</span>
  <span class="hljs-attr">ng-submit</span>=<span class="hljs-string">"login(credentials)"</span> <span class="hljs-attr">novalidate</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"username"</span>&gt;</span>Username:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"username"</span>
     <span class="hljs-attr">ng-model</span>=<span class="hljs-string">"credentials.username"</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span>&gt;</span>Password:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"password"</span>
     <span class="hljs-attr">ng-model</span>=<span class="hljs-string">"credentials.password"</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>

将该表单代码放入view1.html中,并且修改view1.js为该表单添加对应的controller,即LoginController.如下:

<span class="hljs-comment">// controller</span>
.controller(<span class="hljs-string">'LoginController'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope, $rootScope, AUTH_EVENTS, AuthService)</span> </span>{
  $scope.credentials = {
     username : <span class="hljs-string">''</span>,
     password : <span class="hljs-string">''</span>
};  
  $scope.login = <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(credentials)</span> </span>{
    console.log(<span class="hljs-string">'login'</span>, credentials);
    AuthService.login(credentials).then(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(user)</span> </span>{
        $rootScope.$broadcast(AUTH_EVENTS.loginSuccess);
        $scope.$parent.setCurrentUser(user);
    }, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
        $rootScope.$broadcast(AUTH_EVENTS.loginFailed);
    });
  };
})

这里的credentials存放用户信息,值得注意的是:这里$scope.login仅完成抽象逻辑,具体的逻辑实现依靠AuthService这样的service,在controller里面建议多使用抽象逻辑,而非具体的实现。


用户登录状态记录

通常,用户的登录情况会放置在服务器端的Session中,当用户在应用内跳转页面时,相应的状态会保留在Session中。这里先定义__用户登录的状态__和__用户权限__,这里使用constants定义:

<span class="hljs-comment">//用户登录状态</span>
.constant(<span class="hljs-string">'AUTH_EVENTS'</span>, {
  loginSuccess: <span class="hljs-string">'auth-login-success'</span>,
  loginFailed: <span class="hljs-string">'auth-login-failed'</span>,
  logoutSuccess: <span class="hljs-string">'auth-logout-success'</span>,
  sessionTimeout: <span class="hljs-string">'auth-session-timeout'</span>,
  notAuthenticated: <span class="hljs-string">'auth-not-authenticated'</span>,
  notAuthorized: <span class="hljs-string">'auth-not-authorized'</span>
})

LoginController可以看出,constants可以像service一样方便注入;

<span class="hljs-comment">//用户权限</span>
.constant(<span class="hljs-string">'USER_ROLES'</span>, {
  all: <span class="hljs-string">'*'</span>,
  admin: <span class="hljs-string">'admin'</span>,
  editor: <span class="hljs-string">'editor'</span>,
  guest: <span class="hljs-string">'guest'</span>
})

用户登录状态和用户权限将保存在Session中。


登录服务AuthService

将登录实现以及用户权限管理统一交给AuthService,可在顶层模块中注册该服务,这里是app.js中的myApp模块。

.factory(<span class="hljs-string">'AuthService'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">($http, Session)</span> </span>{
  <span class="hljs-keyword">var</span> authService = {};
 
  authService.login = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(credentials)</span> </span>{

    <span class="hljs-comment">//本地提供的服务,可用loopback快速搭建</span>
    <span class="hljs-keyword">var</span> api = $resource(<span class="hljs-string">'http://localhost:3000/api/user_tests'</span>);
    
    <span class="hljs-comment">//因为没有写服务端验证用户密码,使用save是为了方便;</span>
    <span class="hljs-comment">//这里,如果服务端已存在该credentials,返回的response会包含错误信息,可用来替代401、403等;</span>
    <span class="hljs-keyword">return</span> api.save(credentials)
        .$promise
        .then(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(res)</span> </span>{
            Session.create(res.id, res.id,
                           res.Role);
            <span class="hljs-keyword">return</span> res;
        });
  };
 
  authService.isAuthenticated = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> !!Session.userId;
  };
 
  authService.isAuthorized = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(authorizedRoles)</span> </span>{
    <span class="hljs-keyword">if</span> (!angular.isArray(authorizedRoles)) {
      authorizedRoles = [authorizedRoles];
    }
    <span class="hljs-keyword">return</span> (authService.isAuthenticated() &amp;&amp;
      authorizedRoles.indexOf(Session.userRole) !== <span class="hljs-number">-1</span>);
  };
 
  <span class="hljs-keyword">return</span> authService;
})

Session

用户登录后,将服务器中关于用户的Session存储起来。

myApp模块中注册一个服务Session,用于存储服务端用户的Session。

.service(<span class="hljs-string">'Session'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> () </span>{
  <span class="hljs-keyword">this</span>.create = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">sessionId, userId, userRole</span>) </span>{
    <span class="hljs-keyword">this</span>.id = sessionId;
    <span class="hljs-keyword">this</span>.userId = userId;
    <span class="hljs-keyword">this</span>.userRole = userRole;
  };
  <span class="hljs-keyword">this</span>.destroy = <span class="hljs-function"><span class="hljs-keyword">function</span> () </span>{
    <span class="hljs-keyword">this</span>.id = <span class="hljs-literal">null</span>;
    <span class="hljs-keyword">this</span>.userId = <span class="hljs-literal">null</span>;
    <span class="hljs-keyword">this</span>.userRole = <span class="hljs-literal">null</span>;
  };
})

用户信息

当用户登录之后,用户的信息(用户名、id等)应该保存在哪里?

这里的做法是将用户对象currentUser保存在应用顶层模块myApp$scope中,由于它位于$scope根部,应用中任何$scope都继承它,子代$scope可以很方便地使用根的变量和方法。

.controller(<span class="hljs-string">'ApplicationController'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">($scope, USER_ROLES, AuthService)</span> </span>{
  $scope.currentUser = <span class="hljs-keyword">null</span>;
  $scope.userRoles = USER_ROLES;
  $scope.isAuthorized = AuthService.isAuthorized;
 
  $scope.setCurrentUser = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(user)</span> </span>{
    $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中插入:

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">ng-if</span>=<span class="hljs-string">"currentUser"</span>&gt;</span>Welcome, </span><span class="hljs-template-variable">{{ currentUser.name }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">ng-if</span>=<span class="hljs-string">"isAuthorized(userRoles.admin)"</span>&gt;</span>You're admin.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">ng-switch</span> <span class="hljs-attr">on</span>=<span class="hljs-string">"currentUser.role"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">ng-switch-when</span>=<span class="hljs-string">"userRoles.admin"</span>&gt;</span>You're admin.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">ng-switch-when</span>=<span class="hljs-string">"userRoles.editor"</span>&gt;</span>You're editor.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">ng-switch-default</span>&gt;</span>You're something else.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>

限制访问

有些页面仅允许具有权限的用户访问,这里需要限制其他用户的访问,在ui-router下可以通过传参进行限制,规定页面允许访问的角色:

.config(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">($stateProvider, USER_ROLES)</span> </span>{
  $stateProvider.state(<span class="hljs-string">'dashboard'</span>, {
    url: <span class="hljs-string">'/dashboard'</span>,
    templateUrl: <span class="hljs-string">'dashboard/index.html'</span>,
    data: {
      authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor]
    }
  });
})

接下来,需要在每次页面改变前判断用户是否有权限访问,通过监听$stateChangeStart来实现:

.run(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">($rootScope, AUTH_EVENTS, AuthService)</span> </span>{
  $rootScope.$on(<span class="hljs-string">'$stateChangeStart'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(event, next)</span> </span>{
    <span class="hljs-keyword">var</span> authorizedRoles = next.data.authorizedRoles;
    <span class="hljs-keyword">if</span> (!AuthService.isAuthorized(authorizedRoles)) {
      event.preventDefault();
      <span class="hljs-keyword">if</span> (AuthService.isAuthenticated()) {
        <span class="hljs-comment">// user is not allowed</span>
        $rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// user is not logged in</span>
        $rootScope.$broadcast(AUTH_EVENTS.notAuthenticated);
      }
    }
  });
})

如果用户 未登录/无权限,将被限制在当前页面,发出 认证失败/授权失败 的广播;
之后,需要有相应的交互,如弹出登录框,提醒用户完成登录操作,或者弹出错误提示,告诉用户无权限访问相应的页面。

会话过期(Session expiration)

向服务器发送请求,如果出现非法访问等情况,服务端将返回HTTP response会包含相应的错误信息,例如:

  • 401 Unauthorized — 用户未登录
  • 403 Forbidden — 已登录,但无权限访问
  • 419 Authentication Timeout (non standard) — 会话过期
  • 440 Login Timeout (Microsoft only) — 会话过期

返回401、419、440时,需要弹出登录框让用户登录;
返回403时,需要弹出错误信息;
为了方便,这里的登录框使用Angulardirective封装,提供一个叫LoginDialog的标签。

.config(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">($httpProvider)</span> </span>{
  $httpProvider.interceptors.push([
    <span class="hljs-string">'$injector'</span>,
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">($injector)</span> </span>{
      <span class="hljs-keyword">return</span> $injector.get(<span class="hljs-string">'AuthInterceptor'</span>);
    }
  ]);
})
.factory(<span class="hljs-string">'AuthInterceptor'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">($rootScope, $q,
                                      AUTH_EVENTS)</span> </span>{
  <span class="hljs-keyword">return</span> {
    responseError: <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(response)</span> </span>{ 
      $rootScope.$broadcast({
        <span class="hljs-number">401</span>: AUTH_EVENTS.notAuthenticated,
        <span class="hljs-number">403</span>: AUTH_EVENTS.notAuthorized,
        <span class="hljs-number">419</span>: AUTH_EVENTS.sessionTimeout,
        <span class="hljs-number">440</span>: AUTH_EVENTS.sessionTimeout
      }[response.status], response);
      <span class="hljs-keyword">return</span> $q.reject(response);
    }
  };
})

loginDialog的实现如下,通过监听AUTH_EVENTS.notAuthenticatedAUTH_EVENTS.sessionTimeout,当用户 未登录/会话过期 时,将loginDialogvisible设为true,显示登录框:

.directive(<span class="hljs-string">'loginDialog'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(AUTH_EVENTS)</span> </span>{
  <span class="hljs-keyword">return</span> {
    restrict: <span class="hljs-string">'A'</span>,
    template: <span class="hljs-string">'&lt;div ng-if="visible" ng-include="\'view1/view1.html\'"&gt;'</span>,
    link: <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(scope)</span> </span>{
      <span class="hljs-keyword">var</span> showDialog = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
        scope.visible = <span class="hljs-keyword">true</span>;
      };
  
      scope.visible = <span class="hljs-keyword">false</span>;
      scope.$on(AUTH_EVENTS.notAuthenticated, showDialog);
      scope.$on(AUTH_EVENTS.sessionTimeout, showDialog)
    }
  };
})

为方便测试,将其放入index.html中:

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">ng-controller</span>=<span class="hljs-string">'ApplicationController'</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">login-dialog</span> <span class="hljs-attr">ng-if</span>=<span class="hljs-string">"NotLoginPage"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"menu"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#!/view1"</span>&gt;</span>view1<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#!/view2"</span>&gt;</span>view2<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
...</span>

到这里,登录涉及的主要模块已经完成。

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

============ 欢迎各位老板打赏~ ===========

本文版权归Bruce's Blog所有,转载引用请完整注明以下信息:
本文作者:Bruce
本文地址:AngularJs 登录的简单实现 | Bruce's Blog

发表评论

留言无头像?