PureJS (6.4):利用 proxy 对象实现权限控制和数据校验
利用上一篇文章提到的 proxy 对象,我们还可以实现更多实用的功能。比如本文将探讨的权限控制和数据校验。
权限控制的思路是截获对 page.* 和 api.* 的调用,并利用 session 中记录的用户角色信息进行权限检查;
数据校验还将用到之前的客户端与服务器端共用代码的功能,对数据进行双重检查,以防恶意攻击。
接下来就让我们看看具体的实现吧。
权限控制
这里以对 page.* 的调用为例。基本思路是:
1. 通过正则表达式 /^page./ 和 /^api./ 匹配需要拦截的方法调用
2. 获取参数中的 req (ServletHttpRequest)
3. 获取 session 中的用户角色
4. 如果用户的角色是 admin ,则显示相应页面;否则显示登陆页面
(function() { var log = pure.log("proxy.security"); proxy.security = { priority: 80 }; // 对 page.* 的调用进行权限控制 proxy.security.page = { priority: 100, expr: /^page./, func: function(name, method, args) { // 获取方法的第二个参数,即 req var req = args[1]; // 读取 session 中的role。返回值是 java.lang.String // 加上空字符串转为 JavaScript 中的 String var role = req.session.getAttribute("user.role") + ""; // 如果角色是 "admin",则显示相应页面 // 否则,显示登录页面 if (role === "admin") { return this[method].apply(this, args); } else { log.info("Redirect to login page."); return pure.render("login"); } } } // 利用类似的方法对 api.* 的调用进行权限控制,略 proxy.security.api = { ... } }());
简单起见,这里仅包含了 admin 一种角色。
启动 mongod 和 PureJS 工程(见附件),输入http://localhost:8080/,将显示登录页面,在控制台输出(或日志)中也可以看到“Redirect to login page.”的提示。
输入用户名和密码并点击 Sign in 之后,将显示用户列表。
数据校验
在介绍服务器端利用 JQuery 进行页面渲染时,我们提到了服务器端与客户端共用代码的实现(http://xxing22657-yahoo-com-cn.iteye.com/blog/1113665)。
现在在数据校验功能的实现中我们将再次利用这个功能。
首先编写在服务器端和浏览器中共用的 validator 对象:
webapp/js/both/validator.js
validator = {}; // 校验异常信息 validator.USER_INVALID = "Invalid user data."; validator.USER_NAME_EMPTY = "Name cannot be empty."; validator.USER_NAME_TOO_LONG = "Name cannot be longer than 50."; validator.USER_DESC_EMPTY = "Description cannot be empty."; validator.USER_DESC_TOO_LONG = "Description cannot be longer than 50."; // 检查 user 对象的方法 validator.validateUser = function(user) { // 参数类型错误,可能是恶意攻击 if (typeof user.name !== "string" || typeof user.desc !== "string") { return { success: false, error: validator.USER_INVALID }; } // name 为空 if (!user.name) { return { success: false, error: validator.USER_NAME_EMPTY }; } // name 过长 if (user.name.length > 50) { return { success: false, error: validator.USER_NAME_TOO_LONG }; } // desc 为空 if (!user.desc) { return { success: false, error: validator.USER_DESC_EMPTY }; } // desc 过长 if (user.desc.length > 50) { return { success: false, error: validator.USER_DESC_TOO_LONG }; } // 提取 name 和 desc;因为对象中可能还有其他不需要的属性 var data = { name: user.name, desc: user.desc } return { success: true, data: data }; }
这段代码在服务器端的 proxy.validation.saveUser 和 浏览器端的 save(...) 中被用到。
客户端的校验是为了给用户更快的反馈,服务器端的校验是为了避免恶意攻击。
代码实现如下:
scripts/app/proxy/validation.js
(function() { var log = pure.log("proxy.validation"); proxy.validation = { priority: 60 }; proxy.validation.saveUser = { priority: 100, expr: /^dbo.users.save$/, func: function(name, method, args) { // 获取验证结果 var validated = validator.validateUser(args[0]); // 验证失败,抛出异常 if (!validated.success) { log.info(validated.error); throw validated.error; } var data = validated.data; // 检查用户是否已经存在 // 注意,这里的 this 表示 dbo 对象 if (this.exists(data.name)) { var msg = "Save User Faild: User already exists."; log.info(msg); throw msg; } // 通过验证,返回所需的结果 args[0] = data; return this[method].apply(this, args); } } }());
webapp/js/index.js
$(function(){ // 其他代码,略 function save(user, callback) { // 获取验证结果 var validated = validator.validateUser(user); // 验证失败,显示异常 if (!validated.success) { $("#error").html(validated.error); return; } var data = validated.data; // 检查用户是否已经存在 for (var i = 0, l = users.length; i < l; ++i) { if (users[i].name === data.name) { $("#error").html("User already exists."); return; } } callback(data); show(); pure.post({ action: "users.save", params: data }); } });
可以看到,检查输入是否为空和检查输入参数长度的部分是共用的,但检查用户是否存在的逻辑在客户端和服务器端略有不同。
另外,proxy 对象的 func 中 的 this 表示的是被截获的对象,因此我们可以在 proxy.validation.saveUser 中调用 dbo.exists(name) 来检查用户是否已经存在。
小结
1. 我们可以利用 proxy 对象拦截方法的调用,进行权限和数据的检查
2. 我们可以将服务器和浏览器端共用的代码放在 both 目录下
3. proxy 对象的 func 中 的 this 表示的是被截获的对象