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');
});
Ответ 3
Мне очень нравится такой подход:
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';
Ответ 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);
}
}
}