详解Angular开发中的登陆与身份验证
前言
由于 Angular 是单页应用,会在一开始,就把大部分的资源加载到浏览器中,所以就更需要注意验证的时机,并保证只有通过了验证的用户才能看到对应的界面。
本篇文章中的身份验证,指的是如何确定用户是否已经登陆,并确保在每次与服务器的通信中,都能够满足服务器的验证需求。注意,并不包括对具体是否具有某一个权限的判断。
对于登陆,主要是接受用户的用户名密码输入,提交到服务器进行验证,处理验证响应,在浏览器端构建身份验证数据。
实现身份验证的两种方式
目前,实现身份验证的方法,主要有两个大类:
Cookies
传统的浏览器网页,都是使用 Cookies 来验证身份,实际上,浏览器端的应用层里,基本不用去管身份验证的事情,Cookies 的设置,由服务器端完成,在提交请求的时候,由浏览器自动附加对应的 Cookies 信息,所以在 JavaScript 代码中,不需要为此编写专门的代码。但每次请求的时候,都会带上全部的 Cookies 数据,
随着 CDN 的应用,移动端的逐渐兴起, Cookies 越来越不能满足复杂的、多域名下的身份验证需求。
密钥
实际上基于密钥的身份验证并不是最近才兴起,它一直存在,甚至比 Cookies 历史更长。当浏览器在请求服务器的时候,将密钥以特定的方式附加在请求中,比如放在请求的头部( headers )。为此,需要编写专门的客户端代码来管理。
最近出现的基于 JSON 的 Web 密钥(JSON Web Token)
标准,便是典型的使用密钥来实现的身份验证。
在 Web 应用中,如果是构造 API ,则应优先考试使用密钥方式。
处理登陆
登陆是身份验证第一步,通过登陆,才能够组织起来对应的身份验证数据。
需要使用单独的登陆页吗?
登陆页的处理,有两种方式:
单独的登陆页,在登陆完成后,跳转到单页应用之中,这样做可以对单页应用的资源进行访问控制,防止非登陆用户访问,适合后台或者管理工具的应用场景。但实际上降低了单页应用的用户体验
在单页应用之内执行登陆,这样更符合单页应用的设计理念,比较适合大众产品的场景,因为恶意的人总是能够拿到你单页应用的前端代码
单独的登陆页
一般情况下,使用单独的登陆页的目的在于保护登陆后跳转的页面不被匿名用户访问。因此,在登陆页里,构造一个表单,直接采用传统的表彰提交方式(非 Ajax),后端验证用户名密码成功后,输出登陆后单面应用页面的 HTML 。
在这种情况下,可以直接将身份验证信息放在输出的 HTML 里,比如,可以使用 Jade 构造一个这样的页面:
1 <!-- dashboard.jade --> 2 doctype html 3 html 4 head 5 link(rel="stylesheet", href="/assets/app.e1c2c6ea9350869264da10de799dced1.css") 6 body 7 script. 8 window.token = !{JSON.stringify(token)}; 9 div.md-padding(ui-view) 10 script(src="/assets/app.84b1e53df1b4b23171da.js")
后端在用户名密码验证成功之后,可以采用如下的方式来渲染输出 HTML :
1 return res.render('dashboard', { 2 token: token 3 });
Angular 应用一启动,便可以进行需要使用身份验证的通信。而且还保证了只有登陆成功的用户才可以进入这个页面。
单页应用内登陆的组织
对于多视图的 Angular 应用,一般会采用路由,在页面之内,一般有固定的侧边栏菜单,或者顶部导航菜单,正文区域由路由模块来控制。
下面的示例代码,使用的是 Angular Material 来组织页面,路由模块使用的是 ui-router 。在应用打开的时候,有专门的加载动画,加载完成之后,显示的页面,使用 AppController
这个控制器,对于没有登陆的用户,会显示登陆表单,登陆完成之后,页面分为三大部分,一是顶部面包屑导航,二是侧边栏菜单,另外就是路由控制的正文部分。
1 <body ng-app="app" layout="row"> 2 <div id="loading"> 3 <!--页面加载的提示--> 4 </div> 5 <div flex layout="row" ng-cloak ng-controller="AppController" ng-init="load()"> 6 <div ng-if="!isUserAuth", ng-controller="LoginController"> 7 <!--登陆表单--> 8 </div> 9 <div ng-if="isUserAuth" flex layout="row"> 10 <md-sidenav flex="15" md-is-locked-open="true" class="stop-text-select bbmd-sidebar md-whiteframe-4dp"> 11 <!--侧边栏菜单--> 12 </md-sidenav> 13 <md-content flex layout="column" role="main"> 14 <md-toolbar class="stop-text-select md-whiteframe-glow-z1"> 15 <!--顶部菜单--> 16 </md-toolbar> 17 <md-content> 18 <!--路由--> 19 <div ui-view class="md-padding"></div> 20 </md-content> 21 </md-content> 22 </div> 23 </div> 24 </body>
对于 Loading 动画,是在 AppController
之外的,可以在 AppController
的代码中,对其进行隐藏。这样达到了所有 CSS / JavaScript 加载完成之后 Loading 就消失的目的。
AppController
中有一个变量 isUserAuth
,初始化的时候是 false
,当本地存储的会话信息验证有效,或者登陆完成之后,这个值便会置为 ture
,由于 ng-if
的控制,便可以实现隐藏登陆表单、显示应用内容的目的。要注意,这里只有使用 ng-if
而不是 ng-show/ng-hide
,前者才会真正的删除和增加 DOM 元素,而后者只是修改某个 DOM 元素的 CSS 属性,这点很重要,只有这样,才能够保证登陆完成之后,再加载单页应用中的内容,防止还没有登陆,当前路由中的控制器代码就直接执行了。
为什么客户端也要加密密码
一个比较理想的基于用户名和密码的登陆流程是这样的:
1.浏览器端获取用户输入的密码,使用 MD5 一类的哈希算法,生成固定长度的新密码,如 md5(username + md5(md5(password)))
,再将密码哈希值和用户名提交给后端
2.后端根据用户名获取对应的盐,使用用户名和密码哈希值,算出一个密文,根据用户名和密文去数据库查询
3.如果查询成功,则生成密钥,返回给浏览器,并执行第 4 步
4.后端生成新的盐,根据新的盐和浏览器提交的密码哈希值,生成新的密文。在数据库中更新盐和密文
可能有 80% 的人无法理解为什么要把一个登陆做得这么复杂。这可能要写一篇专门的文章才解释得清楚。
在这里先解释一下为什么浏览器端要对密码做哈希,原因如下:
1.从源头上保护用户的密码,保证只有做按键记录才可以拿到用户的原始密码
2.就算网络被窃听,又没有使用 https ,那么被偷走的密码,也只是哈希之后的,最多影响用户在这个服务器里的数据,而不影响使用相同密码的其它网站
3.就算是服务器的所有者,都无法获取用户的原始密码
这种做法,使得用户的最大风险,也只是当前这个应用中的数据被窃取。不会扩大损失范围,绝不会出现 CSDN 之流的问题。
登陆成功的通知
对于有些应用,并不是所有的页面都需要用户登陆的,可能是进行某些操作的时候,才需要登陆。在这种情况下,登陆完成之后,必须要通知整个应用。这可以使用广播这个功能。
简易代码如下:
1 angular 2 .module('app') 3 .controller('LoginController', ['$rootScope', LoginController]); 4 5 function LoginController($rootScope) { 6 // 登陆成功之后调用的函数 7 function afterLoginSuccess() { 8 $rootScope.$broadcast('user.login.success', { 9 // 需要传输的数据 10 }); 11 } 12 }
在其它的控制器中,便可以监听这个广播,并执行登陆成功之后需要进行的操作,如获取列表或者详情:
1 $scope.$on('user.login.success', function(handle, data){ 2 // 处理 3 });
身份验证信息
登陆成功之后,服务器返回了密钥,之后的 API 请求都需要带上密钥,而且请求返回的响应,还需要检查是否是关于身份信息失效的错误。这一系列的工作比较繁琐,应该是自动完成才行。
保存
密钥的保存,大概有如下几个办法:
1.Cookies:前面已经提到了,这个并不推荐使用。同时,它还有最大 4k 的限制
2.sessionStorage:tab 页内有效,一旦关闭,或者打开了新的 tab 页,sessionStorage 是不能共享的
3.localStorage:较为理想的存储方式,除非清理浏览器数据,否则 localStorage 存储的数据会一直存在
4.Angular 单例 Service:存储在应用之内得话,刷新后数据会丢失,当然也不能 tab 页之间共享
比较好的办法是,身份验证信息存储在 localStorage 里,但在应用启动时,初始化到 Angular 的单例 Service 中。
在请求中加入身份验证信息
身份验证信息的目的,是为了向服务器表明身份,获取数据。所以,在请求中需要附加身份验证信息。
一般的应用中,身份验证信息都是放在请求的 headers 头部中。如果在每次请求的时候,一一设置 headers ,那就太费时费力了。Angular 中的 $httpProvider
提供了一个拦截器 interceptors
,通过它可以实现对每一个请求和响应的统一处理。
添加拦截器的方式如下:
1 angular 2 .module('app') 3 .config(['$httpProvider', function($httpProvider){ 4 $httpProvider.interceptors.push(HttpInterceptor); 5 }]);
HttpInterceptor 的定义方式如下:
1 angular 2 .module('app') 3 .factory('HttpInterceptor', ['$q', HttpInterceptor]); 4 5 function HttpInterceptor($q) { 6 return { 7 // 请求发出之前,可以用于添加各种身份验证信息 8 request: function(config){ 9 if(localStorage.token) { 10 config.headers.token = localStorage.token; 11 } 12 return config; 13 }, 14 // 请求发出时出错 15 requestError: function(err){ 16 return $q.reject(err); 17 }, 18 // 成功返回了响应 19 response: function(res){ 20 return res; 21 }, 22 // 返回的响应出错,包括后端返回响应时,设置了非 200 的 http 状态码 23 responseError: function(err){ 24 return $q.reject(err); 25 } 26 }; 27 }
拦截器提供了对发出请求到返回响应的全生命周期处理,一般可以用来做下面几个事情:
1.统一在发出的请求中添加数据,如添加身份验证信息
2.统一处理错误,包括请求发出时出的错(如浏览器端的网络不通),还有响应时返回的错误
3.统一处理响应,比如缓存一些数据等
4.显示请求进度条
在上面的示例代码中,当 localStorage
中包括 token
这个值时,就在每一个请求的头部,添加一个 token
值。
失效及处理
一般的,后端应该在 token
验证失败时,将响应的 http 状态码设置为 401 ,这样,在拦截器的 responseError
中便可以统一处理:
1 responseError: function(err){ 2 if(-1 === err.status) { 3 // 远程服务器无响应 4 } else if(401 === err.status) { 5 // 401 错误一般是用于身份验证失败,具体要看后端对身份验证失败时抛出的错误 6 } else if(404 === err.status) { 7 // 服务器返回了 404 8 } 9 return $q.reject(err); 10 }
总结
其实,只要服务器返回的状态码不是 200 ,都会调用 responseError
,可以在这里,统一处理并显示错误。
以上内容是关于Angular开发应用中的登陆与身份验证的相关知识,希望对大家学习Angular有所帮助。
原文链接:http://chensd.com/2016-07/Authorzation-and-Login-in-Angular.html?utm_source=tuicool&utm_medium=referral
转载于:https://www.cnblogs.com/lili-lili-lili-lili/p/6206338.html
详解Angular开发中的登陆与身份验证相关推荐
- java 开发模式详解_Java开发中的23种设计模式详解4
其实每个设计模式都是很重要的一种思想,看上去很熟,其实是因为我们在学到的东西中都有涉及,尽管有时我们并不知道,其实在Java本身的设计之中处处都有体现,像AWT.JDBC.集合类.IO管道或者是Web ...
- android include 控件详解,Android开发中include控件用法分析
本文实例讲述了Android开发中include控件用法.分享给大家供大家参考,具体如下: 我们知道,基于Android系统的应用程序的开发,界面设计是非常重要的,它关系着用户体验的好坏.一个好的界面 ...
- android代码实现手机加速功能吗,详解Android开发中硬件加速支持的使用方法
Android从3.0(API Level 11)开始,在绘制View的时候支持硬件加速,充分利用GPU的特性,使得绘制更加平滑,但是会多消耗一些内存. 开启或关闭硬件加速: 由于硬件加速自身并非完美 ...
- 移动架构 (一) 详解架构设计中UML图的使用
距离上一个 "性能优化系列" 已经快一个月没有发布文章了,最近公司真的是太忙了,甚是想念掘友们啊.最近把学习架构方面的知识记录下来,供自己和掘友们一起学习. 注意: 文章中 UML ...
- java调用项目中的文件_详解eclipse项目中.classpath文件的使用
1 前言 在使用eclipse或者myeclipse进行java项目开发的时候,每个project(工程)下面都会有一个.classpath文件,那么这个文件究竟有什么作用? 2 作用 .classp ...
- python利器的使用-图文详解python开发利器之ulipad的使用实践
Ulipad是一个国人limodou编写的专业Python编辑器,它基于wxpython开发的GUI(图形化界面).下面这篇文章主要介绍了python开发利器之ulipad的使用实践,文中介绍的非常详 ...
- 详解深度学习中的Normalization,不只是BN(2)
" 深度神经网络模型训练之难众所周知,其中一个重要的现象就是 Internal Covariate Shift. Batch Normalization 大法自 2015 年由Google ...
- java 泛型详解、Java中的泛型方法、 java泛型详解
本文参考java 泛型详解.Java中的泛型方法. java泛型详解 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用. 什么是泛型?为什么要使用泛型? 泛型,即& ...
- angular字符串转成html,详解angular如何调用HTML字符串的方法
详解angular如何调用HTML字符串的方法 前面的文章我们介绍过angular6.0的数据绑定,也就是前面页面如何调用后台的数据,我们接触到了调用普通数据--如:调用产品详情{{post.cont ...
- 详解MAC硬盘中各个文件夹
详解MAC硬盘中各个文件夹 打开Macintosh HD你会发现内中有四个文件夹(一般情况下,隐藏文件夹是不可见的,而且,可能会更多,比如安装xcode后会有developer文件夹). 分别有--应 ...
最新文章
- 网络波动服务器维护中,官方回应《王者荣耀》游戏无法登录:网络波动导致,已修复...
- 为何多线程就能提高Java程序的执行效率
- FragmentTabHost切换Fragment时避免重复加载UI
- 关于数据准确性,精益求精,神策数据矢志不渝的坚持
- How to write an operating system
- 提取json对象中的数据,转化为数组
- 第二十期:核心交换机的链路聚合、冗余、堆叠、热备份
- mysql8.0.13 rpm_Centos7 安装mysql 8.0.13(rpm)的教程详解
- php如何输出复选框的值,php 怎么输出复选框呢?
- mysql中int、bigint、smallint 和 tinyint的区别与长度
- Pytorch——可视化不同的优化器效果
- java开发是什么_java开发到底是做什么的
- 铃木dl250参数_豪爵铃木DL250 ABS测评-通勤篇
- c语言实现审查元素,如何删除qq空间说说?一键自动删除QQ空间说说审查元素代码分享(超简单)...
- 华为应用市场2021年度安全隐私报告发布:护航应用安全是场“持久战”
- 【git】error: .repo/manifests/: contains uncommitted changes解决思路
- 移动机器人全覆盖路径规划及仿真(三.地图分割)
- 惠普HP Deskjet Ink Advantage 3540 打印机驱动
- 【OpenCV图像处理】十五、图像空域滤波(上)
- 《创新者的基因》读书笔记
热门文章
- 沟通与设计讨论总结:设计师不能不知道的10个沟通秘诀
- 在Axapta中实现split函数
- shell for while循环
- HCIE-Security Day30:IPSec:实验(五)配置基于路由的IPSec PN(采用预共享密钥认证)
- Windows平台CocosStudioV3.10安装配置(使用Cocos2d-xV3.17.2进行开发)
- Linux之squirrelmail小松鼠客户端搭建
- 修改 win10 的 CMD 控制台字体
- Python-字典遍历
- 读书笔记 之《Thinking in Java》(对象、集合、异常)
- Apache Spark源码走读(十)ShuffleMapTask计算结果的保存与读取 WEB UI和Metrics初始化及数据更新过程分析...