AngularJs 登录的简单实现
多数AngularJs应用离不开登录操作,最近阅读了一篇关于AngularJs登录的博客,博客中实现的登录系统demo能够应用于多数小型AngularJs应用,实现也并不困难,这里讲讲如何实现这个简单的登录系统。
种子项目
这里使用的种子项目是 angular-seed
,登录系统会在这个种子项目的基础上完成
,github地址:https://github.com/angular/angular-seed/。按照github上`README.md`配置后便可在上面添加我们自己的登录系统。
angular-seed文件目录:
app/ <span class="hljs-comment">--> all of the source files for the application</span>
app.css <span class="hljs-comment">--> default stylesheet</span>
components/ <span class="hljs-comment">--> all app specific modules</span>
version/ <span class="hljs-comment">--> version related components</span>
version.js <span class="hljs-comment">--> version module declaration and basic "version" value service</span>
version_test.js <span class="hljs-comment">--> "version" value service tests</span>
version-directive.js <span class="hljs-comment">--> custom directive that returns the current app version</span>
version-directive_test.js <span class="hljs-comment">--> version directive tests</span>
interpolate-filter.js <span class="hljs-comment">--> custom interpolation filter interpolate-filter_test.js --> interpolate filter tests</span>
view1/ <span class="hljs-comment">--> the view1 view template and logic</span>
view1.html <span class="hljs-comment">--> the partial template</span>
view1.js <span class="hljs-comment">--> the controller logic</span>
view1_test.js <span class="hljs-comment">--> tests of the controller</span>
view2/ <span class="hljs-comment">--> the view2 view template and logic</span>
view2.html <span class="hljs-comment">--> the partial template</span>
view2.js <span class="hljs-comment">--> the controller logic</span>
view2_test.js <span class="hljs-comment">--> tests of the controller</span>
app.js <span class="hljs-comment">--> main application module</span>
index.html <span class="hljs-comment">--> app layout file (the main html template file of the app)</span>
index-async.html <span class="hljs-comment">--> just like index.html, but loads js files asynchronously</span>
karma.conf.js <span class="hljs-comment">--> config file for running unit tests with Karma</span>
e2e-tests/ <span class="hljs-comment">--> end-to-end tests</span>
protractor-conf.js <span class="hljs-comment">--> Protractor config file</span>
scenarios.js <span class="hljs-comment">--> end-to-end scenarios to be run by Protractor</span>
这里,主要修改app.js以及view1文件夹相关文件,其中,view1将作为登录界面。
具体实现
实现登录表单
一个简单实用的登录表单的html文件:
<span class="xml"><span class="hljs-tag"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"username"</span>></span>Username:<span class="hljs-tag"></<span class="hljs-name">label</span>></span>
<span class="hljs-tag"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span>></span>Password:<span class="hljs-tag"></<span class="hljs-name">label</span>></span>
<span class="hljs-tag"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>></span>Login<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">form</span>></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() &&
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-show
和ng-hide
是对DOM进行操作,会增加浏览器负担;这里选择使用ng-if
和ng-switch
。
在view2.html
中插入:
<span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">ng-if</span>=<span class="hljs-string">"currentUser"</span>></span>Welcome, </span><span class="hljs-template-variable">{{ currentUser.name }}</span><span class="xml"><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">ng-if</span>=<span class="hljs-string">"isAuthorized(userRoles.admin)"</span>></span>You're admin.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">ng-switch-when</span>=<span class="hljs-string">"userRoles.admin"</span>></span>You're admin.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">ng-switch-when</span>=<span class="hljs-string">"userRoles.editor"</span>></span>You're editor.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">ng-switch-default</span>></span>You're something else.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></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时,需要弹出错误信息;
为了方便,这里的登录框使用Angular
的directive
封装,提供一个叫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.notAuthenticated
和AUTH_EVENTS.sessionTimeout
,当用户 未登录/会话过期 时,将loginDialog
的visible
设为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">'<div ng-if="visible" ng-include="\'view1/view1.html\'">'</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"><<span class="hljs-name">body</span> <span class="hljs-attr">ng-controller</span>=<span class="hljs-string">'ApplicationController'</span>></span>
<span class="hljs-tag"><<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>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"menu"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">li</span>></span><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#!/view1"</span>></span>view1<span class="hljs-tag"></<span class="hljs-name">a</span>></span><span class="hljs-tag"></<span class="hljs-name">li</span>></span>
<span class="hljs-tag"><<span class="hljs-name">li</span>></span><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#!/view2"</span>></span>view2<span class="hljs-tag"></<span class="hljs-name">a</span>></span><span class="hljs-tag"></<span class="hljs-name">li</span>></span>
<span class="hljs-tag"></<span class="hljs-name">ul</span>></span>
...</span>
到这里,登录涉及的主要模块已经完成。
本文主要参考:https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec#.kr5puik92
============ 欢迎各位老板打赏~ ===========
与本文相关的文章
- · AngularJs 禁止模板缓存
- · 小程序可以绑定其它小程序吗?
- · vue3+vite+多环境发面到二级目录配置
- · 微信小程序防止事件穿透防止事件冒泡
- · 普通链接二维码跳转小程序
- · 解决flex-direction: column 之后元素宽度自动变为100%
- · vue/react/node/vite/npm/yarn build自动更新版本号
- · getVisitDistribution 访问来源定义(访问来源 key 对应关系)
- · TinyMCE工具栏配置详解
- · Ant Design Vue 1.7.8 (vu2)自定义路由菜单图标
- · vue获取节点的父节点、兄弟节点、子节点
- · vue3+vite3实现路由自动化