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


Я хочу отправить маркер аутентификации при запросе ресурса из моего 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()} );

но я бы предпочел определить маркер доступа в качестве параметра Todo сервис, так как он должен быть отправлен с каждым вызовом. И улучшить сухой. Но все на фабрике выполняется только один раз, поэтому access_token должен быть доступен до определения фабрики, и он не может измениться впоследствии.

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

8 62

8 ответов:

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

.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() для каждого действия. В конце концов он возвращает измененную копию ресурса.

The tokenWrapperметод прежде всего создает копию уже существующего ресурса действие. Эта копия имеет конечное подчеркивание. Так что query()становится _query(). После этого новый метод перезаписывает исходный query() метод. Этот новый метод обертывания _query(), как предложил Энди Джослин, чтобы предоставить маркер аутентификации с каждым запросом отправить через это действие.

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

другой способ-использовать перехватчик HTTP, который заменяет заголовок авторизации "magic" текущим маркером 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');
});

Мне очень нравится такой подход:

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';

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

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

мне приходилось иметь дело с этой проблемой. Я не думаю, что это элегантное решение, но оно работает, и есть 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 будут иметь токен в своем заголовке.

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

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

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

я мог бы неправильно понять весь ваш вопрос (не стесняйтесь поправлять меня :)) но специально обратиться к добавлению 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

надеюсь, что это помогает :)

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