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

前言

由于 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有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索angular
, 身份验证
, 登陆验证
angularjs登陆验证
angularjs身份验证、angularjs 指令详解、angularjs q详解、angularjs mvc 详解、angularjs event详解,以便于您获取更多的相关知识。

时间: 2024-09-09 15:32:29

详解Angular开发中的登陆与身份验证_AngularJS的相关文章

详解Android开发中Fragment的使用_java

前言学习Java和Android将近一年的时间了,期间的成果应该就是独立完成了一个Android客户端,并且保证了其在主线版本的稳定性.期间遇到了很多坑,也跟着师兄学到了很多Android知识.但是人总是要拥抱变化,不能让自己太安逸,虽然有不舍,但是我已经证明了自己的学习能力,下一步就是开始做Rom Porting了.这里总结一下之前项目中用到最多的Fragment. Fragment简介Fragment可以理解成Activity中用户界面的一个行为或者一部分,它必须被嵌套在Activity中.

详解Android开发中ContentObserver类的使用_Android

ContentObserver--内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于 数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它.触发器分为表触发器.行触发器, 相应地ContentObserver也分为"表"ContentObserver."行"ContentObserver,当然这是与它所监听的Uri MIME Type有关的. 熟悉Content Prov

详解WordPress开发中用于获取分类及子页面的函数用法_php技巧

get_categoryget_category 可能我们平时接触的不多,但却是很有用,网上这个函数介绍的貌似不多,所以今天只针对官方 WordPress 英文文档做一下翻译. 函数描述 获得指定分类,以数组或是对象的形式返回. 函数使用 get_category( $cat, $out, $filter ) 参数描述 $cat:分类ID,或 $out返回值类型[OBJECT, ARRAY_A, or ARRAY_N] $filter 函数返回值 这里主要讲一下对象类型的返回值, 都有注释,请自

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

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

详解WordPress开发中的get_post与get_posts函数使用_php技巧

get_post() 在一般主题制作时,get_post()函数我们一般很少会用到,但因为后面会讲到get_posts(),所以我们不得不先讲一下这个单数形式.这个函数的主要作用是,将一片指定的文章以一个对象或是数组的形式返回,以便我们后期利用.下面让我们简单的了解一下他的使用方法. get_post()函数说明 WordPress 的函数名总是那么浅显易懂,get_post()函数正如其表,即获得一篇文章,将一篇指定的文章以一个对象或是数组的形式返回,以便我们后期利用. 函数使用 <?php

详解Android开发中Activity的四种launchMode_Android

Activity栈主要用于管理Activity的切换.当使用Intent跳转至某个目标Activity,需要根据目标Activity的加载模式来加载. Activity一共有以下四种launchMode: 1.standard:默认,每次使用Intent跳转到目标Activity时都创建一个新的实例.坏处是每次进入都要创建新的实例,执行OnCreate方法. 2.singleTop:如果要跳转的目标Activity正好在task的顶部(说明当前肯定不在目标task里,例如我在微信首页,然后想使用

详解WordPress开发中get_current_screen()函数的使用_javascript技巧

get_current_screen() 函数是一个我们很少用到,但却超级实用的一个函数,如果你正着手于制作一个主题,却不知道文档应该放在哪里的话,那你应该看一下这个从 WordPress 3.0 才开始有的函数,该函数允许我们获得一个 WP_Screen 对象,并使用该对象的成员方法在后台里面加挂我们自定义的一个帮助菜单(该功能在,3.3版后得到完善). 如果你不喜欢将 WordPress 研究的太透彻的话,那你现在就可以拿着酱油瓶,向前打酱油去了. 引言首先,get_current_scre

详解iOS开发中的转场动画和组动画以及UIView封装动画_IOS

一.转场动画 CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果.iOS比Mac OS X的转场动画效果少一点 UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果 属性解析: type:动画过渡类型 subtype:动画过渡方向 startProgress:动画起点(在整体动画的百分比) endProgress:动画终点(在整体动画的百分比) 转场动画代码示例 1.界面搭建 2.实现代码 复制代码

详解iOS开发中UItableview控件的数据刷新功能的实现_IOS

实现UItableview控件数据刷新一.项目文件结构和plist文件 二.实现效果 1.说明:这是一个英雄展示界面,点击选中行,可以修改改行英雄的名称(完成数据刷新的操作). 运行界面: 点击选中行: 修改数据后自动刷新: 三.代码示例 数据模型部分: YYheros.h文件 复制代码 代码如下: // //  YYheros.h //  10-英雄展示(数据刷新) // //  Created by apple on 14-5-29. //  Copyright (c) 2014年 itca