我想在从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,之后不能更改。
有没有办法在服务中添加动态更新的请求参数?
答案 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);
}
}
}