日期:2014-05-19 浏览次数:20776 次
虽然许多文章曾经讨论过J2EE最佳实践那么,为什么我还要再写一篇文章呢?本文究竟与以前的文章有何不同或者说比其他文章好在哪呢?
首先,本文的目标读者是正在从事技术工作的架构师为了避免浪费大家的才智,我会避免讲述一些陈腐的最佳实践,例如日常构建(build daily)测试一切(test everything)和经常集成( integrate often) 任何具有称职架构师的项目都有分工明确的定义良好的团队结构他们还为进行编码检查构建代码(每日或在需要时)进行测试(单元集成和系统的)部署和配置/释放管理而具备已记录的过程
其次,我将跳过通常吹捧的最佳实践,例如基于接口的设计使用著名的设计模型以及使用面向服务的架构等相反,我将集中讲述我曾学过并且使用了若干年的6(不是很多)个方面的in-the-trench课程最后,本文的目的是让您思考一下自己的架构,提供工作代码示例或者解决方案超出了本文的范围下面就让我介绍一下这6课:
第1课:切勿绕过服务器端验证
作为一位软件顾问,我曾有机会不但设计并实现了Web应用程序,而且还评估/审核了许多Web应用程序在复杂的并且用JavaScript客户端封装的应用程序内,我经常遇到对用户输入信息执行大量检查的Web页面即使HTML元素具有数据有效性的属性也如此,例如MAXLENGTH只有在成功验证所有输入信息后,才能提交HTML表单结果,一旦服务器端收到通知表单(请求),便恰当地执行业务逻辑
在此,您发现问题了么?开发人员已经做了许多重要的假设例如,他们假设所有的Web应用程序用户都同样诚实开发人员还假设所有用户将总是使用他们测试过的浏览器访问Web应用程序还有很多其他的假设这些开发人员忘记了利用可以免费得到的工具,通过命令行很容易地模拟类似浏览器的行为事实上,通过在浏览器窗口中键入适当的URL,您可以发送任何posted表单,尽管如此,通过禁用这些页面的GET请求,您很容易地阻止这样的表单发送但是,您不能阻止人们模拟甚至创建他们自己的浏览器来入侵您的系统
根本的问题在于开发人员不能确定客户端验证与服务器端验证的主要差别两者的主要差别不在于验证究竟发生在哪里,例如在客户端或者在服务器端主要的差别在于验证背后的目的不同
客户端验证仅仅是方便执行它可为用户提供快速反馈??使应用程序似乎做出响应,给人一种运行桌面应用程序的错觉
另一方面,服务器端验证是构建安全Web应用程序必需的不管在客户端一侧输入的是什么,它可以确保客户端送往服务器的所有数据都是有效的
因而,只有服务器端验证才可以提供真正应用程序级的安全许多开发人员陷入了错误感觉的圈套:只有在客户端进行所有数据的验证才能确保安全下面是说明此观点的一个常见的示例:
一个典型的登录页面拥有一个用来输入用户名的文本框和一个输入密码的文本框在服务器端,某人在接收servlet中可能遇到一些代码,这些代码构成了下面形式的SQL查询:
"SELECT * FROM SecurityTable WHERE username = '" + form.getParameter("username") + "' AND password = '" + form.getParameter("password") + "';",并执行这些代码如果查询在结果集的某一行返回,则用户登录成功,否则用户登录失败
第一个问题是构造SQL的方式,但现在让我们暂时忽略它如果用户在用户名中输入Alice'--会怎样呢?假设名为Alice的用户已经在SecurityTable中,这时此用户(更恰当的说法是黑客)成功地登录我将把找出为什么会出现这种情况的原因做为留给您的一道习题
许多创造性的客户端验证可以阻止一般的用户从浏览器中这样登录但对于已经禁用了JavaScript的客户端,或者那些能够使用其他类似浏览器程序直接发送命令(HTTP POST和GET命令)的高级用户(或者说黑客)来说,我们又有什么办法呢?服务器端验证是防止这种漏洞类型所必须的这时,SSL防火墙等都派不上用场了
第2课:安全并非是附加物
如第1课所述,我曾有幸研究过许多Web应用程序我发现所有的JavaServer Page(JSP)都有一个共同的主题,那就是具有类似下面伪代码的布局:
<%
User user =
session.getAttribute("User");
if(user == null)
{
// redirect to
// the logon page
}
if(!user.role.equals("manager"))
{
// redirect to the
// "unauthorized" page
}
%>
<!-
HTML, JavaScript, and JSP
code to display data and
allow user interaction -->
如果项目使用诸如Struts这样的MVC框架,所有的Action Bean都会具有类似的代码尽管最后这些代码可能运行得很好,但如果您发现一个bug,或者您必须添加一个新的角色(例如,guest或者admin),这就会代表一场维护恶梦
此外,所有的开发人员,不管您多年轻,都需要熟悉这种编码模式当然,您可以用一些JSP标签来整理JSP代码,可以创建一个清除派生Action Bean的基本Action Bean尽管如此,由于与安全相关的代码会分布到多个地方,所以维护时的恶梦仍旧存在由于Web应用程序的安全是强迫建立在应用程序代码的级别上(由多个开发人员),而不是建立在架构级别上,所以Web应用程序还是很可能存在弱点
很可能,根本的问题是在项目接近完成时才处理安全性问题最近作为一名架构师,我曾在一年多的时间里亲历了某一要实现项目的6个版本,而直到第四版时我们才提到了安全性??即使该项目会将高度敏感的个人数据暴露于Web上,我们也没有注意到安全性为了更改发布计划,我们卷入了与项目资助人及其管理人员的争斗中,以便在第一版中包含所有与安全相关的功能,并将一些业务功能放在后续的版本中最终,我们赢得了胜利而且由于应用程序的安全性相当高,能够保护客户的私有数据,这一点我们引以为荣,我们的客户也非常高兴
遗憾的是,在大多数应用程序中,安全性看起来并未增加任何实际的商业价值,所以直到最后才解决发生这种情况时,人们才匆忙开发与安全相关的代码,而丝毫没有考虑解决方案的长期可维护性或者健壮性忽视该安全性的另一个征兆是缺乏全面的服务器端验证,如我在第1课中所述,这一点是安全Web应用程序的一个重要组成部分
记住:J2EE Web应用程序的安全性并非仅仅是在Web.xml 和ejb-jar.xml文件中使用合适的声明,也不是使用J2EE技术,如Java 认证和授权服务(Java Authentication and Authorization Service,JAAS)而是经过深思熟虑后的设计,且实现一个支持它的架构
第3课:国际化(I18N)不再是纸上谈兵
当今世界的事实是许多英语非母语的人们将访问您的公共Web应用程序随着电子政务的实行,由于它允许人们(某个国家的居民)在线与政府机构交互,所以这一点特别真实这样的例子包括换发驾照或者车辆登记证许多第一语言不是英语的人们很可能将访问这样的应用程序国际化(即:i18n,因为在internationalization这个单词中,字母i和字母n之间一共有18个字母)使得您的应用程序能够支持多种语言
显然,如果您的JSP 页面中有硬编码的文本,或者您的Java代码返回硬编码的错误消息,那么您要花费很多时间开发此Web应用程序的西班牙语版本然而,在Web应用程序中,为了支持多种语言,文本不是惟一必须具体化的部分因为许多图像中嵌有文字,所以图形和图像也应该是可配置的在极端的情况下,图像(或者颜色)在不同的文化背景中可能有完全不同的意思类似地,任何格式化数字和日期的Java代码也必须本地化但问题是:您的页面布局可能也需要更改
例如,如果您使用HTML表格来格式化和显示菜单选项应用程序题头或注脚,则您可能必须为每一种支持的语言更改每一栏的最小宽度和表格其他可能的方面为了适应不同的字体和颜色,您可能必须为每一种语言使用单独的样式表
显然,现在创建一个国际化的Web应用程序面临的是架构挑战而不是应用程序方面的挑战一个架构良好的Web应用程序意味着您的JSP页面和所有与业务相关的(应用程序特有的)Java代码都不知不觉地选择了本地化要记住的教训是:不要因为JavaJ2EE支持国际化而不考虑国际化您必须从第一天起就记住设计具有国际化的解决方案
第4课:在MVC表示中避免共同的错误
J2EE开发已经足够成熟,在表示层,大多数项目使用MVC架构的某些形式,例如Struts在这样的项目中,我常见到的现象是对MVC模式的误用下面是几个示例
常见的误用是在模型层(例如,在Struts的Action Bean中)实现了所有的业