62

从我的 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服务中的参数,因为它必须随每次调用一起发送。并改善 DRY。但是工厂中的所有内容都只执行一次,因此 access_token 在定义工厂之前必须可用,并且之后不能更改。

有没有办法在服务中放置一个动态更新的请求参数?

4

8 回答 8

60

感谢安迪乔斯林。我选择了他包装资源操作的想法。资源的服务现在看起来像这样:

.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()方法将资源和一组动作作为参数。

正如您所期望的那样,后一种方法实际上包装了操作以在每个请求中包含身份验证令牌并返回修改后的资源。因此,让我们看一下代码:

.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()方法。正如 Andy Joslin 所建议的,这种新方法包装_query()了 auth 令牌,以便为通过该操作发送的每个请求提供身份验证令牌。

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

于 2012-06-24T21:20:18.517 回答
35

另一种方法是使用 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');
});
于 2013-07-17T08:56:20.570 回答
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';
于 2013-11-07T14:16:29.883 回答
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;
})
于 2012-06-24T18:35:59.020 回答
5

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

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

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

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

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

于 2013-05-23T13:38:10.087 回答
3

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

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

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

于 2014-03-26T21:11:07.400 回答
1

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

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

希望这可以帮助 :)

于 2012-06-24T14:09:31.787 回答
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);
    }
  }
}
于 2014-10-06T12:52:19.300 回答