跨站点脚本编制 |
修订建议 |
若干问题的补救方法在于对用户输入进行清理。 通过验证用户输入未包含危险字符,便可能防止恶意的用户导致应用程序执行计划外的任务,例如:启动任意 SQL 查询、嵌入将在客户端执行的 Javascript 代码、运行各种操作系统命令,等等。 建议过滤出所有以下字符:
[1] |(竖线符号)
[2] & (& 符号)
[3];(分号)
[4] $(美元符号)
[5] %(百分比符号)
[6] @(at 符号)
[7] '(单引号)
[8] "(引号)
[9] \'(反斜杠转义单引号)
[10] \"(反斜杠转义引号)
[11] <>(尖括号)
[12] ()(括号)
[13] +(加号)
[14] CR(回车符,ASCII 0x0d)
[15] LF(换行,ASCII 0x0a)
[16] ,(逗号)
[17] \(反斜杠)
以下部分描述各种问题、问题的修订建议以及可能触发这些问题的危险字符: SQL 注入和 SQL 盲注: A. 确保用户输入的值和类型(如 Integer、Date 等)有效,且符合应用程序预期。 B. 利用存储过程,将数据访问抽象化,让用户不直接访问表或视图。当使用存储过程时,请利用 ADO 命令对象来实施它们,以强化变量类型。 C. 清理输入以排除上下文更改符号,例如:
[1] '(单引号)
[2] "(引号)
[3] \'(反斜线转义单引号)
[4] \"(反斜杠转义引号)
[5] )(结束括号)
[6] ;(分号)
跨站点脚本编制: A. 清理用户输入,并过滤出 JavaScript 代码。我们建议您过滤下列字符:
[1] <>(尖括号)
[2] "(引号)
[3] '(单引号)
[4] %(百分比符号)
[5] ;(分号)
[6] ()(括号)
[7] &(& 符号)
[8] +(加号)
B. 如果要修订 <%00script> 变体,请参阅 MS 文章 821349 C. 对于 UTF-7 攻击: [-] 可能的话,建议您施行特定字符集编码(使用 'Content-Type' 头或 <meta> 标记)。 HTTP 响应分割:清理用户输入(至少是稍后嵌入在 HTTP 响应中的输入)。 请确保输入未包含恶意的字符,例如:
[1] CR(回车符,ASCII 0x0d)
[2] LF(换行,ASCII 0x0a)远程命令执行:清理输入以排除对执行操作系统命令有意义的符号,例如:
[1] |(竖线符号)
[2] & (& 符号)
[3];(分号)
执行 shell 命令: A. 绝不将未检查的用户输入传递给 eval()、open()、sysopen()、system() 之类的 Perl 命令。 B. 确保输入未包含恶意的字符,例如:
[1] $(美元符号)
[2] %(百分比符号)
[3] @(at 符号)
XPath 注入:清理输入以排除上下文更改符号,例如:
[1] '(单引号)
[2] "(引号) 等
LDAP 注入: A. 使用正面验证。字母数字过滤(A..Z,a..z,0..9)适合大部分 LDAP 查询。 B. 应该过滤出或进行转义的特殊 LDAP 字符:
[1] 在字符串开头的空格或“#”字符
[2] 在字符串结尾的空格字符
[3] ,(逗号)
[4] +(加号)
[5] "(引号)
[6] \(反斜杠)
[7] <>(尖括号)
[8] ;(分号)
[9] ()(括号)
MX 注入: 应该过滤出特殊 MX 字符:
[1] CR(回车符,ASCII 0x0d)
[2] LF(换行,ASCII 0x0a)记录伪造:
应该过滤出特殊记录字符:
[1] CR(回车符,ASCII 0x0d)
[2] LF(换行,ASCII 0x0a)
[3] BS(退格,ASCII 0x08)
ORM 注入: A. 确保用户输入的值和类型(如 Integer、Date 等)有效,且符合应用程序预期。 B. 利用存储过程,将数据访问抽象化,让用户不直接访问表或视图。 C. 使用参数化查询 API D. 清理输入以排除上下文更改符号,例如: (*):
[1] '(单引号)
[2] "(引号)
[3] \'(反斜线转义单引号)
[4] \"(反斜杠转义引号)
[5] )(结束括号)
[6] ;(分号)
(*) 这适用于 SQL。高级查询语言可能需要不同的清理机制。
[1] 我们建议您将服务器升级至 .NET Framework 2.0(或更新的版本),它本身就包括针对跨站点脚本编制攻击进行保护的安全检查。 [2] 您可以使用验证控件,将输入验证添加到“Web 表单”页面。 验证控件提供适用于标准验证的所有常见类型的易用机制(例如,测试验证日期是否有效,或验证值是否在范围内)。 另外,验证控件也支持定制编写验证,可让您完整定制向用户显示错误信息的方式。 验证控件可以搭配“Web 表单”页面类文件中处理的任何控件来使用,其中包括 HTML 和 Web 服务器控件。
要确保用户输入仅包含有效值,您可以使用以下验证控件中的一种:
[1] “RangeValidator”:检查用户条目(值)是否在指定的上下界限之间。您可以检查配对数字、字母字符和日期内的范围。 [2] “RegularExpressionValidator”:检查条目是否与正则表达式定义的模式相匹配。此类型的验证使您能够检查可预见的字符序列,如社会保险号码、电子邮件地址、电话号码、邮政编码等中的字符序列。 有助于阻止跨站点脚本编制的正则表达式示例:
- 可以拒绝基本跨站点脚本编制变体的正则表达式可能如下:^([^<]|\<[^a-zA-Z])*[<]?$ - 拒绝上述所有字符的一般正则表达式可能如下:^([^\<\>\"\'\%\;\)\(\&\+]*)$
重要注意事项:验证控件不会阻止用户输入或更改页面处理流程;它们只会设置错误状态,并产生错误消息。程序员的职责是,在执行进一步的应用程序特定操作前,测试代码中控件的状态。 有两种方法可检查用户输入的有效性: 1. 测试常规错误状态: 在您的代码中,测试页面的 IsValid 属性。 该属性会将页面上所有验证控件的 IsValid 属性值汇总(使用逻辑 AND)。如果将其中一个验证控件设置为无效,那么页面属性将会返回 false。 2. 测试个别控件的错误状态: 在页面的“验证器”集合中循环,该集合包含对所有验证控件的引用。 然后,您就可以检查每个验证控件的 IsValid 属性。
最后,我们建议使用 Microsoft Anti-Cross Site Scripting Library(V1.5 更高版本)对不受信任的用户输入进行编码。
Anti-Cross Site Scripting Library 显现下列方法:
[1] HtmlEncode - 将在 HTML 中使用的输入字符串编码 [2] HtmlAttributeEncode - 将在 HTML 属性中使用的输入字符串编码 [3] JavaScriptEncode - 将在 JavaScript 中使用的输入字符串编码 [4] UrlEncode - 将在“统一资源定位器 (URL)”中使用的输入字符串编码 [5] VisualBasicScriptEncode - 将在 Visual Basic 脚本中使用的输入字符串编码 [6] XmlEncode - 将在 XML 中使用的输入字符串编码 [7] XmlAttributeEncode - 将在 XML 属性中使用的输入字符串编码
如果要适当使用 Microsoft Anti-Cross Site Scripting Library 来保护 ASP.NET Web 应用程序,您必须运行下列操作:
第 1 步:复查生成输出的 ASP.NET 代码 第 2 步:判断是否包括不受信任的输入参数 第 3 步:判断不受信任的输入的上下文是否作为输出,判断要使用哪个编码方法 第 4 步:编码输出
第 3 步骤的示例:
注意:如果要使用不受信任的输入来安装 HTML 属性,便应该使用 Microsoft.Security.Application.HtmlAttributeEncode 方法,将不受信任的输入编码。 另外,如果要在 JavaScript 的上下文中使用不受信任的输入,便应该使用 Microsoft.Security.Application.JavaScriptEncode 来编码。
第 4 步骤的示例:将输出编码时,必须记住的一些重要事项:
[1] 输出应该编码一次。 [2] 输出的编码与实际撰写,应该尽可能接近。 例如,如果应用程序读取用户输入、处理输入,再用某种形式将它重新写出,便应该紧接在撰写输出之前进行编码。
要确保用户输入仅包含有效值,您可以使用以下验证控件中的一种:
[1] “RangeValidator”:检查用户条目(值)是否在指定的上下界限之间。您可以检查配对数字、字母字符和日期内的范围。 [2] “RegularExpressionValidator”:检查条目是否与正则表达式定义的模式相匹配。此类型的验证使您能够检查可预见的字符序列,如社会保险号码、电子邮件地址、电话号码、邮政编码等中的字符序列。 有助于阻止跨站点脚本编制的正则表达式示例:
- 可以拒绝基本跨站点脚本编制变体的正则表达式可能如下:^([^<]|\<[^a-zA-Z])*[<]?$ - 拒绝上述所有字符的一般正则表达式可能如下:^([^\<\>\"\'\%\;\)\(\&\+]*)$
重要注意事项:验证控件不会阻止用户输入或更改页面处理流程;它们只会设置错误状态,并产生错误消息。程序员的职责是,在执行进一步的应用程序特定操作前,测试代码中控件的状态。 有两种方法可检查用户输入的有效性: 1. 测试常规错误状态: 在您的代码中,测试页面的 IsValid 属性。 该属性会将页面上所有验证控件的 IsValid 属性值汇总(使用逻辑 AND)。如果将其中一个验证控件设置为无效,那么页面属性将会返回 false。 2. 测试个别控件的错误状态: 在页面的“验证器”集合中循环,该集合包含对所有验证控件的引用。 然后,您就可以检查每个验证控件的 IsValid 属性。
最后,我们建议使用 Microsoft Anti-Cross Site Scripting Library(V1.5 更高版本)对不受信任的用户输入进行编码。
Anti-Cross Site Scripting Library 显现下列方法:
[1] HtmlEncode - 将在 HTML 中使用的输入字符串编码 [2] HtmlAttributeEncode - 将在 HTML 属性中使用的输入字符串编码 [3] JavaScriptEncode - 将在 JavaScript 中使用的输入字符串编码 [4] UrlEncode - 将在“统一资源定位器 (URL)”中使用的输入字符串编码 [5] VisualBasicScriptEncode - 将在 Visual Basic 脚本中使用的输入字符串编码 [6] XmlEncode - 将在 XML 中使用的输入字符串编码 [7] XmlAttributeEncode - 将在 XML 属性中使用的输入字符串编码
如果要适当使用 Microsoft Anti-Cross Site Scripting Library 来保护 ASP.NET Web 应用程序,您必须运行下列操作:
第 1 步:复查生成输出的 ASP.NET 代码 第 2 步:判断是否包括不受信任的输入参数 第 3 步:判断不受信任的输入的上下文是否作为输出,判断要使用哪个编码方法 第 4 步:编码输出
第 3 步骤的示例:
注意:如果要使用不受信任的输入来安装 HTML 属性,便应该使用 Microsoft.Security.Application.HtmlAttributeEncode 方法,将不受信任的输入编码。 另外,如果要在 JavaScript 的上下文中使用不受信任的输入,便应该使用 Microsoft.Security.Application.JavaScriptEncode 来编码。
// Vulnerable code // Note that untrusted input is being treated as an HTML attribute Literal1.Text = "<hr noshade size=[untrusted input here]>"; // Modified code Literal1.Text = "<hr noshade size="+Microsoft.Security.Application.AntiXss.HtmlAttributeEncode([untrusted input here])+">";
第 4 步骤的示例:将输出编码时,必须记住的一些重要事项:
[1] 输出应该编码一次。 [2] 输出的编码与实际撰写,应该尽可能接近。 例如,如果应用程序读取用户输入、处理输入,再用某种形式将它重新写出,便应该紧接在撰写输出之前进行编码。
// Incorrect sequence protected void Button1_Click(object sender, EventArgs e) { // Read input String Input = TextBox1.Text; // Encode untrusted input Input = Microsoft.Security.Application.AntiXss.HtmlEncode(Input); // Process input ... // Write Output Response.Write("The input you gave was"+Input); } // Correct Sequence protected void Button1_Click(object sender, EventArgs e) { // Read input String Input = TextBox1.Text; // Process input ... // Encode untrusted input and write output Response.Write("The input you gave was"+ Microsoft.Security.Application.AntiXss.HtmlEncode(Input)); }
** 输入数据验证:虽然为了用户的方便,可以提供“客户端”层数据的数据验证,但必须使用 Servlet 在服务器层执行验证。 客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 Javascript。 一份好的设计通常需要 Web 应用程序框架,以提供服务器端实用程序例程,从而验证以下内容:[1] 必需字段[2] 字段数据类型(缺省情况下,所有 HTTP 请求参数都是“字符串”)[3] 字段长度[4] 字段范围[5] 字段选项[6] 字段模式[7] cookie 值[8] HTTP 响应好的做法是将以上例程作为“验证器”实用程序类中的静态方法实现。以下部分描述验证器类的一个示例。 [1] 必需字段“始终”检查字段不为空,并且其长度要大于零,不包括行距和后面的空格。
[2] 输入的 Web 应用程序中的字段数据类型和输入参数欠佳。例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。 使用 Java 基本包装程序类,来检查是否可将字段值安全地转换为所需的基本数据类型。 验证数字字段(int 类型)的方式的示例:
好的做法是将所有 HTTP 请求参数转换为其各自的数据类型。例如,开发者应将请求参数的“integerValue”存储在请求属性中,并按以下示例所示来使用:
应用程序应处理的主要 Java 数据类型: - Byte - Short - Integer - Long - Float - Double - Date
[3] 字段长度“始终”确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。以下示例验证 userName 字段的长度是否在 8 至 20 个字符之间:
[4] 字段范围 始终确保输入参数是在由功能需求定义的范围内。 以下示例验证输入 numberOfChoices 是否在 10 至 20 之间:
[5] 字段选项 Web 应用程序通常会为用户显示一组可供选择的选项(例如,使用 SELECT HTML 标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。以下示例验证用户针对允许的选项列表进行的选择:
[6] 字段模式 始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]*$
Java 1.3 或更早的版本不包含任何正则表达式包。建议将“Apache 正则表达式包”(请参阅以下“资源”)与 Java 1.3 一起使用,以解决该缺乏支持的问题。执行正则表达式验证的示例:
Java 1.4 引进了一种新的正则表达式包(java.util.regex)。以下是使用新的 Java 1.4 正则表达式包的 Validator.matchPattern 修订版:
[7] cookie 值使用 javax.servlet.http.Cookie 对象来验证 cookie 值。适用于 cookie 值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。
验证必需 cookie 值的示例:
[8] HTTP 响应 [8-1] 过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,请通过将敏感字符转换为其对应的字符实体来清理 HTML。这些是 HTML 敏感字符:< > " ' % ; ) ( & +
以下示例通过将敏感字符转换为其对应的字符实体来过滤指定字符串:
Java Servlet API 2.3 引进了过滤器,它支持拦截和转换 HTTP 请求或响应。
以下示例使用 Validator.filter 来用“Servlet 过滤器”清理响应:
[8-2] 保护 cookie 在 cookie 中存储敏感数据时,确保使用 Cookie.setSecure(布尔标志)在 HTTP 响应中设置 cookie 的安全标志,以指导浏览器使用安全协议(如 HTTPS 或 SSL)发送 cookie。 保护“用户”cookie 的示例:
推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是: [1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator 实施所有以上数据验证需求,是强大的框架。这些规则配置在定义表单字段的输入验证规则的 XML 文件中。在缺省情况下,Struts 支持在使用 Struts“bean:write”标记撰写的所有数据上,过滤 [8] HTTP 响应中输出的危险字符。可通过设置“filter=false”标志来禁用该过滤。 Struts 定义以下基本输入验证器,但也可定义定制的验证器: required:如果字段包含空格以外的任何字符,便告成功。 mask:如果值与掩码属性给定的正则表达式相匹配,便告成功。 range:如果值在 min 和 max 属性给定的值的范围内((value >= min) & (value <= max)),便告成功。 maxLength:如果字段长度小于或等于 max 属性,便告成功。 minLength:如果字段长度大于或等于 min 属性,便告成功。 byte、short、integer、long、float、double:如果可将值转换为对应的基本类型,便告成功。 date:如果值代表有效日期,便告成功。可能会提供日期模式。 creditCard:如果值可以是有效的信用卡号码,便告成功。 e-mail:如果值可以是有效的电子邮件地址,便告成功。
[2] JavaServer Faces 技术 “JavaServer Faces 技术”是一组代表 UI 组件、管理组件状态、处理事件和验证输入的 Java API(JSR 127)。 JavaServer Faces API 实现以下基本验证器,但可定义定制的验证器: validate_doublerange:在组件上注册 DoubleRangeValidator。 validate_length:在组件上注册 LengthValidator。 validate_longrange:在组件上注册 LongRangeValidator。 validate_required:在组件上注册 RequiredValidator。 validate_stringrange:在组件上注册 StringRangeValidator。 validator:在组件上注册定制的 Validator。 JavaServer Faces API 定义以下 UIInput 和 UIOutput 处理器(标记): input_date:接受以 java.text.Date 实例格式化的 java.util.Date。 output_date:显示以 java.text.Date 实例格式化的 java.util.Date。 input_datetime:接受以 java.text.DateTime 实例格式化的 java.util.Date。 output_datetime:显示以 java.text.DateTime 实例格式化的 java.util.Date。 input_number:显示以 java.text.NumberFormat 格式化的数字数据类型(java.lang.Number 或基本类型)。 output_number:显示以 java.text.NumberFormat 格式化的数字数据类型(java.lang.Number 或基本类型)。 input_text:接受单行文本字符串。 output_text:显示单行文本字符串。 input_time:接受以 java.text.DateFormat 时间实例格式化的 java.util.Date。 output_time:显示以 java.text.DateFormat 时间实例格式化的 java.util.Date。 input_hidden:允许页面作者在页面中包括隐藏变量。 input_secret:接受不含空格的单行文本,并在输入时,将其显示为一组星号。 input_textarea:接受多行文本。 output_errors:显示整个页面的错误消息,或与指定的客户端标识相关联的错误消息。 output_label:将嵌套的组件显示为指定输入字段的标签。 output_message:显示本地化消息。
引用 Java API 1.3 - http://java.sun.com/j2se/1.3/docs/api/ Java API 1.4 - http://java.sun.com/j2se/1.4/docs/api/ Java Servlet API 2.3 - http://java.sun.com/products/servlet/2.3/javadoc/ Java 正则表达式包 - http://jakarta.apache.org/regexp/Jakarta 验证器 - http://jakarta.apache.org/commons/validator/ JavaServer Faces 技术 - http://java.sun.com/j2ee/javaserverfaces/ ** 错误处理: 许多 J2EE Web 应用程序体系结构都遵循“模型视图控制器(MVC)”模式。在该模式中,Servlet 扮演“控制器”的角色。Servlet 将应用程序处理委派给 EJB 会话 Bean(模型)之类的 JavaBean。然后,Servlet 再将请求转发给 JSP(视图),以呈现处理结果。Servlet 应检查所有的输入、输出、返回码、错误代码和已知的异常,以确保实际处理按预期进行。 数据验证可保护应用程序免遭恶意数据篡改,而有效的错误处理策略则是防止应用程序意外泄露内部错误消息(如异常堆栈跟踪)所不可或缺的。好的错误处理策略会处理以下项: [1] 定义错误 [2] 报告错误 [3] 呈现错误 [4] 错误映射 [1] 定义错误 应避免在应用程序层(如 Servlet)中硬编码错误消息。 相反地,应用程序应该使用映射到已知应用程序故障的错误密钥。好的做法是定义错误密钥,且该错误密钥映射到 HTML 表单字段或其他 Bean 属性的验证规则。例如,如果需要“user_name”字段,其内容为字母数字,并且必须在数据库中是唯一的,那么就应定义以下错误密钥: (a) ERROR_USERNAME_REQUIRED:该错误密钥用于显示消息,以通知用户需要“user_name”字段; (b) ERROR_USERNAME_ALPHANUMERIC:该错误密钥用于显示消息,以通知用户“user_name”字段应该是字母数字; (c) ERROR_USERNAME_DUPLICATE:该错误密钥用于显示消息,以通知用户“user_name”值在数据库中重复; (d) ERROR_USERNAME_INVALID:该错误密钥用于显示一般消息,以通知用户“user_name”值无效; 好的做法是定义用于存储和报告应用程序错误的以下框架 Java 类: - ErrorKeys:定义所有错误密钥
- Errors:封装错误的集合
以下是使用上述框架类来处理“user_name”字段验证错误的示例:
[2] 报告错误 有两种方法可报告 web 层应用程序错误: (a) Servlet 错误机制 (b) JSP 错误机制
[2-a] Servlet 错误机制 Servlet 可通过以下方式报告错误: - 转发给输入 JSP(已将错误存储在请求属性中),或 - 使用 HTTP 错误代码参数来调用 response.sendError,或 - 抛出异常
好的做法是处理所有已知应用程序错误(如 [1] 部分所述),将这些错误存储在请求属性中,然后转发给输入 JSP。输入 JSP 应显示错误消息,并提示用户重新输入数据。以下示例阐明转发给输入 JSP(userInput.jsp)的方式:
如果 Servlet 无法转发给已知的 JSP 页面,那么第二个选项是使用 response.sendError 方法,将 HttpServletResponse.SC_INTERNAL_SERVER_ERROR(状态码 500)作为参数,来报告错误。 请参阅 javax.servlet.http.HttpServletResponse 的 Javadoc,以获取有关各种 HTTP 状态码的更多详细信息。返回 HTTP 错误的示例:
作为最后的手段,Servlet 可以抛出异常,且该异常必须是以下其中一类的子类: - RuntimeException - ServletException - IOException [2-b] JSP 错误机制 JSP 页面通过定义 errorPage 伪指令来提供机制,以处理运行时异常,如以下示例所示:
未捕获的 JSP 异常被转发给指定的 errorPage,并且原始异常设置在名称为 javax.servlet.jsp.jspException 的请求参数中。错误页面必须包括 isErrorPage 伪指令:
isErrorPage 伪指令导致“exception”变量初始化为所抛出的异常对象。 [3] 呈现错误 J2SE Internationalization API 提供使应用程序资源外部化以及将消息格式化的实用程序类,其中包括: (a) 资源束 (b) 消息格式化 [3-a] 资源束 资源束通过将本地化数据从使用该数据的源代码中分离来支持国际化。每一资源束都会为特定的语言环境存储键/值对的映射。 java.util.PropertyResourceBundle 将内容存储在外部属性文件中,对其进行使用或扩展都很常见,如以下示例所示:
可定义多种资源,以支持不同的语言环境(因此名为资源束)。例如,可定义 ErrorMessages_fr.properties 以支持该束系列的法语成员。如果请求的语言环境的资源成员不存在,那么会使用缺省成员。在以上示例中,缺省资源是 ErrorMessages.properties。应用程序(JSP 或 Servlet)会根据用户的语言环境从适当的资源检索内容。 [3-b] 消息格式化 J2SE 标准类 java.util.MessageFormat 提供使用替换占位符来创建消息的常规方法。MessageFormat 对象包含嵌入了格式说明符的模式字符串,如下所示:
以下是使用 ResourceBundle 和 MessageFormat 来呈现错误消息的更加全面的示例:
建议定义定制 JSP 标记(如 displayErrors),以迭代处理并呈现错误消息,如以上示例所示。
[4] 错误映射 通常情况下,“Servlet 容器”会返回与响应状态码或异常相对应的缺省错误页面。可以使用定制错误页面来指定状态码或异常与 Web 资源之间的映射。好的做法是开发不会泄露内部错误状态的静态错误页面(缺省情况下,大部分 Servlet 容器都会报告内部错误消息)。该映射配置在“Web 部署描述符(web.xml)”中,如以下示例所指定:
推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是: [1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator是 Java 框架,定义如上所述的错误处理机制。验证规则配置在 XML 文件中,该文件定义了表单字段的输入验证规则以及对应的验证错误密钥。Struts 提供国际化支持以使用资源束和消息格式化来构建本地化应用程序。
Struts JSP 标记库定义了有条件地显示一组累计错误消息的“errors”标记,如以下示例所示:
[2] JavaServer Faces 技术 “JavaServer Faces 技术”是一组代表 UI 组件、管理组件状态、处理事件、验证输入和支持国际化的 Java API(JSR 127)。
JavaServer Faces API 定义“output_errors”UIOutput 处理器,该处理器显示整个页面的错误消息,或与指定的客户端标识相关联的错误消息。
引用 Java API 1.3 - http://java.sun.com/j2se/1.3/docs/api/ Java API 1.4 - http://java.sun.com/j2se/1.4/docs/api/ Java Servlet API 2.3 - http://java.sun.com/products/servlet/2.3/javadoc/ Java 正则表达式包 - http://jakarta.apache.org/regexp/Jakarta 验证器 - http://jakarta.apache.org/commons/validator/ JavaServer Faces 技术 - http://java.sun.com/j2ee/javaserverfaces/
// Java example to validate required fields public Class Validator { ... public static boolean validateRequired(String value) { boolean isFieldValid = false; if (value != null && value.trim().length() > 0) { isFieldValid = true; } return isFieldValid; } ... } ... String fieldValue = request.getParameter("fieldName"); if (Validator.validateRequired(fieldValue)) { // fieldValue is valid, continue processing request ... }
[2] 输入的 Web 应用程序中的字段数据类型和输入参数欠佳。例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。 使用 Java 基本包装程序类,来检查是否可将字段值安全地转换为所需的基本数据类型。 验证数字字段(int 类型)的方式的示例:
// Java example to validate that a field is an int number public Class Validator { ... public static boolean validateInt(String value) { boolean isFieldValid = false; try { Integer.parseInt(value); isFieldValid = true; } catch (Exception e) { isFieldValid = false; } return isFieldValid; } ... } ... // check if the HTTP request parameter is of type int String fieldValue = request.getParameter("fieldName"); if (Validator.validateInt(fieldValue)) { // fieldValue is valid, continue processing request ... }
好的做法是将所有 HTTP 请求参数转换为其各自的数据类型。例如,开发者应将请求参数的“integerValue”存储在请求属性中,并按以下示例所示来使用:
// Example to convert the HTTP request parameter to a primitive wrapper data type // and store this value in a request attribute for further processing String fieldValue = request.getParameter("fieldName"); if (Validator.validateInt(fieldValue)) { // convert fieldValue to an Integer Integer integerValue = Integer.getInteger(fieldValue); // store integerValue in a request attribute request.setAttribute("fieldName", integerValue); } ... // Use the request attribute for further processing Integer integerValue = (Integer)request.getAttribute("fieldName"); ...
应用程序应处理的主要 Java 数据类型: - Byte - Short - Integer - Long - Float - Double - Date
[3] 字段长度“始终”确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。以下示例验证 userName 字段的长度是否在 8 至 20 个字符之间:
// Example to validate the field length public Class Validator { ... public static boolean validateLength(String value, int minLength, int maxLength) { String validatedValue = value; if (!validateRequired(value)) { validatedValue = ""; } return (validatedValue.length() >= minLength && validatedValue.length() <= maxLength); } ... } ... String userName = request.getParameter("userName"); if (Validator.validateRequired(userName)) { if (Validator.validateLength(userName, 8, 20)) { // userName is valid, continue further processing ... } }
[4] 字段范围 始终确保输入参数是在由功能需求定义的范围内。 以下示例验证输入 numberOfChoices 是否在 10 至 20 之间:
// Example to validate the field range public Class Validator { ... public static boolean validateRange(int value, int min, int max) { return (value >= min && value <= max); } ... } ... String fieldValue = request.getParameter("numberOfChoices"); if (Validator.validateRequired(fieldValue)) { if (Validator.validateInt(fieldValue)) { int numberOfChoices = Integer.parseInt(fieldValue); if (Validator.validateRange(numberOfChoices, 10, 20)) { // numberOfChoices is valid, continue processing request ... } } }
[5] 字段选项 Web 应用程序通常会为用户显示一组可供选择的选项(例如,使用 SELECT HTML 标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。以下示例验证用户针对允许的选项列表进行的选择:
// Example to validate user selection against a list of options public Class Validator { ... public static boolean validateOption(Object[] options, Object value) { boolean isValidValue = false; try { List list = Arrays.asList(options); if (list != null) { isValidValue = list.contains(value); } } catch (Exception e) { } return isValidValue; } ... } ... // Allowed options String[] options = {"option1", "option2", "option3"); // Verify that the user selection is one of the allowed options String userSelection = request.getParameter("userSelection"); if (Validator.validateOption(options, userSelection)) { // valid user selection, continue processing request ... }
[6] 字段模式 始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]*$
Java 1.3 或更早的版本不包含任何正则表达式包。建议将“Apache 正则表达式包”(请参阅以下“资源”)与 Java 1.3 一起使用,以解决该缺乏支持的问题。执行正则表达式验证的示例:
// Example to validate that a given value matches a specified pattern // using the Apache regular expression package import org.apache.regexp.RE; import org.apache.regexp.RESyntaxException; public Class Validator { ... public static boolean matchPattern(String value, String expression) { boolean match = false; if (validateRequired(expression)) { RE r = new RE(expression); match = r.match(value); } return match; } ... } ... // Verify that the userName request parameter is alphanumeric String userName = request.getParameter("userName"); if (Validator.matchPattern(userName, "^[a-zA-Z0-9]*$")) { // userName is valid, continue processing request ... }
Java 1.4 引进了一种新的正则表达式包(java.util.regex)。以下是使用新的 Java 1.4 正则表达式包的 Validator.matchPattern 修订版:
// Example to validate that a given value matches a specified pattern // using the Java 1.4 regular expression package import java.util.regex.Pattern; import java.util.regexe.Matcher; public Class Validator { ... public static boolean matchPattern(String value, String expression) { boolean match = false; if (validateRequired(expression)) { match = Pattern.matches(expression, value); } return match; } ... }
[7] cookie 值使用 javax.servlet.http.Cookie 对象来验证 cookie 值。适用于 cookie 值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。
验证必需 cookie 值的示例:
// Example to validate a required cookie value // First retrieve all available cookies submitted in the HTTP request Cookie[] cookies = request.getCookies(); if (cookies != null) { // find the "user" cookie for (int i=0; i<cookies.length; ++i) { if (cookies[i].getName().equals("user")) { // validate the cookie value if (Validator.validateRequired(cookies[i].getValue()) { // valid cookie value, continue processing request ... } } } }
[8] HTTP 响应 [8-1] 过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,请通过将敏感字符转换为其对应的字符实体来清理 HTML。这些是 HTML 敏感字符:< > " ' % ; ) ( & +
以下示例通过将敏感字符转换为其对应的字符实体来过滤指定字符串:
// Example to filter sensitive data to prevent cross-site scripting public Class Validator { ... public static String filter(String value) { if (value == null) { return null; } StringBuffer result = new StringBuffer(value.length()); for (int i=0; i<value.length(); ++i) { switch (value.charAt(i)) { case '<': result.append("<"); break; case '>': result.append(">"); break; case '"': result.append("""); break; case '\'': result.append("'"); break; case '%': result.append("%"); break; case ';': result.append(";"); break; case '(': result.append("("); break; case ')': result.append(")"); break; case '&': result.append("&"); break; case '+': result.append("+"); break; default: result.append(value.charAt(i)); break; } return result; } ... } ... // Filter the HTTP response using Validator.filter PrintWriter out = response.getWriter(); // set output response out.write(Validator.filter(response)); out.close();
Java Servlet API 2.3 引进了过滤器,它支持拦截和转换 HTTP 请求或响应。
以下示例使用 Validator.filter 来用“Servlet 过滤器”清理响应:
// Example to filter all sensitive characters in the HTTP response using a Java Filter. // This example is for illustration purposes since it will filter all content in the response, including HTML tags! public class SensitiveCharsFilter implements Filter { ... public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { PrintWriter out = response.getWriter(); ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse)response); chain.doFilter(request, wrapper); CharArrayWriter caw = new CharArrayWriter(); caw.write(Validator.filter(wrapper.toString())); response.setContentType("text/html"); response.setContentLength(caw.toString().length()); out.write(caw.toString()); out.close(); } ... public class CharResponseWrapper extends HttpServletResponseWrapper { private CharArrayWriter output; public String toString() { return output.toString(); } public CharResponseWrapper(HttpServletResponse response){ super(response); output = new CharArrayWriter(); } public PrintWriter getWriter(){ return new PrintWriter(output); } } } }
[8-2] 保护 cookie 在 cookie 中存储敏感数据时,确保使用 Cookie.setSecure(布尔标志)在 HTTP 响应中设置 cookie 的安全标志,以指导浏览器使用安全协议(如 HTTPS 或 SSL)发送 cookie。 保护“用户”cookie 的示例:
// Example to secure a cookie, i.e. instruct the browser to // send the cookie using a secure protocol Cookie cookie = new Cookie("user", "sensitive"); cookie.setSecure(true); response.addCookie(cookie);
推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是: [1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator 实施所有以上数据验证需求,是强大的框架。这些规则配置在定义表单字段的输入验证规则的 XML 文件中。在缺省情况下,Struts 支持在使用 Struts“bean:write”标记撰写的所有数据上,过滤 [8] HTTP 响应中输出的危险字符。可通过设置“filter=false”标志来禁用该过滤。 Struts 定义以下基本输入验证器,但也可定义定制的验证器: required:如果字段包含空格以外的任何字符,便告成功。 mask:如果值与掩码属性给定的正则表达式相匹配,便告成功。 range:如果值在 min 和 max 属性给定的值的范围内((value >= min) & (value <= max)),便告成功。 maxLength:如果字段长度小于或等于 max 属性,便告成功。 minLength:如果字段长度大于或等于 min 属性,便告成功。 byte、short、integer、long、float、double:如果可将值转换为对应的基本类型,便告成功。 date:如果值代表有效日期,便告成功。可能会提供日期模式。 creditCard:如果值可以是有效的信用卡号码,便告成功。 e-mail:如果值可以是有效的电子邮件地址,便告成功。
<form-validation> <global> ... <validator name="required" classname="org.apache.struts.validator.FieldChecks" method="validateRequired" msg="errors.required"> </validator> <validator name="mask" classname="org.apache.struts.validator.FieldChecks" method="validateMask" msg="errors.invalid"> </validator> ... </global> <formset> <form name="loginForm"> <!-- userName is required and is alpha-numeric case insensitive --> <field property="userName" depends="required,mask"> <!-- message resource key to display if validation fails --> <msg name="mask" key="login.userName.maskmsg"/> <arg0 key="login.userName.displayname"/> <var> <var-name>mask</var-name> <var-value>^[a-zA-Z0-9]*$</var-value> </var> </field> ... </form> ... </formset> </form-validation>
[2] JavaServer Faces 技术 “JavaServer Faces 技术”是一组代表 UI 组件、管理组件状态、处理事件和验证输入的 Java API(JSR 127)。 JavaServer Faces API 实现以下基本验证器,但可定义定制的验证器: validate_doublerange:在组件上注册 DoubleRangeValidator。 validate_length:在组件上注册 LengthValidator。 validate_longrange:在组件上注册 LongRangeValidator。 validate_required:在组件上注册 RequiredValidator。 validate_stringrange:在组件上注册 StringRangeValidator。 validator:在组件上注册定制的 Validator。 JavaServer Faces API 定义以下 UIInput 和 UIOutput 处理器(标记): input_date:接受以 java.text.Date 实例格式化的 java.util.Date。 output_date:显示以 java.text.Date 实例格式化的 java.util.Date。 input_datetime:接受以 java.text.DateTime 实例格式化的 java.util.Date。 output_datetime:显示以 java.text.DateTime 实例格式化的 java.util.Date。 input_number:显示以 java.text.NumberFormat 格式化的数字数据类型(java.lang.Number 或基本类型)。 output_number:显示以 java.text.NumberFormat 格式化的数字数据类型(java.lang.Number 或基本类型)。 input_text:接受单行文本字符串。 output_text:显示单行文本字符串。 input_time:接受以 java.text.DateFormat 时间实例格式化的 java.util.Date。 output_time:显示以 java.text.DateFormat 时间实例格式化的 java.util.Date。 input_hidden:允许页面作者在页面中包括隐藏变量。 input_secret:接受不含空格的单行文本,并在输入时,将其显示为一组星号。 input_textarea:接受多行文本。 output_errors:显示整个页面的错误消息,或与指定的客户端标识相关联的错误消息。 output_label:将嵌套的组件显示为指定输入字段的标签。 output_message:显示本地化消息。
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> ... <jsp:useBean id="UserBean" class="myApplication.UserBean" scope="session" /> <f:use_faces> <h:form formName="loginForm" > <h:input_text id="userName" size="20" modelReference="UserBean.userName"> <f:validate_required/> <f:validate_length minimum="8" maximum="20"/> </h:input_text> <!-- display errors if present --> <h:output_errors id="loginErrors" clientId="userName"/> <h:command_button id="submit" label="Submit" commandName="submit" /><p> </h:form> </f:use_faces>
引用 Java API 1.3 - http://java.sun.com/j2se/1.3/docs/api/ Java API 1.4 - http://java.sun.com/j2se/1.4/docs/api/ Java Servlet API 2.3 - http://java.sun.com/products/servlet/2.3/javadoc/ Java 正则表达式包 - http://jakarta.apache.org/regexp/Jakarta 验证器 - http://jakarta.apache.org/commons/validator/ JavaServer Faces 技术 - http://java.sun.com/j2ee/javaserverfaces/ ** 错误处理: 许多 J2EE Web 应用程序体系结构都遵循“模型视图控制器(MVC)”模式。在该模式中,Servlet 扮演“控制器”的角色。Servlet 将应用程序处理委派给 EJB 会话 Bean(模型)之类的 JavaBean。然后,Servlet 再将请求转发给 JSP(视图),以呈现处理结果。Servlet 应检查所有的输入、输出、返回码、错误代码和已知的异常,以确保实际处理按预期进行。 数据验证可保护应用程序免遭恶意数据篡改,而有效的错误处理策略则是防止应用程序意外泄露内部错误消息(如异常堆栈跟踪)所不可或缺的。好的错误处理策略会处理以下项: [1] 定义错误 [2] 报告错误 [3] 呈现错误 [4] 错误映射 [1] 定义错误 应避免在应用程序层(如 Servlet)中硬编码错误消息。 相反地,应用程序应该使用映射到已知应用程序故障的错误密钥。好的做法是定义错误密钥,且该错误密钥映射到 HTML 表单字段或其他 Bean 属性的验证规则。例如,如果需要“user_name”字段,其内容为字母数字,并且必须在数据库中是唯一的,那么就应定义以下错误密钥: (a) ERROR_USERNAME_REQUIRED:该错误密钥用于显示消息,以通知用户需要“user_name”字段; (b) ERROR_USERNAME_ALPHANUMERIC:该错误密钥用于显示消息,以通知用户“user_name”字段应该是字母数字; (c) ERROR_USERNAME_DUPLICATE:该错误密钥用于显示消息,以通知用户“user_name”值在数据库中重复; (d) ERROR_USERNAME_INVALID:该错误密钥用于显示一般消息,以通知用户“user_name”值无效; 好的做法是定义用于存储和报告应用程序错误的以下框架 Java 类: - ErrorKeys:定义所有错误密钥
// Example: ErrorKeys defining the following error keys: // - ERROR_USERNAME_REQUIRED // - ERROR_USERNAME_ALPHANUMERIC // - ERROR_USERNAME_DUPLICATE // - ERROR_USERNAME_INVALID // ... public Class ErrorKeys { public static final String ERROR_USERNAME_REQUIRED = "error.username.required"; public static final String ERROR_USERNAME_ALPHANUMERIC = "error.username.alphanumeric"; public static final String ERROR_USERNAME_DUPLICATE = "error.username.duplicate"; public static final String ERROR_USERNAME_INVALID = "error.username.invalid"; ... }- Error:封装个别错误
// Example: Error encapsulates an error key. // Error is serializable to support code executing in multiple JVMs. public Class Error implements Serializable { // Constructor given a specified error key public Error(String key) { this(key, null); } // Constructor given a specified error key and array of placeholder objects public Error(String key, Object[] values) { this.key = key; this.values = values; } // Returns the error key public String getKey() { return this.key; } // Returns the placeholder values public Object[] getValues() { return this.values; } private String key = null; private Object[] values = null; }
- Errors:封装错误的集合
// Example: Errors encapsulates the Error objects being reported to the presentation layer. // Errors are stored in a HashMap where the key is the bean property name and value is an // ArrayList of Error objects. public Class Errors implements Serializable { // Adds an Error object to the Collection of errors for the specified bean property. public void addError(String property, Error error) { ArrayList propertyErrors = (ArrayList)errors.get(property); if (propertyErrors == null) { propertyErrors = new ArrayList(); errors.put(property, propertyErrors); } propertyErrors.put(error); } // Returns true if there are any errors public boolean hasErrors() { return (errors.size > 0); } // Returns the Errors for the specified property public ArrayList getErrors(String property) { return (ArrayList)errors.get(property); } private HashMap errors = new HashMap(); }
以下是使用上述框架类来处理“user_name”字段验证错误的示例:
// Example to process validation errors of the "user_name" field. Errors errors = new Errors(); String userName = request.getParameter("user_name"); // (a) Required validation rule if (!Validator.validateRequired(userName)) { errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_REQUIRED)); } // (b) Alpha-numeric validation rule else if (!Validator.matchPattern(userName, "^[a-zA-Z0-9]*$")) { errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_ALPHANUMERIC)); } else { // (c) Duplicate check validation rule // We assume that there is an existing UserValidationEJB session bean that implements // a checkIfDuplicate() method to verify if the user already exists in the database. try { ... if (UserValidationEJB.checkIfDuplicate(userName)) { errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_DUPLICATE)); } } catch (RemoteException e) { // log the error logger.error("Could not validate user for specified userName: " + userName); errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_DUPLICATE); } } // set the errors object in a request attribute called "errors" request.setAttribute("errors", errors); ...
[2] 报告错误 有两种方法可报告 web 层应用程序错误: (a) Servlet 错误机制 (b) JSP 错误机制
[2-a] Servlet 错误机制 Servlet 可通过以下方式报告错误: - 转发给输入 JSP(已将错误存储在请求属性中),或 - 使用 HTTP 错误代码参数来调用 response.sendError,或 - 抛出异常
好的做法是处理所有已知应用程序错误(如 [1] 部分所述),将这些错误存储在请求属性中,然后转发给输入 JSP。输入 JSP 应显示错误消息,并提示用户重新输入数据。以下示例阐明转发给输入 JSP(userInput.jsp)的方式:
// Example to forward to the userInput.jsp following user validation errors RequestDispatcher rd = getServletContext().getRequestDispatcher("/user/userInput.jsp"); if (rd != null) { rd.forward(request, response); }
如果 Servlet 无法转发给已知的 JSP 页面,那么第二个选项是使用 response.sendError 方法,将 HttpServletResponse.SC_INTERNAL_SERVER_ERROR(状态码 500)作为参数,来报告错误。 请参阅 javax.servlet.http.HttpServletResponse 的 Javadoc,以获取有关各种 HTTP 状态码的更多详细信息。返回 HTTP 错误的示例:
// Example to return a HTTP error code RequestDispatcher rd = getServletContext().getRequestDispatcher("/user/userInput.jsp"); if (rd == null) { // messages is a resource bundle with all message keys and values response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, messages.getMessage(ErrorKeys.ERROR_USERNAME_INVALID)); }
作为最后的手段,Servlet 可以抛出异常,且该异常必须是以下其中一类的子类: - RuntimeException - ServletException - IOException [2-b] JSP 错误机制 JSP 页面通过定义 errorPage 伪指令来提供机制,以处理运行时异常,如以下示例所示:
<%@ page errorPage="/errors/userValidation.jsp" %>
未捕获的 JSP 异常被转发给指定的 errorPage,并且原始异常设置在名称为 javax.servlet.jsp.jspException 的请求参数中。错误页面必须包括 isErrorPage 伪指令:
<%@ page isErrorPage="true" %>
isErrorPage 伪指令导致“exception”变量初始化为所抛出的异常对象。 [3] 呈现错误 J2SE Internationalization API 提供使应用程序资源外部化以及将消息格式化的实用程序类,其中包括: (a) 资源束 (b) 消息格式化 [3-a] 资源束 资源束通过将本地化数据从使用该数据的源代码中分离来支持国际化。每一资源束都会为特定的语言环境存储键/值对的映射。 java.util.PropertyResourceBundle 将内容存储在外部属性文件中,对其进行使用或扩展都很常见,如以下示例所示:
################################################ # ErrorMessages.properties ################################################ # required user name error message error.username.required=User name field is required # invalid user name format error.username.alphanumeric=User name must be alphanumeric # duplicate user name error message error.username.duplicate=User name {0} already exists, please choose another one ...
可定义多种资源,以支持不同的语言环境(因此名为资源束)。例如,可定义 ErrorMessages_fr.properties 以支持该束系列的法语成员。如果请求的语言环境的资源成员不存在,那么会使用缺省成员。在以上示例中,缺省资源是 ErrorMessages.properties。应用程序(JSP 或 Servlet)会根据用户的语言环境从适当的资源检索内容。 [3-b] 消息格式化 J2SE 标准类 java.util.MessageFormat 提供使用替换占位符来创建消息的常规方法。MessageFormat 对象包含嵌入了格式说明符的模式字符串,如下所示:
// Example to show how to format a message using placeholder parameters String pattern = "User name {0} already exists, please choose another one"; String userName = request.getParameter("user_name"); Object[] args = new Object[1]; args[0] = userName; String message = MessageFormat.format(pattern, args);
以下是使用 ResourceBundle 和 MessageFormat 来呈现错误消息的更加全面的示例:
// Example to render an error message from a localized ErrorMessages resource (properties file) // Utility class to retrieve locale-specific error messages public Class ErrorMessageResource { // Returns the error message for the specified error key in the environment locale public String getErrorMessage(String errorKey) { return getErrorMessage(errorKey, defaultLocale); } // Returns the error message for the specified error key in the specified locale public String getErrorMessage(String errorKey, Locale locale) { return getErrorMessage(errorKey, null, locale); } // Returns a formatted error message for the specified error key in the specified locale public String getErrorMessage(String errorKey, Object[] args, Locale locale) { // Get localized ErrorMessageResource ResourceBundle errorMessageResource = ResourceBundle.getBundle("ErrorMessages", locale); // Get localized error message String errorMessage = errorMessageResource.getString(errorKey); if (args != null) { // Format the message using the specified placeholders args return MessageFormat.format(errorMessage, args); } else { return errorMessage; } } // default environment locale private Locale defaultLocale = Locale.getDefaultLocale(); } ... // Get the user's locale Locale userLocale = request.getLocale(); // Check if there were any validation errors Errors errors = (Errors)request.getAttribute("errors"); if (errors != null && errors.hasErrors()) { // iterate through errors and output error messages corresponding to the "user_name" property ArrayList userNameErrors = errors.getErrors("user_name"); ListIterator iterator = userNameErrors.iterator(); while (iterator.hasNext()) { // Get the next error object Error error = (Error)iterator.next(); String errorMessage = ErrorMessageResource.getErrorMessage(error.getKey(), userLocale); output.write(errorMessage + "\r\n"); } }
建议定义定制 JSP 标记(如 displayErrors),以迭代处理并呈现错误消息,如以上示例所示。
[4] 错误映射 通常情况下,“Servlet 容器”会返回与响应状态码或异常相对应的缺省错误页面。可以使用定制错误页面来指定状态码或异常与 Web 资源之间的映射。好的做法是开发不会泄露内部错误状态的静态错误页面(缺省情况下,大部分 Servlet 容器都会报告内部错误消息)。该映射配置在“Web 部署描述符(web.xml)”中,如以下示例所指定:
<!-- Mapping of HTTP error codes and application exceptions to error pages --> <error-page> <exception-type>UserValidationException</exception-type> <location>/errors/validationError.html</error-page> </error-page> <error-page> <error-code>500</exception-type> <location>/errors/internalError.html</error-page> </error-page> <error-page> ... </error-page> ...
推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是: [1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator是 Java 框架,定义如上所述的错误处理机制。验证规则配置在 XML 文件中,该文件定义了表单字段的输入验证规则以及对应的验证错误密钥。Struts 提供国际化支持以使用资源束和消息格式化来构建本地化应用程序。
<form-validation> <global> ... <validator name="required" classname="org.apache.struts.validator.FieldChecks" method="validateRequired" msg="errors.required"> </validator> <validator name="mask" classname="org.apache.struts.validator.FieldChecks" method="validateMask" msg="errors.invalid"> </validator> ... </global> <formset> <form name="loginForm"> <!-- userName is required and is alpha-numeric case insensitive --> <field property="userName" depends="required,mask"> <!-- message resource key to display if validation fails --> <msg name="mask" key="login.userName.maskmsg"/> <arg0 key="login.userName.displayname"/> <var> <var-name>mask</var-name> <var-value>^[a-zA-Z0-9]*$</var-value> </var> </field> ... </form> ... </formset> </form-validation>
Struts JSP 标记库定义了有条件地显示一组累计错误消息的“errors”标记,如以下示例所示:
<%@ page language="java" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <html:html> <head> <body> <html:form action="/logon.do"> <table border="0" width="100%"> <tr> <th align="right"> <html:errors property="username"/> <bean:message key="prompt.username"/> </th> <td align="left"> <html:text property="username" size="16"/> </td> </tr> <tr> <td align="right"> <html:submit><bean:message key="button.submit"/></html:submit> </td> <td align="right"> <html:reset><bean:message key="button.reset"/></html:reset> </td> </tr> </table> </html:form> </body> </html:html>
[2] JavaServer Faces 技术 “JavaServer Faces 技术”是一组代表 UI 组件、管理组件状态、处理事件、验证输入和支持国际化的 Java API(JSR 127)。
JavaServer Faces API 定义“output_errors”UIOutput 处理器,该处理器显示整个页面的错误消息,或与指定的客户端标识相关联的错误消息。
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> ... <jsp:useBean id="UserBean" class="myApplication.UserBean" scope="session" /> <f:use_faces> <h:form formName="loginForm" > <h:input_text id="userName" size="20" modelReference="UserBean.userName"> <f:validate_required/> <f:validate_length minimum="8" maximum="20"/> </h:input_text> <!-- display errors if present --> <h:output_errors id="loginErrors" clientId="userName"/> <h:command_button id="submit" label="Submit" commandName="submit" /><p> </h:form> </f:use_faces>
引用 Java API 1.3 - http://java.sun.com/j2se/1.3/docs/api/ Java API 1.4 - http://java.sun.com/j2se/1.4/docs/api/ Java Servlet API 2.3 - http://java.sun.com/products/servlet/2.3/javadoc/ Java 正则表达式包 - http://jakarta.apache.org/regexp/Jakarta 验证器 - http://jakarta.apache.org/commons/validator/ JavaServer Faces 技术 - http://java.sun.com/j2ee/javaserverfaces/
** 输入数据验证:虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 Javascript。 一份好的设计通常需要 Web 应用程序框架,以提供服务器端实用程序例程,从而验证以下内容:[1] 必需字段[2] 字段数据类型(缺省情况下,所有 HTTP 请求参数都是“字符串”)[3] 字段长度[4] 字段范围[5] 字段选项[6] 字段模式[7] cookie 值[8] HTTP 响应好的做法是实现一个或多个验证每个应用程序参数的函数。以下部分描述一些检查的示例。
[2] 输入的 Web 应用程序中的字段数据类型和输入参数欠佳。例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。[3] 字段长度“始终”确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。[4] 字段范围 始终确保输入参数是在由功能需求定义的范围内。 [5] 字段选项 Web 应用程序通常会为用户显示一组可供选择的选项(例如,使用 SELECT HTML 标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。[6] 字段模式 始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]+$
[7] cookie 值 适用于 cookie 值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。 [8] HTTP 响应[8-1] 过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,开发者应通过将敏感字符转换为其对应的字符实体来清理 HTML。这些是 HTML 敏感字符:< > " ' % ; ) ( & +
PHP 包含一些自动化清理实用程序函数,如 htmlentities():
此外,为了避免“跨站点脚本编制”的 UTF-7 变体,您应该显式定义响应的 Content-Type 头,例如:
[8-2] 保护 cookie 在 cookie 中存储敏感数据且通过 SSL 来传输时,请确保先在 HTTP 响应中设置 cookie 的安全标志。这将会指示浏览器仅通过 SSL 连接来使用该 cookie。 为了保护 cookie,您可以使用以下代码示例:
此外,我们建议您使用 HttpOnly 标志。当 HttpOnly 标志设置为 TRUE 时,将只能通过 HTTP 协议来访问 cookie。这意味着无法用脚本语言(如 JavaScript)来访问 cookie。该设置可有效地帮助减少通过 XSS 攻击盗用身份的情况(虽然并非所有浏览器都支持该设置)。 在 PHP 5.2.0 中添加了 HttpOnly 标志。 引用[1] 使用 HTTP 专用 cookie 来减轻“跨站点脚本编制”的影响: http://msdn2.microsoft.com/en-us/library/ms533046.aspx [2] PHP 安全协会: http://phpsec.org/ [3] PHP 和 Web 应用程序安全博客(Chris Shiflett): http://shiflett.org/
// PHP example to validate required fields function validateRequired($input) { ... $pass = false; if (strlen(trim($input))>0){ $pass = true; } return $pass; ... } ... if (validateRequired($fieldName)) { // fieldName is valid, continue processing request ... }
[2] 输入的 Web 应用程序中的字段数据类型和输入参数欠佳。例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。[3] 字段长度“始终”确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。[4] 字段范围 始终确保输入参数是在由功能需求定义的范围内。 [5] 字段选项 Web 应用程序通常会为用户显示一组可供选择的选项(例如,使用 SELECT HTML 标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。[6] 字段模式 始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]+$
[7] cookie 值 适用于 cookie 值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。 [8] HTTP 响应[8-1] 过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,开发者应通过将敏感字符转换为其对应的字符实体来清理 HTML。这些是 HTML 敏感字符:< > " ' % ; ) ( & +
PHP 包含一些自动化清理实用程序函数,如 htmlentities():
$input = htmlentities($input, ENT_QUOTES, 'UTF-8');
此外,为了避免“跨站点脚本编制”的 UTF-7 变体,您应该显式定义响应的 Content-Type 头,例如:
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
[8-2] 保护 cookie 在 cookie 中存储敏感数据且通过 SSL 来传输时,请确保先在 HTTP 响应中设置 cookie 的安全标志。这将会指示浏览器仅通过 SSL 连接来使用该 cookie。 为了保护 cookie,您可以使用以下代码示例:
<$php $value = "some_value"; $time = time()+3600; $path = "/application/"; $domain = ".example.com"; $secure = 1; setcookie("CookieName", $value, $time, $path, $domain, $secure, TRUE); ?>
此外,我们建议您使用 HttpOnly 标志。当 HttpOnly 标志设置为 TRUE 时,将只能通过 HTTP 协议来访问 cookie。这意味着无法用脚本语言(如 JavaScript)来访问 cookie。该设置可有效地帮助减少通过 XSS 攻击盗用身份的情况(虽然并非所有浏览器都支持该设置)。 在 PHP 5.2.0 中添加了 HttpOnly 标志。 引用[1] 使用 HTTP 专用 cookie 来减轻“跨站点脚本编制”的影响: http://msdn2.microsoft.com/en-us/library/ms533046.aspx [2] PHP 安全协会: http://phpsec.org/ [3] PHP 和 Web 应用程序安全博客(Chris Shiflett): http://shiflett.org/
© Copyright IBM Corp. 2000, 2011. All Rights Reserved.