TokenBased的Angular认证模块设计

8/20/2015

在开发web应用时,有时候需要加入用户管理的功能,或者页面的授权查看,这些功能都依赖于web的认证模块实现。本节内容主要是针对认证系统的设计进行一些简单分析,并对于如何利用JWT的token方式来进行安全认证做了代码实现。

####安全认证模块

认证模块的设计一般来讲主要包含两种类型:

  1. 基于Cookie的认证方式: 用户的认证信息存储在Cookie中保存,默认情况,只要设置一次,浏览器就会在之后每次请求中都带属于该域名的所有Cookie信息发送到服务器端。

  2. 基于Token的认证方式: 用户认证结束后,将用户信息保存在一串Token(签名)中,发送给客户端,客户端获得之后将其保存在sessionStorage或者localStorage(关闭页面后消失),或者cookie(低版本浏览器中使用)中。在此后的每次请求中客户端将该信息提取后加入header中以特定格式保存后发送给服务器端。

基于Cookie的方式设计简单,但是却有很多的安全隐患,比如Cookie的跨域请求限制,以及针对Cookie的CSRF(跨站点脚本攻击) 。下图是一张对比图 比较了两者的主要区别。

image

###什么是JWT?

JWT是JSON web token的缩写,他使用自己的一套标准来完成整个的基于Token的认证方式,比如规定默认的http请求header中使用字段的名称为:Authorization, 默认以Bearer开头的token字段等。所以我们在接下来的编码中也要尽量按照标准去编写实现代码。 下图是加入JWT支持的http请求,其中包含了Auth字段内容

image

###Token-Based认证实现

####环境准备

搭建运行环境:首先我们使用express-generator生成标准的Express框架:

D:\GitHub> mkdir token-base-example
D:\GitHub> cd token-base-example

如果没有生成器的话使用如下命令安装后再执行,运行之后程序将包含有Express的框架,启动后监听在本地的3000端口

npm install -g express-generator
express -e --git
npm install
npm start

这样我们就有了一个完整的web app,不过还需要添加angular前端库:

bower init
vim .bowerrc 

输入,将安装的前端库放入public的vendor中:

{
  "directory": "public/vendor/"
  //将bower install的内容加入到指定的位置
}

运行以下命令即可安装好angular代码到指定的vendor目录下

bower install angular --save

####客户端程序实现

修改view中的index.ejs代码这里我们直接全部替换为下面的代码即可,加入了angular的前端库,并设置app名称为MyApp,所有的angular程序在/public/javascripts文件夹下的app.js(手工创建)文件内。

<!DOCTYPE html>
<html ng-app="MyApp">
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <script type="text/javascript" src="/vendor/angular/angular.min.js">
    </script>
    <script type="text/javascript" src="/javascripts/app.js">
    </script>
  </head>
  <body>
      <div ng-controller="UserCtrl">
        <span></span>
        <form ng-submit="submit()">
          <input ng-model="user.username" type="text" name="user" placeholder="Username" />
          <input ng-model="user.password" type="password" name="pass" placeholder="Password" />
          <input type="submit" value="Login" />
        </form>
        <span ng-bind="message"></span>
        <button ng-click="apiCall()">CallRestricApi</button>
      </div>
  </body>
</html>

编辑 public\javascripts\app.js文件:

//创建主模块
var app=angular.module('MyApp',[]);

//创建认证拦截器:拦截器将拦截所有的http请求并加入自定义的。
app.factory('authInterceptor',function ($rootScope,$q,$window) {
  return{
//对于所有的http请求配置其头信息字段Authorization,
    request:function (config) {
      config.headers=config.headers||{};
      if(window.sessionStorage.token){
        config.headers.Authorization='Bearer '+$window.sessionStorage.token;
      }
      return config;
    },

  //对于未授权的请求可以将用户转到login页面
    response:function (response) {
      if(response.status===401){
        console.log("UnAuth");
      }
      return response||$q.when(response);
    }
  }
});
//将拦截器配置到httpProvide中
app.config(function ($httpProvider) {
  $httpProvider.interceptors.push('authInterceptor');
});



//定义自己的Controller处理用户登录请求
app.controller('UserCtrl',function ($scope,$window,$http) {
  'use strict';
 //设置初始的值
  $scope.user = {username: 'mike', password: 'secret'};
  //设置提示信息
  $scope.message = '';
 //提交信息 
  $scope.submit = function () {
    $http
      .post('/authenticate', $scope.user)
      .success(function (data, status, headers, config) {
     //发送认证之后等待响应,一旦成功的获得响应则存储token
        $window.sessionStorage.token = data.token;
        $scope.message = 'Welcome';
      })
      .error(function (data, status, headers, config) {
        // Erase the token if the user fails to log in
        delete $window.sessionStorage.token;
        $scope.message = 'Error: Invalid user or password';
      });
  };

  $scope.apiCall=function () {
    $http({url: '/api/restricted', method: 'GET'})
      .success(function (data, status, headers, config) {
       //这里不再需要额外的设置直接发送的请求中已经包含了http的Authentication头信息
        console.log(data.name);
      });
  }
});

####服务器端程序实现

服务器端需要安装express-jwt库来完成认证之后的数据签名,将签名信息发送到客户端

npm install express-jwt --save 
npm install jsonwebtoken --save 

服务器端 app.js中

var expressJwt=require('express-jwt');
var jsonwebtoken = require('jsonwebtoken');

...


app.use('/api',expressJwt({secret:"mike-secret"}));
//设置所有包含有api的路径,获取头信息并提取Authentication字段后解码

//用户认证处理单元
app.post('/authenticate', function (req, res) {

  if (!(req.body.username === 'mike' && req.body.password === 'secret')) {
    res.send(401, 'Wrong user or password');
    return;
  }
 //成功的用户将用户的相关信息保存在profile中 使用jwt签名发送给客户端
  var profile = {
    name: 'mike',
    email: 'mike@gmail.com',
    id: 1
  };

  // We are sending the profile inside the token
  var token = jsonwebtoken.sign(profile, "mike-secret", { expiresInMinutes: 60*5 });
  console.log(token);
  res.json({ token: token });
});


//编写任何访问受限的代码 都不在需要加入认证模块

app.get('/api/restricted', function (req, res) {
  console.log('user ' + req.user.email + ' is calling /api/restricted');
  res.json({
    name: 'mike '
  });
});

源码Github位置:Github


前端技术 Angular 页面已被访问2387次

发表评论