首先,如果这是一个重复的问题,我道歉。我看过几个相似的问题,但它们没有提供我能理解的答案。
我有一个Backbone.js应用程序,有很多视图/模板。有些视图只有在用户登录时才能访问,有些则不是。我理解如何保护底层REST调用,但我仍然不明白在特定视图中检查实际访问控制级别的正确架构/设计是什么。此外,首次登录后存储和验证用户的正确方法是什么。 例如:
我见过几种解决方案,但看起来都像黑客。
这样做的正确,精心设计的方法是什么?
谢谢, ELAD。
答案 0 :(得分:14)
如您所知,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 API返回401
。
在更明智的情况下,您可能希望根据用户的状态将用户重定向到其他视图。在这些情况下,对REST API的数据请求不应返回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
},
//...
EtVoilà,您正在使用两步路由器,其中第二步根据用户的凭据进行分支,错误由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
}
},
//...