TL;DR 要求用户登录才能查看 Durandal 单页应用程序 (SPA) 中的某些页面的良好模式是什么?
我需要一个系统,如果用户尝试导航到需要他们登录的"页面",他们会被重定向到登录页面。在此登录页面上成功进行身份验证后,我希望应用程序在重定向到登录页面之前将他们重定向到他们之前尝试访问的页面。
我能想到的一种可以实现的用于将用户重定向到登录页面和从登录页面重定向的方法是将 URL 存储在浏览器历史记录中需要身份验证的页面,并在成功进行身份验证后,从登录页面导航回此历史记录项。
实现
我试图实现上述模式,但遇到了一些问题。在main.js
(或调用router.activate()
之前的任何地方)我保护需要身份验证的路由:
router.guardRoute = function (instance, instruction) {
if (user.isAuthenticated()) {
return true;
} else {
if (instance && typeof (instance.allowAnonymous) === "boolean") {
if (!instance.allowAnonymous) {
/* Use one of the following two methods to store an item in the
browser history. */
/* Add guarded URL to the history and redirect to login page.
* We will navigate back to this URL after a successful login.
*/
if (history.pushState) {
history.pushState(null, null, '#' + instruction.fragment);
}
else {
location.hash = '#' + instruction.fragment;
}
*/
/* This will not work - history is not updated if the fragment
* matches the current fragment.*/
////router.navigate(instruction.fragment, false);
/* The following solution puts in a fragment to the history
* that we do not want the user to see. Is this an
* acceptable solution? */
router.navigate(instruction.fragment + 'LoginRedirect', false);
return router.convertRouteToHash("login");
}
}
return true;
}
};
shell.js
我向登录页面添加一条路由:
return {
router: router,
activate: function () {
router.map([
...
{ route: 'login', title: '', moduleId: 'viewmodels/login', nav: true },
...
]).buildNavigationModel();
return router.activate();
},
...
}
在viewmodels/login.js
中,我有代码片段:
if (account.isAuthenticated()) {
router.navigateBack();
}
局限性
此方法的一个限制是,它允许用户在进行身份验证并被重定向离开登录页面后向前导航到登录页面。
此外,使用
router.navigate(instruction.fragment + 'LoginRedirect', false);
我偶尔会在URL中看到LoginRedirect
闪现。
被带到上一个(不受保护的)页面(而是会被带到受保护的页面,这将重定向到登录页面)。
似乎还有一个错误,guardRoute
允许页面刷新(见这里。这仍然是一个问题吗?
它们是否包含上述行为并且没有与上面列出的类似的限制的标准杜兰达尔模式?
查询字符串模式
尝试实现我的问题中描述的"导航回"方法,由于我上面描述的限制,我决定不使用此方法。
我在实践中发现,一种效果更好的方法是将需要身份验证的页面的 URL 作为查询字符串传递给登录页面,然后登录页面可以使用它在用户通过身份验证后向前导航到此 URL。
以下是我采用的此方法的实现的概述。但是,我仍然渴望了解人们在 Durandal 应用程序中采用的任何其他登录模式。
实现
在main.js
(或调用router.activate()
之前的任何地方),我仍然保护需要身份验证的路由:
router.guardRoute = function (instance, instruction) {
if (user.isAuthenticated()) {
return true;
} else {
if (instance && typeof (instance.preventAnonymous) === "boolean") {
if (instance.preventAnonymous) {
return 'login/' + instruction.fragment;
}
}
return true;
}
};
在shell.js
:
return {
router: router,
activate: function () {
router.map([
...
{ route: 'login', title: '', moduleId: 'viewmodels/login', nav: true },
{ route: 'login/:redirect', title: '', moduleId: 'viewmodels/login', nav: true },
...
]).buildNavigationModel();
return router.activate();
},
...
}
在viewmodels/login.js
:
viewmodel = {
...,
canActivate: function() {
if (!user.isAuthenticated()) {
return true;
}
return false;
},
activate: function(redirect) {
viewmodel.redirect = redirect || "";
},
loginUser() {
...
if (user.isAuthenticated()) {
router.navigate(viewmodel.redirect);
}
...
},
...
}
限度
此方法的一个小缺点是,在应用程序 URL 查询字符串中成功登录时,要重定向到的页面片段的先见之明。如果您不喜欢此页面片段在 URL 查询字符串中的优先顺序,则可以轻松地使用本地存储来存储此值。router.guardRoute
可以简单地更换生产线
return 'login/' + instruction.fragment;
跟
/* Check for local storage, cf. http://diveintohtml5.info/storage.html */
if ('localStorage' in window && window['localStorage'] !== null){
localStorage.setItem("redirect", instruction.fragment);
return 'login/';
} else {
return 'login/' + instruction.fragment;
}
我们的activate
方法可能如下所示:
activate: function(redirect) {
if ('localStorage' in window && window['localStorage'] !== null){
viewmodel.redirect = localStorage.getItem("redirect");
} else {
viewmodel.redirect = redirect || "";
}
},
我的方法:
我开发了一个视图模型基函数,它是我应用程序的所有视图模型的基础。在 canActivate
方法(在视图模型库中)中,如果当前用户无权查看登录页面,我将返回false
并导航到登录页面。我还对客户端数据库执行查询(以确定用户是否有权访问页面)。
好消息是杜兰达尔支持canActivate
的延迟执行。在登录页面中,成功登录后,我将使用 navigator.goBack();
返回上一页。
我希望这对你有所帮助。