安全假设
如您所知,javascript 是一种解释性语言,因此它的源代码是人类可读的,即使是经过混淆的。因此,可以安全地假设一个邪恶的或未注册的用户,我们称他为 Edward,可以访问应用程序的整个客户端代码。此外,根据您的模板加载机制,Edward 可以拥有对您的 html 模板的完全读取权限也是一个合理的假设。
鉴于上述假设,很明显必须保护的是填充这些模板的数据。您的 REST API 访问点必须强制执行一些身份验证机制,并在未经授权的访问时返回相关的错误消息。
有许多工具/中间件/框架可以实现良好的身份验证方案,其中大多数将使用会话对象来验证请求的状态(已授权、未授权),但这超出了本问题的范围。
处理未经授权的请求
假设您有一个适用于 REST API 的身份验证机制,以下是您如何处理未经授权的访问。
要求登录
对于所有需要登录的数据,您可以保留一个 HTTP 响应代码,并在收到该代码时将应用程序导航到登录视图。正如您可能在其他问题中看到的那样,您只需为与401 - unauthorized
http 代码对应的每个错误响应设置一个回调(假设您的骨干路由器是app.router
):
$.ajaxError(function (event, xhr) {
if (xhr.status == 401)
app.router.navigate('/login', { trigger, true });
});
现在您只需要确保您的 REST API401
在此类请求需要重定向到登录表单时返回一个。
分支案例
在更明智的情况下,您可能希望根据用户的状态将用户重定向到不同的视图。在这些情况下,对 REST API 的数据请求不应返回 a 401
,因为它会被前一个回调拦截。任何其他错误代码应该做的403 - forbidden
可能是合适的,或者400 - bad request
,这并不重要。
话虽如此,当用户从主干路由器请求特定视图时,最好在渲染之前获取正确显示视图所需的数据。当您调用此特定数据时,如果用户没有正确的凭据来获取它,您的服务器将返回带有错误代码的响应,该错误代码不会被应用程序拦截,然后您可以对其进行分支。一个例子:
// Assuming we are defining methods inside your backbone app router:
listProducts : function() {
var sensibleData = new app.SensibleData(),
products = new app.Products();
router = this;
products.fetch();
sensibleData.fetch({
success : function( model, response, options ) {
router.listProductsSignedIn( model, products );
},
error : function( model, response, options ) {
router.listProductsNotSignedIn( products );
}
},
listProductsSignedIn : function( sensibleData, products ) {
//Create and show the full products view with all data
},
listProductsNotSignedIn : function( products ) {
//Create and show the limited products view with the products data
},
//...
Et Voilà,您有一个两步路由器,其中第二步根据用户的凭据进行分支,错误是由 REST API 上的单独访问点生成的,用于未签名用户无法访问的敏感数据。由于授权过程和执行由服务器管理,因此您的应用了解用户是否具有正确访问权限的唯一方法是尝试获取数据。然后,该应用程序可以以正确的方式做出反应。
存储用户信息
拥有单页 javascript 应用程序的一大优势是您可以在应用程序的不同视图中保持状态。您可能希望在应用程序的某个状态下保持用户信息可用,以便正确路由用户并显示适当的视图。您可能有一些可供用户使用的配置选项(语言、配置文件等...)。此信息应通过 REST 调用提供,并且是成功登录的结果。用户登录后,您的应用将拥有一个已定义的用户,然后可以使用此状态来显示正确的视图。
如果您不为您的应用程序共享一个全局对象,那么让您的不同视图共享信息的一个好方法是使用事件。
// Assuming you have defined a User model that has a proper route for authentication.
var loginView = Backbone.View.extend({
events : {'submit .login-form' : 'onLogin'},
initialize : function() {
this.model = new User();
// here, we listen for the successful login event and we proxy it to the global
// Backbone object in order to communicate with views that are out of scope.
this.model.on('login:successful', function() {
Backbone.trigger('login:successful', this.model );
});
}
onLogin : function() {
var username = this.$('#username').val(),
password = this.$('#password').val();
this.model.authenticate({
username : username,
password : password
}); // this method should make a REST call and trigger an event on success
}
在您的骨干应用路由器内部:
initialize : function() {
// Here, we register to the successful login event and simply store the state of
// a user inside of the router.
this.listenTo( Backbone, 'login:successful', function( user ) {
this.user = user;
}, this );
}
您现在可以在整个会话期间使用此状态,并使用它来分支您的路线。
// Still in the router
mainPage : function() {
if ( this.user ) {
// show view for a registered user
} else {
// show a view for an unregistered user
}
},
//...