日期:2014-05-16  浏览次数:20386 次

PureJS (6.4):利用 proxy 对象实现权限控制和数据校验
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, a