我是Backbone / Marionette的新手,我在客户端应用上实施授权时遇到了一些麻烦。基本上我有一个具有一些公共和私有路由的应用程序,当用户尝试执行某些私有操作时,我需要自动执行登录流程,例如原始私有用户流程停止,启动登录流程,然后恢复原始用户流程。非常标准的东西......
我正在尝试从我的客户端应用程序中找到一种自动化或拦截并实现此行为的方法。我没有使用任何服务器重定向,因为该策略重新加载我的应用程序并破坏我的状态,所以我试图添加一些自定义代码到Backbone.sync
来完成此任务。
本质上我正在做的是扩展Backbone.Model
以使用我的自定义sync
,自定义sync
应该返回自定义承诺(不是$.ajax
) Backbone.sync
,我总是尝试使用原始同步来解析承诺但是如果我从服务器捕获401,我将同步上下文(方法,模型,选项)以及我的自定义承诺对象添加到我的全局可访问的App对象,然后我导航我的应用程序登录,“暂停”用户流程。一旦用户提交登录信息,我检查App上是否有Deferred对象,如果是,我用原始上下文调用Backbone.sync
并用该结果解析初始自定义承诺(“恢复用户流”),然后我就完成了导航到原始片段以同步URL。
我发现这是一个简单的解决方案,但它在Backbone / Marionette上下文应用程序中是否有意义?
这是我的customSync
function (method, model, options) {
var deferred = $.Deferred();
if (options)
deferred.then(options.success, options.error);
var sync = Backbone.sync(method, model, _.omit(options, 'success', 'error'));
sync.done(deferred.resolve);
sync.fail(function() {
if (sync.status === 401) {
// Add logic to send the user to the login page,
// or general authentication page.
App.Defer = deferred;
App.Callback = Backbone.sync;
App.Arguments = [method, model, _.omit(options, 'success', 'error')];
App.Fragment = Backbone.history.fragment;
// In this example, we'll send the user to the "login" page:
App.navigate("login", { trigger: true, replace: true });
} else {
deferred.reject.apply(sync, arguments);
}
});
return deferred;
}
我的登录提交LoginView事件
loginView.on('auth:login', function (data) {
var sessionModel = new App.Models.Session();
sessionModel.login(data, function (err, user) {
if (err) return loginView.setError(err);
App.trigger('set:user', user);
var defer = App.Defer;
var callback = App.Callback;
var arguments = App.Arguments;
var fragment = App.Fragment;
delete App.Defer;
delete App.Callback;
delete App.Arguments;
delete App.Fragment;
if (defer) {
callback.apply(this, arguments).then(defer.resolve, defer.reject);
App.navigate(fragment, { trigger: false, replace: true });
} else {
App.navigate('', { trigger: true, replace: true });
}
});
});
答案 0 :(得分:0)
这是一个有趣的想法,但我不确定它在实践中对你有多好处。登录后,您将把用户重定向回401发生时他们所在的路线。该路由处理将最终重新制定服务请求(除非您做了大量工作以避免这种情况),因此解决之前被拒绝的承诺不会有任何好处。当后来解决被拒绝的承诺时,我也不会期望承诺库的行为方式符合您的要求。从编码的角度来看,如果这样做,请尝试避免全局状态 - 您可以将数据传回.reject参数并保持全局状态清洁(当您有异步调用多个服务的视图时,这将特别重要)。还有一件事......从安全角度来看,确保在401之前和之后处理相同的用户帐户至关重要。
我在使用OAuth服务时做了非常类似的事情,其中自定义同步方法查找401s然后尝试使用refresh_token获取新的access_token然后重试初始请求 - 但是在踢用户之后重试请求登录屏幕似乎走得太远了。
答案 1 :(得分:0)
Backbone.sync
函数似乎是处理导航逻辑的一个奇怪的地方。同步用于管理模型状态,而不是UI。您希望普遍重试每个失败的同步似乎也值得怀疑。对我来说似乎更有可能的是,您可能希望保留可能因重定向而丢失的数据(例如表单数据)。使用Backbone很容易,因为您可以继续传递对模型的引用。
我建议您删除Backbone.sync
覆盖中的所有全局变量和路由逻辑。相反,只需触发事件并将您可能想要的任何信息传递给单独的处理程序。像这样:
sync.fail(function() {
if (sync.status === 401) {
App.vent.trigger("sync:failed", method, model, options);
}
});
这使您可以更灵活地处理故障,保持同步功能不受导航代码的影响,并且可以更轻松地处理您将要使用全局变量遇到的令人讨厌的情况(特别是因为您可能会得到一组失败的同步调用,而不只是一个。)
现在添加一些处理程序来监听失败。
var redirectTo = "";
App.vent.on("sync:failed", function () {
// Handle routing
redirectTo = Backbone.history.fragment; // Use this url when navigating after auth
Backbone.history.navigate("login", true);
});
App.vent.on("sync:failed", function (method, model, options) {
// Handle model data you care about
// e.g. manage a queue of unsaved changes; clear changes that didn't save; preserve UI specific models, etc.
});
如果你已经决定重试你的承诺,你也应该这样做,特别是因为你只想选择性地重试(更不用说罗伯特围绕这个问题提出的问题)。
以下是我如何设置处理程序以在登录后重新提交邮件:
App.vent.on("sync:failed", function (method, model, options) {
if (model.retrySaveOnLogin) { // Property I would include on models that can be safely retried
model.listenOnce(App.vent, "login:success", function () { // Some event you trigger when login is successful
this.save({}, options);
});
}
});