AngularJS: Как отправить токен аутентификации с запросами $resource?

Я хочу отправить токен аутентификации при запросе ресурса из моего API.

Я реализовал сервис, используя $resource:

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

И у меня есть служба, которая хранит токен auth:

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, так как он должен быть отправлен с каждым вызовом. И улучшить СУХОЙ. Но все в factory выполняется только один раз, поэтому access_token должен быть доступен до определения factory, и он не может изменить его впоследствии.

Есть ли способ добавить динамически обновляемый параметр запроса в службу?

Ответ 1

Спасибо Энди Джослину. Я выбрал его идею обернуть действия ресурсов. Теперь сервис для ресурса выглядит следующим образом:

.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(). Этот новый метод обертывает _query(), как предложил Энди Хослин, для предоставления токена аутентификации с каждым запросом, отправляемым через это действие.

Хорошо, что при таком подходе мы все еще можем использовать предопределенные действия, которые приходят с каждым ресурсом angularjs (get, query, save и т.д.), без необходимости переопределять их. А в остальной части кода (например, в контроллерах) мы можем использовать имя действия по умолчанию.

Ответ 2

Другой способ - использовать 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');
});

Ответ 4

Вы можете создать для него функцию-оболочку.

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;
})

Ответ 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 будут иметь токен в заголовке.

Ответ 6

Другим решением было бы использовать resource.bind(дополнительныеParamDefaults), которые возвращают новый экземпляр ресурса, связанный с дополнительными параметрами

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

Функция access_token будет вызываться каждый раз, когда вызывается любое действие на ресурсе.

Ответ 7

Я мог бы не понимать весь ваш вопрос (не стесняйтесь меня корректировать:)), но специально для добавления добавления 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 к вашему URL-адресу. Или, если вы предпочитаете добавлять токен-заполнитель, вы тоже можете это сделать:

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

Надеюсь, что это поможет:)

Ответ 8

Следуя принятому ответу, я бы предложил расширить ресурс, чтобы установить токен с объектом 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;
}]);

Таким образом, нет необходимости импортировать TokenHandler каждый раз, когда вы хотите использовать объект Todo, и вы можете использовать:

todo.setToken(theNewToken);

Еще одно изменение, которое я хотел бы сделать, - разрешить действия по умолчанию, если они пусты в wrapActions:

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