AngularJS:如何使用$ resource请求发送身份验证令牌?

时间:2012-06-24 09:26:43

标签: javascript authentication angularjs

我想在从API请求资源时发送身份验证令牌。

我确实使用$ resource实现了一项服务:

factory('Todo', ['$resource', function($resource) {
 return $resource('http://localhost:port/todos.json', {port:":3001"} , {
   query: {method: 'GET', isArray: true}
 });
}])

我有一个存储身份验证令牌的服务:

factory('TokenHandler', function() {
  var tokenHandler = {};
  var token = "none";

  tokenHandler.set = function( newToken ) {
    token = newToken;
  };
  tokenHandler.get = function() {
    return token;
  };

  return tokenHandler;
});

我希望通过tokenHandler.get服务发送每个请求,从Todo发送令牌。我能够通过将其置于特定动作的调用中来发送它。例如,这有效:

Todo.query( {access_token : tokenHandler.get()} );

但我更愿意将access_token定义为Todo服务中的参数,因为它必须在每次调用时发送。并改善干旱。 但是工厂中的所有东西都只执行一次,因此在定义工厂之前必须提供access_token,之后不能更改。

有没有办法在服务中添加动态更新的请求参数?

8 个答案:

答案 0 :(得分:60)

感谢Andy Joslin。我选择了包装资源操作的想法。资源的服务现在看起来像这样:

.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
  var resource = $resource('http://localhost:port/todos/:id', {
    port:":3001",
    id:'@id'
    }, {
      update: {method: 'PUT'}
    });

  resource = tokenHandler.wrapActions( resource, ["query", "update"] );

  return resource;
}])

正如您所看到的那样,资源首先是通常的定义方式。在我的示例中,这包括一个名为update的自定义操作。然后,返回tokenHandler.wrapAction()方法覆盖资源,该方法将资源和一系列操作作为参数。

正如您所期望的那样,后一种方法实际上将操作包装在每个请求中包含auth令牌并返回修改后的资源。那么让我们来看看代码:

.factory('TokenHandler', function() {
  var tokenHandler = {};
  var token = "none";

  tokenHandler.set = function( newToken ) {
    token = newToken;
  };

  tokenHandler.get = function() {
    return token;
  };

  // wrap given actions of a resource to send auth token with every
  // request
  tokenHandler.wrapActions = function( resource, actions ) {
    // copy original resource
    var wrappedResource = resource;
    for (var i=0; i < actions.length; i++) {
      tokenWrapper( wrappedResource, actions[i] );
    };
    // return modified copy of resource
    return wrappedResource;
  };

  // wraps resource action to send request with auth token
  var tokenWrapper = function( resource, action ) {
    // copy original action
    resource['_' + action]  = resource[action];
    // create new action wrapping the original and sending token
    resource[action] = function( data, success, error){
      return resource['_' + action](
        angular.extend({}, data || {}, {access_token: tokenHandler.get()}),
        success,
        error
      );
    };
  };

  return tokenHandler;
});

正如您所看到的,wrapActions()方法从其参数创建资源的副本,并通过actions数组循环,以便为每个操作调用另一个函数tokenWrapper()。最后,它返回资源的修改副本。

tokenWrapper方法首先创建预先存在的资源操作的副本。此副本具有尾随下划线。因此query()变为_query()。然后,新方法将覆盖原始query()方法。正如安迪·乔斯林所建议的那样,这个新方法包装_query(),为通过该操作发送的每个请求提供身份验证令牌。

这种方法的好处是,我们仍然可以使用每个angularjs资源(获取,查询,保存等)附带的预定义操作,而无需重新定义它们。在其余代码中(例如在控制器内),我们可以使用默认操作名称。

答案 1 :(得分:34)

另一种方法是使用HTTP拦截器,用当前的OAuth令牌替换“魔术”授权头。下面的代码是特定于OAuth的,但对于读者来说,这是一个简单的练习。

// Injects an HTTP interceptor that replaces a "Bearer" authorization header
// with the current Bearer token.
module.factory('oauthHttpInterceptor', function (OAuth) {
  return {
    request: function (config) {
      // This is just example logic, you could check the URL (for example)
      if (config.headers.Authorization === 'Bearer') {
        config.headers.Authorization = 'Bearer ' + btoa(OAuth.accessToken);
      }
      return config;
    }
  };
});

module.config(function ($httpProvider) {
  $httpProvider.interceptors.push('oauthHttpInterceptor');
});

答案 2 :(得分:21)

我真的很喜欢这种方法:

http://blog.brunoscopelliti.com/authentication-to-a-restful-web-service-in-an-angularjs-web-app

其中令牌始终在请求标头内自动发送,而不需要包装器。

// Define a new http header
$http.defaults.headers.common['auth-token'] = 'C3PO R2D2';

答案 3 :(得分:9)

你可以为它创建一个包装函数。

app.factory('Todo', function($resource, TokenHandler) {
    var res= $resource('http://localhost:port/todos.json', {
        port: ':3001',
    }, {
        _query: {method: 'GET', isArray: true}
    });

    res.query = function(data, success, error) {
        //We put a {} on the first parameter of extend so it won't edit data
        return res._query(
            angular.extend({}, data || {}, {access_token: TokenHandler.get()}),
            success,
            error
        );
    };

    return res;
})

答案 4 :(得分:5)

我也必须处理这个问题。我不认为它是否是一个优雅的解决方案,但它的工作原理有两行代码:

我想您在SessionService中进行身份验证后会从服务器获取令牌。然后,调用这种方法:

   angular.module('xxx.sessionService', ['ngResource']).
    factory('SessionService', function( $http,  $rootScope) {

         //...
       function setHttpProviderCommonHeaderToken(token){
          $http.defaults.headers.common['X-AUTH-TOKEN'] = token;
       }  
   });

之后,来自$ resource和$ http的所有请求都会在其标题中包含令牌。

答案 5 :(得分:3)

另一个解决方案是使用resource.bind(additionalParamDefaults),它返回绑定了附加参数的资源的新实例

var myResource = $resource(url, {id: '@_id'});
var myResourceProtectedByToken = myResource.bind({ access_token : function(){
        return tokenHandler.get();
}});
return myResourceProtectedByToken;

每次调用资源上的任何操作时,都会调用access_token函数。

答案 6 :(得分:1)

我可能误解了你的所有问题(随意纠正我:))但是为了专门解决为每个请求添加access_token的问题,您是否尝试将TokenHandler模块注入Todo 1}}模块?

// app
var app = angular.module('app', ['ngResource']);

// token handler
app.factory('TokenHandler', function() { /* ... */ });

// inject the TokenHandler
app.factory('Todo', function($resource, TokenHandler) {
    // get the token
    var token = TokenHandler.get();
    // and add it as a default param
    return $resource('http://localhost:port/todos.json', {
        port: ':3001',
        access_token : token
    });
})

您可以致电Todo.query(),它会将?token=none附加到您的网址。或者,如果您希望添加令牌占位符,您当然也可以这样做:

http://localhost:port/todos.json/:token

希望这会有所帮助:)

答案 7 :(得分:1)

根据您接受的答案,我建议扩展资源,以便使用Todo对象设置令牌:

.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
  var resource = $resource('http://localhost:port/todos/:id', {
    port:":3001",
    id:'@id'
    }, {
      update: {method: 'PUT'}
    });

  resource = tokenHandler.wrapActions( resource, ["query", "update"] );
  resource.prototype.setToken = function setTodoToken(newToken) {
    tokenHandler.set(newToken);
  };
  return resource;
}]);

这样,每次要使用Todo对象时都无需导入TokenHandler,您可以使用:

todo.setToken(theNewToken);

我要做的另一项更改是允许默认操作,如果它们在wrapActions中为空:

if (!actions || actions.length === 0) {
  actions = [];
  for (i in resource) {
    if (i !== 'bind') {
      actions.push(i);
    }
  }
}