在AngularJS应用中实现认证授权

在AngularJS应用中实现认证授权

在每一个严肃的应用中,认证和授权都是非常重要的一个部分。单页应用也不例外。应用并不会将所有的数据和功能都 暴露给所有的用户。用户需要通过认证和授权来查看应用的某个特定部分,或者在应用中进行特定的行为。为了在应用中对用户进行识别,我们需要让用户进行登录。

在用户管理方面,传统的服务器端应用和单页应用的实现方式有所不同,单页应用能够和服务器通信的方式只有AJAX。对于登录和退出来说也是如此。

负责识别用户的服务器端需要暴露出一个认证断电。单页应用将会把用户输入的信息发送到这个节点进行认证。在一个基于认证系统的典型token中,这 项服务用于在认证完毕之后获取一个token或者一个包含已登录用户的名字和角色信息的对象。客户端则需要在所有的安全API中获取这个token。

由于获取toekn的行为将会多次发生,我们最好将这个token存在客户端。在Angular中,我们可以将这个值存在一个服务中,因为服务在客 户端中是一个单体。但是,如果用户刷新了页面,服务中的值将会丢失。在这种情况下,最好将值存放在一个有浏览器提供的安全存储中,在这里我们要是用的是 sessionStorage,因为它在浏览器关闭时会自动被清空。

实现登录

我们现在来看一些代码。假设我们已经实现了所有的服务器端的逻辑,并且有一个叫做api/login的REST接口进行登录认证,它将返回一个token。我们来写一个简单的服务用于用户登录。在后面我们会为这个服务逐渐添加功能:

app.factory("authenticationSvc", function($http, $q, $window) {
  var userInfo;

  function login(userName, password) {
    var deferred = $q.defer();

    $http.post("/api/login", {
      userName: userName,
      password: password
    }).then(function(result) {
      userInfo = {
        accessToken: result.data.access_token,
        userName: result.data.userName
      };
      $window.sessionStorage["userInfo"] = JSON.stringify(userInfo);
      deferred.resolve(userInfo);
    }, function(error) {
      deferred.reject(error);
    });

    return deferred.promise;
  }

  return {
    login: login
  };
});

在实际的代码中,你可能会想要将存储的代码重构为一个单独的服务。在这里为了简单起见,我们只是将它放在他用一个服务中。这个服务可以被一个用于处理登录功能的控制器所用。

安全路由

我们需要在应用中设置一些安全路由。如果一个用户没有登录同时想要进入到某一个安全路由中,他应该被重定向到登录页。我们可以使用路由选项中的resolve来实现这个功能。下面的代码片段展示了其中一种实现思路:

$routeProvider.when("/", {
  templateUrl: "templates/home.html",
  controller: "HomeController",
  resolve: {
    auth: ["$q", "authenticationSvc", function($q, authenticationSvc) {
      var userInfo = authenticationSvc.getUserInfo();

      if (userInfo) {
        return $q.when(userInfo);
      } else {
        return $q.reject({ authenticated: false });
      }
    }]
  }
});

resolve块可以包含多个代码块,这些代码块将会在完成时返回promise对象。为了说明,上面代码中的auth并不在框架中,而是我们自己定义的。你可以根据你的需求来进行修改。

通过或者拒绝路由的原因有很多种。在这里的情形中,你可以在解析/拒绝一个promise的时候传递一个对象。我们在服务中还没有实现getLoggedInUser()方法。它是一个很简单的方法,能够从服务中返回loggedInUser对象。

app.factory("authenticationSvc", function() {
  var userInfo;

  function getUserInfo() {
    return userInfo;
  }
});

 通过上面的代码中的promise发送的想将会通过$rootScope进行广播。如果路由被解析,那么$routeChangeSuccess事件将会 被广播。然而,如果路由失败,那么事件$touteChangeError将会被广播。我们将监听$routeChangeError事件并将用户重定向 到登录页上。由于事件是在$rootScope层级上,最好在run函数中绑定事件处理器。

app.run(["$rootScope", "$location", function($rootScope, $location) {
  $rootScope.$on("$routeChangeSuccess", function(userInfo) {
    console.log(userInfo);
  });

  $rootScope.$on("$routeChangeError", function(event, current, previous, eventObj) {
    if (eventObj.authenticated === false) {
      $location.path("/login");
    }
  });
}]);

处理页面刷新

当用户刷新页面时,服务将会失去现有状态。我们需要从浏览器的session storage中获取数据并将这些值赋值给loggerInUser变量。由于一个factory只会被调用一次,我们需要在一个初始化函数中设置这个变量,代码如下所示:

    function init() {
      if ($window.sessionStorage["userInfo"]) {
        userInfo = JSON.parse($window.sessionStorage["userInfo"]);
      }
    }


init();

退出

当用户想要从应用中退出时,相应的API必须连同包含在请求头部中的token一起被调用。一旦用户退出,我们还需要清空sessionstorage中的数据。下面例子包含了一个退出函数,这个函数需要被添加到认证服务中。

function logout() {
  var deferred = $q.defer();

  $http({
    method: "POST",
    url: logoutUrl,
    headers: {
      "access_token": userInfo.accessToken
    }
  }).then(function(result) {
    $window.sessionStorage["userInfo"] = null;
    userInfo = null;
    deferred.resolve(result);
  }, function(error) {
    deferred.reject(error);
  });

  return deferred.promise;
}

总结

单页应用的认证方式和传统web应用的认证方式非常不同。由于主要的工作都搬到了浏览器端,用户的状态也需要存储在客户端。重要的一点是要记住用户的状态也需要的服务器端保存和进行验证,因为骇客很可能慧聪客户端窃取用户的数据。


本文译自Implementing Authentication in Angular Applications,原文地址http://www.sitepoint.com/implementing-authentication-angular-applications/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏GopherCoder

Django:web框架的学习(2)

1174
来自专栏Python

web框架

http协议 HTTP简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World ...

3046
来自专栏卡少编程之旅

React项目的服务端渲染改造(koa2+webpack3.11)

3527
来自专栏程序员的碎碎念

ThinkPHP基础知识(二)

今天闲来无事,继续来看我们的tp下一个教程(勉强叫做这个吧)。看前面的博客文章我们知道: ? 那么,我们怎么创建控制器和方法呢? 一、创建控制器和方法 创建控制...

3496
来自专栏曾大稳的博客

ffmpeg 封装格式转换 MP4转AVI

格式转换直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。

1163
来自专栏安富莱嵌入式技术分享

【RL-TCPnet网络教程】第37章 RL-TCPnet之FTP客户端

本章节为大家讲解RL-TCPnet的FTP客户端应用,学习本章节前,务必要优先学习第35章的FTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。

532
来自专栏程序员互动联盟

【编程基础】linux常用命令介绍(一)

系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecod...

37611
来自专栏Grace development

XDEBUG 从入门到精通

Xdebug是PHP的扩展,用于协助调试和开发。 – 它包含一个用于IDE的调试器 – 它升级了PHP的var_dump()函数 – 它为通知,警告,错误...

1301
来自专栏happyJared

Linux私房菜:打包、压缩和备份

目前常见的Linux压缩命令包括有gzip、bzip2和最新的xz,下表是一些常见的压缩文件扩展名及对应指令关系:

421
来自专栏极客猴

详解 python3 urllib

本文是爬虫系列文章的第一篇,主要讲解 Python 3 中的 urllib 库的用法。urllib 是 Python 标准库中用于网络请求的库。该库有四个模块,...

501

扫码关注云+社区