Angular.JS 与node.JS架构中基于token身份验证实现。

14-01-08 banq
                   

前面帖子身份验证中Cookies与 Tokens比较已经说明令牌比cookie好,下面是AngularJS中的实现。

服务器后端

首先安装express-jwt:

$ npm install express-jwt

配置express保护/api的每个调用:

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

// We are going to protect /api routes with JWT

app.use('/api', expressJwt({secret: secret}));

app.use(express.json());

app.use(express.urlencoded());

Angular应用使用用户证书通过AJAX提交POST:

app.post('/authenticate', function (req, res) {
  //TODO validate req.body.username and req.body.password
  //if is invalid, return 401
  if (!(req.body.username === 'john.doe' && req.body.password === 'foo[author]bar[/author]')) {
    res.send(401, 'Wrong user or password');
    return;
  }

  var profile = {
    first_name: 'John',
    last_name: 'Doe',
    email: 'john@doe.com',
    id: 123
  };

  // We are sending the profile inside the token
  var token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 });

  res.json({ token: token });
});
<p>

下面是调用 /api/restricted获得一个资源,其证书检查由expressJwt中间件执行。

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

前端Angular.js

客户端首先用AngularJS 获得JWT的令牌,为了这样做我们还需要用户证书,我们将将创建一个表单让用户输入他们的用户名和密码。

<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>
</div>
<p>

一个控制器处理提交的action:

myApp.controller('UserCtrl', function ($scope, $http, $window) {
  $scope.user = {username: 'john.doe', password: 'foo[author]bar[/author]'};
  $scope.message = '';
  $scope.submit = function () {
    $http
      .post('/authenticate', $scope.user)
      .success(function (data, status, headers, config) {
        $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;

        // Handle login errors here
        $scope.message = 'Error: Invalid user or password';
      });
  };
});
<p>

现在我们已经有JWT存储在sessionStorage中,如果这个令牌被设置,我们可以为每个发出请求设置Authorization 头部,只要用$http方式即可,头部值部分使用Bearer<token>

myApp.factory('authInterceptor', function ($rootScope, $q, $window) {
  return {
    request: function (config) {
      config.headers = config.headers || {};
      if ($window.sessionStorage.token) {
        config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
      }
      return config;
    },
    response: function (response) {
      if (response.status === 401) {
        // handle the case where the user is not authenticated
      }
      return response || $q.when(response);
    }
  };
});

myApp.config(function ($httpProvider) {
  $httpProvider.interceptors.push('authInterceptor');
});
<p>

尽管sessionStorage并不是所有浏览器支持,你可以使用 polyfill替代cookie保存在localStorage,这个数据能够持久到浏览器标签页关闭。

最后,我们能够向一个授权资源发出请求:

$http({url: '/api/restricted', method: 'GET'})
.success(function (data, status, headers, config) {
  console.log(data.name); // Should log 'foo'
});
<p>

服务器后端控制台会显示:

user foo@bar.com is calling /api/restricted

本案例源码下载:github

[该贴被banq于2014-01-08 13:35修改过]

                   

4