详解Angular开发中的登陆与身份验证

前言

由于 Angular 是单页应用,会在一开始,就把大部分的资源加载到浏览器中,所以就更需要注意验证的时机,并保证只有通过了验证的用户才能看到对应的界面。

本篇文章中的身份验证,指的是如何确定用户是否已经登陆,并确保在每次与服务器的通信中,都能够满足服务器的验证需求。注意,并不包括对具体是否具有某一个权限的判断。

对于登陆,主要是接受用户的用户名密码输入提交到服务器进行验证处理验证响应在浏览器端构建身份验证数据

实现身份验证的两种方式

目前,实现身份验证的方法,主要有两个大类:

Cookies

传统的浏览器网页,都是使用 Cookies 来验证身份,实际上,浏览器端的应用层里,基本不用去管身份验证的事情,Cookies 的设置,由服务器端完成,在提交请求的时候,由浏览器自动附加对应的 Cookies 信息,所以在 JavaScript 代码中,不需要为此编写专门的代码。但每次请求的时候,都会带上全部的 Cookies 数据,

随着 CDN 的应用,移动端的逐渐兴起, Cookies 越来越不能满足复杂的、多域名下的身份验证需求。

密钥

实际上基于密钥的身份验证并不是最近才兴起,它一直存在,甚至比 Cookies 历史更长。当浏览器在请求服务器的时候,将密钥以特定的方式附加在请求中,比如放在请求的头部( headers )。为此,需要编写专门的客户端代码来管理。

最近出现的基于 JSON 的 Web 密钥(JSON Web Token)标准,便是典型的使用密钥来实现的身份验证。

在 Web 应用中,如果是构造 API ,则应优先考试使用密钥方式。

处理登陆

登陆是身份验证第一步,通过登陆,才能够组织起来对应的身份验证数据。

需要使用单独的登陆页吗?

登陆页的处理,有两种方式:

单独的登陆页,在登陆完成后,跳转到单页应用之中,这样做可以对单页应用的资源进行访问控制,防止非登陆用户访问,适合后台或者管理工具的应用场景。但实际上降低了单页应用的用户体验
在单页应用之内执行登陆,这样更符合单页应用的设计理念,比较适合大众产品的场景,因为恶意的人总是能够拿到你单页应用的前端代码

单独的登陆页

一般情况下,使用单独的登陆页的目的在于保护登陆后跳转的页面不被匿名用户访问。因此,在登陆页里,构造一个表单,直接采用传统的表彰提交方式(非 Ajax),后端验证用户名密码成功后,输出登陆后单面应用页面的 HTML 。

在这种情况下,可以直接将身份验证信息放在输出的 HTML 里,比如,可以使用 Jade 构造一个这样的页面:

<!-- dashboard.jade -->
doctype html
html
 head
  link(rel="stylesheet", href="/assets/app.e1c2c6ea9350869264da10de799dced1.css")
 body
  script.
   window.token = !{JSON.stringify(token)};
  div.md-padding(ui-view)
  script(src="/assets/app.84b1e53df1b4b23171da.js")

后端在用户名密码验证成功之后,可以采用如下的方式来渲染输出 HTML :

return res.render('dashboard', {
 token: token
});

Angular 应用一启动,便可以进行需要使用身份验证的通信。而且还保证了只有登陆成功的用户才可以进入这个页面。

单页应用内登陆的组织

对于多视图的 Angular 应用,一般会采用路由,在页面之内,一般有固定的侧边栏菜单,或者顶部导航菜单,正文区域由路由模块来控制。

下面的示例代码,使用的是 Angular Material 来组织页面,路由模块使用的是 ui-router 。在应用打开的时候,有专门的加载动画,加载完成之后,显示的页面,使用 AppController 这个控制器,对于没有登陆的用户,会显示登陆表单,登陆完成之后,页面分为三大部分,一是顶部面包屑导航二是侧边栏菜单,另外就是路由控制的正文部分

代码如下:

<body ng-app="app" layout="row">
 <div id="loading">
  <!--页面加载的提示-->
 </div>
 <div flex layout="row" ng-cloak ng-controller="AppController" ng-init="load()">
  <div ng-if="!isUserAuth", ng-controller="LoginController">
   <!--登陆表单-->
  </div>
  <div ng-if="isUserAuth" flex layout="row">
   <md-sidenav flex="15" md-is-locked-open="true" class="stop-text-select bbmd-sidebar md-whiteframe-4dp">
    <!--侧边栏菜单-->
   </md-sidenav>
   <md-content flex layout="column" role="main">
    <md-toolbar class="stop-text-select md-whiteframe-glow-z1">
     <!--顶部菜单-->
    </md-toolbar>
    <md-content>
     <!--路由-->
     <div ui-view class="md-padding"></div>
    </md-content>
   </md-content>
  </div>
 </div>
</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 之流的问题。

登陆成功的通知

对于有些应用,并不是所有的页面都需要用户登陆的,可能是进行某些操作的时候,才需要登陆。在这种情况下,登陆完成之后,必须要通知整个应用。这可以使用广播这个功能。

简易代码如下:

angular
 .module('app')
 .controller('LoginController', ['$rootScope', LoginController]);

function LoginController($rootScope) {
 // 登陆成功之后调用的函数
 function afterLoginSuccess() {
  $rootScope.$broadcast('user.login.success', {
   // 需要传输的数据
  });
 }
}

在其它的控制器中,便可以监听这个广播,并执行登陆成功之后需要进行的操作,如获取列表或者详情:

$scope.$on('user.login.success', function(handle, data){
 // 处理
});

身份验证信息

登陆成功之后,服务器返回了密钥,之后的 API 请求都需要带上密钥,而且请求返回的响应,还需要检查是否是关于身份信息失效的错误。这一系列的工作比较繁琐,应该是自动完成才行。

保存

密钥的保存,大概有如下几个办法:

1.Cookies:前面已经提到了,这个并不推荐使用。同时,它还有最大 4k 的限制

2.sessionStorage:tab 页内有效,一旦关闭,或者打开了新的 tab 页,sessionStorage 是不能共享的

3.localStorage:较为理想的存储方式,除非清理浏览器数据,否则 localStorage 存储的数据会一直存在

4.Angular 单例 Service:存储在应用之内得话,刷新后数据会丢失,当然也不能 tab 页之间共享
比较好的办法是,身份验证信息存储在 localStorage 里,但在应用启动时,初始化到 Angular 的单例 Service 中。

在请求中加入身份验证信息

身份验证信息的目的,是为了向服务器表明身份,获取数据。所以,在请求中需要附加身份验证信息。

一般的应用中,身份验证信息都是放在请求的 headers 头部中。如果在每次请求的时候,一一设置 headers ,那就太费时费力了。Angular 中的 $httpProvider 提供了一个拦截器 interceptors ,通过它可以实现对每一个请求和响应的统一处理。

添加拦截器的方式如下:

angular
 .module('app')
 .config(['$httpProvider', function($httpProvider){
  $httpProvider.interceptors.push(HttpInterceptor);
 }]);

HttpInterceptor 的定义方式如下:

angular
 .module('app')
 .factory('HttpInterceptor', ['$q', HttpInterceptor]);

function HttpInterceptor($q) {
 return {
  // 请求发出之前,可以用于添加各种身份验证信息
  request: function(config){
   if(localStorage.token) {
    config.headers.token = localStorage.token;
   }
   return config;
  },
  // 请求发出时出错
  requestError: function(err){
   return $q.reject(err);
  },
  // 成功返回了响应
  response: function(res){
   return res;
  },
  // 返回的响应出错,包括后端返回响应时,设置了非 200 的 http 状态码
  responseError: function(err){
   return $q.reject(err);
  }
 };
}

拦截器提供了对发出请求到返回响应的全生命周期处理,一般可以用来做下面几个事情:

1.统一在发出的请求中添加数据,如添加身份验证信息

2.统一处理错误,包括请求发出时出的错(如浏览器端的网络不通),还有响应时返回的错误

3.统一处理响应,比如缓存一些数据等

4.显示请求进度条

在上面的示例代码中,当 localStorage 中包括 token 这个值时,就在每一个请求的头部,添加一个 token 值。

失效及处理

一般的,后端应该在 token 验证失败时,将响应的 http 状态码设置为 401 ,这样,在拦截器的 responseError 中便可以统一处理:

responseError: function(err){
 if(-1 === err.status) {
  // 远程服务器无响应
 } else if(401 === err.status) {
  // 401 错误一般是用于身份验证失败,具体要看后端对身份验证失败时抛出的错误
 } else if(404 === err.status) {
  // 服务器返回了 404
 }
 return $q.reject(err);
}

总结

其实,只要服务器返回的状态码不是 200 ,都会调用 responseError ,可以在这里,统一处理并显示错误。

以上内容是关于Angular开发应用中的登陆与身份验证的相关知识,希望对大家学习Angular有所帮助。

(0)

相关推荐

  • AngularJS使用ngMessages进行表单验证

    AngularJS 诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀的前端JS框架,已经被用于Google的多款产品当中.AngularJS有着诸多特性,最为核心的是:MVVM.模块化.自动化双向数据绑定.语义化标签.依赖注入等等. 名称为"ngMessages"的module,通过npm install angular-messages进行安装.在没有使用ngMessages之前,我们可能这样写验证: <form name="

  • AngularJS实现表单手动验证和表单自动验证

    AngularJS的表单验证大致有两种,一种是手动验证,一种是自动验证. 一.手动验证 所谓手动验证是通过AngularJS表单的属性来验证.而成为AngularJS表单必须满足两个条件: 1.给form元素加上novalidate="novalidate": 2.给form元素加上name="theForm",如下: <!DOCTYPE html> <html lang="en" ng-app="myApp1&quo

  • AngularJS身份验证的方法

    权限的设计中比较常见的就是RBAC基于角色的访问控制,基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合.每一种角色对应一组相应的权限. 一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限.这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销. 在Angular构建的单页面应用中,要实现这样的架构我们需要额外多做一些事

  • 详解AngularJS实现表单验证

    开始学习AngularJS表单验证: 常用的表单验证指令 1. 必填项验证 某个表单输入是否已填写,只要在输入字段元素上添加HTML5标记required即可: 复制代码 代码如下: <<input type="text" required /> 2. 最小长度 验证表单输入的文本长度是否大于某个最小值,在输入字段上使用指令ng-minleng= "{number}": 复制代码 代码如下: <<input type="tex

  • AngularJs表单验证实例详解

    常用的表单验证指令 1. 必填项验证 某个表单输入是否已填写,只要在输入字段元素上添加HTML5标记required即可: 复制代码 代码如下: <input type="text" required /> 2. 最小长度 验证表单输入的文本长度是否大于某个最小值,在输入字段上使用指令ng-minleng= "{number}": 复制代码 代码如下: <input type="text" ng-minlength="

  • 由浅入深剖析Angular表单验证

    最近手上维护的组件剩下的BUG都是表单验证,而且公司的表单验证那块代码经历的几代人,里面的逻辑开始变得不清晰,而且代码结构不是很angular. 是很有必要深入了解表单验证. <body ng-controller="MainController"> <form name="form" novalidate="novalidate"> <input name="text" type="e

  • AngularJS实现表单验证

    虽然我不是前端程序员,但明白前端做好验证是多么重要. 因为这样后端就可以多喘口气了,而且相比后端什么的果然还是前端可以提高用户的幸福感. AngularJS提供了很方便的表单验证功能,在此记录一番. 首先从下面这段代码开始 复制代码 代码如下: <form ng-app="myApp" ng-controller="validationController" name="mainForm" novalidate>     <p&

  • AngularJS使用angular-formly进行表单验证

    当验证表单中有很多字段时,这时候可能希望把html的生成以及验证逻辑放到controller中,在页面,也许是这样的: <some-form fiedls="vm.someFields" ...></some-form> 然后,在controller中定义各个字段以及验证.angular-formly就是为这个需求而存在. 在controller中,把各个字段定义在数组中: vm.rentalFields = [ { key:'first_name', type

  • AngularJS自动表单验证

    AngularJS的另外一种表单验证方式是自动验证,即通过directive来实现,除了AngularJS自带的directive,还需要用到angular-auto-validate这个第三方module. 有关angular-auto-validate: 安装:npm i angular-auto-validate 引用:<script src="../node_modules/angular-auto-validate/dist/jcs-auto-validate.min.js&qu

  • 详解Angular开发中的登陆与身份验证

    前言 由于 Angular 是单页应用,会在一开始,就把大部分的资源加载到浏览器中,所以就更需要注意验证的时机,并保证只有通过了验证的用户才能看到对应的界面. 本篇文章中的身份验证,指的是如何确定用户是否已经登陆,并确保在每次与服务器的通信中,都能够满足服务器的验证需求.注意,并不包括对具体是否具有某一个权限的判断. 对于登陆,主要是接受用户的用户名密码输入,提交到服务器进行验证,处理验证响应,在浏览器端构建身份验证数据. 实现身份验证的两种方式 目前,实现身份验证的方法,主要有两个大类: Co

  • 详解vue项目中使用token的身份验证的简单实践

    工作原理 前端页面进行登录操作, 将用户名与密码发给服务器; 服务器进行效验, 通过后生成token, 包含信息有密钥, uid, 过期时间, 一些随机算法等 ,然后返回给前端 前端将token保存在本地中, 建议使用localstorage进行保存.  下次对服务器发送请求时, 带上本地存储的token 服务器端,进行对token的验证, 通过的话, 进行相应的增删改查操作, 并将数据返回给前端 为通过则返回错误码, 提示保错信息, 然后跳转到登录页. 具体步骤 所用技术: vuex + ax

  • 详解IOS开发中生成推送的pem文件

    详解IOS开发中生成推送的pem文件 具体步骤如下: 首先,需要一个pem的证书,该证书需要与开发时签名用的一致. 具体生成pem证书方法如下: 1. 登录到 iPhone Developer Connection Portal(http://developer.apple.com/iphone/manage/overview/index.action )并点击 App IDs 2. 创建一个不使用通配符的 App ID .通配符 ID 不能用于推送通知服务.例如,  com.itotem.ip

  • 详解微信开发中snsapi_base和snsapi_userinfo及静默授权的实现

    详解微信开发中snsapi_base和snsapi_userinfo及静默授权的实现 snsapi_base与snsapi_userinfo属于微信网页授权获取用户信息的两种作用域 snsapi_base只能获取access_token和openID snsapi_userinfo可以获取更详细的用户资料,比如头像.昵称.性别等 首先,这里的access_token与基础access_token(比如自定义菜单用到的)是不一样的.两者区别如下: 网页授权的access_token在每次获取ope

  • 详解golang开发中http请求redirect的问题

    这两天在开发项目的时候遇到了一个问题,请求了一个URL,它会302到另一个地址,本意上只是想检查这个URL是否会做3XX的redirect跳转,结果每次reqeust都会返回最后一跳的结果.后来就看了下源码,了解下请求跳转的机制 实现代码 看下实现的简单代码 func main() { client := &http.Client{} url := "http://www.qq.com" reqest, err := http.NewRequest("GET"

  • 详解Angular项目中共享模块的实现

    目录 一.共享CommonModule 二.共享MaterialModule 三.共享ConfirmDialog 一.共享CommonModule 创建share Modele:ng g m share import进来所有需要共享的模块都export出去, 暂时只有CommonModule,以后会有一些需要共享的组件. import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'

  • 详解vue开发中调用微信jssdk的问题

    起因 微信分享网址时无法分享图片,这个问题需要用jssdk去解决.其实开始的时候时可以看到图片的,但后来微信禁止了.所以只能使用jssdk去解决. 普通网页开发很简单,但是使用vue或其他前端框架开发spa单页面webapp的时候就会有问题了.只要url发生变化就会报签名错误.其实微信官方上已经写了说明. 所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支

  • 详解Angular.js中$http拦截器的介绍及使用

    前言 $http service在Angular中用于简化与后台的交互过程,其本质上使用XMLHttpRequest或JSONP进行与后台的数据交互.在与后台的交互过程中,可能会对每条请求发送到Server之前进行预处理(如加入token),或者是在Server返回数据到达客户端还未被处理之前进行预处理(如将非JSON格式数据进行转换):当然还有可能对在请求和响应过程过发生的问题进行捕获处理.所有这些需求在开发中都非常常见,所以Angular为我们提供了$http拦截器,用来实现上述需求. 什么

  • 详解WordPress开发中wp_title()函数的用法

    wp_title 函数在 WordPress 中是用来显示文章.页面.分类等等等等标题的一个函数,但在首页索引,该函数将不显示任何的东西.该函数在 WordPress 官方主题中一直被使用,但目前很多定制的主题中这个函数总是为忽视. 函数意义详解 wp_title 函数用来显示页面的标题,如在文章页面,则显示文章标题:在分类页面,则显示分类名称,但在首页索引,该函数将不显示任何的东西. 有点像 WordPress 中的 get_the_title 和 single_cat_title()这两个函

  • 详解WordPress开发中get_header()获取头部函数的用法

    函数意义详解 从当前主题调用header.php文件.是不是很简单?好吧,如果你是新手的话这里要提醒一下,这里的get和get_children().get_category中的get略有不同之处. get_header函数声明(定义) 之前写文章很少会写到函数定义的代码,后来自己翻看的时候发现这个习惯不太好,所以决定,只要篇幅允许,就会把函数主题贴出来,方便自己翻看. get_header 函数,声明(定义)的位置,是在 wp=include/general-template.php 文件的第

随机推荐