- Struts 2.x权威指南
- 李刚编著
- 1733字
- 2025-03-11 03:21:11
5.1.3 服务器端校验
前一节已经介绍了,当我们为图4.2所示的页面增加了客户端校验后,如果浏览者的输入不满足基本要求,将无法通过该页面提交请求——请注意这句话,说得很清楚,仅仅是不能通过该页面提交请求,那么其他用户完全可以通过其他方式提交请求(很多恶意的Cracker,并不是通过浏览器来crack某个应用,他会采用更底层的Socket通信进行crack)。
即使不采用任何高明的手段,我们只是在图4.2所示的页面中,单击Firefox浏览器的“页面另存为”菜单,将该页面的源代码保存在计算机的任何位置,然后对该页面源代码进行简单修改,取消该表单元素的输入校验绑定,并修改该表单的action属性。修改后的表单元素的代码如下:
<!-- 取消表单元素的输入校验绑定,并修改表单元素的action属性 --> <form action="http://localhost:8888/ClientValidate/regist" method="post"> ... </form>
当修改了表单的action属性后,修改后的表单将会直接向该action指定的URL提交,这个URL是一个绝对地址:http://localhost:8888/ClientValidate/regist,此URL是笔者应用中处理该请求Servlet的URL。
如果在上面的输入页面中不输入任何信息,直接单击“注册”按钮,将可以把请求提交给服务器。
对于一个有经验的Cracker而言,他至少有100种方法来绕过客户端校验。因此,服务器端校验是必做的,而且必须更严格。
提示:
客户端校验的主要作用是防止正常浏览者的误输入,仅能对输入进行初步过滤;对于恶意用户的恶意行为,客户端校验将无能为力。因此,客户端校验绝不可代替服务器端校验。当然,客户端校验也绝不可少,因为Web应用的大部分浏览者都是正常的浏览者,他们的输入可能包含大量的误输入,客户端校验把这些误输入阻止在客户端,从而降低了服务器的负载。服务器端校验是请求数据进入系统之前的最后屏障。
从上面的介绍可以看出,服务器端校验是整个应用的最后防线,它阻止了非法数据进入系统,对于系统的安全性、完整性,承载着不可替代的作用。
下面是处理该请求的Servlet代码,该Servlet代码中增加了服务器端校验代码。
程序清单:codes\05\5.1\Validate\WEB-INF\src\org\crazyit\struts2\web\RegistServlet.java
@WebServlet(urlPatterns="/regist") public class RegistServlet extends HttpServlet { // 处理用户请求的service方法 public void service(HttpServletRequest request , HttpServletResponse response) throws IOException, ServletException { // 设置解析请求参数所用的解码集 request.setCharacterEncoding("GBK"); // 从HttpServletRequest中取出4个请求参数 String name = request.getParameter("username"); String pass = request.getParameter("pass"); String strAge = request.getParameter("age"); String strBirth = request.getParameter("birth"); // 错误字符串 String errStr = ""; // 校验用户名为空的情形 if (name == null || name.trim().equals("")) { errStr = "您必须输入用户名!"; } // 要求用户名必须是字母和数字,且长度必须在4到25之间 else if (!Pattern.matches("\\w{4,25}", name.trim())) { errStr += "<br />您输入的用户名必须是字母和数字,且长度必须在4到25之间!"; } // 校验密码为空的情形 if (pass == null || pass.trim().equals("")) { errStr += "<br />您必须输入密码!"; } // 要求密码必须是字母和数字,且长度必须在4到25之间 else if (!Pattern.matches("\\w{4,25}", pass.trim())) { errStr += "<br />您输入的密码必须是字母和数字,且长度必须在4到25之间!"; } int age = 0; try { // 将用户输入的年龄解析成一个整数 age = Integer.parseInt(strAge); // 如果年龄大于150或者小于0 if (age > 150 || age <= 0) { errStr += "<br />您输入的年龄必须是一个有效的年龄!"; } } // 如果将用户输入的年龄转换成整数出现异常,表明输入不合法 catch (Exception e) { errStr += "<br />您输入的年龄必须是整数!"; } // 构造一个日期格式器 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-DD"); Date birth = null; try { // 将用户输入的生日字符串转换成一个日期变量 birth = sdf.parse(strBirth); // 判断用户输入的日期必须在有效的时间段内 if (birth.after(sdf.parse("2050-02-21")) || birth.before(sdf.parse("1900-01-01"))) { errStr += "<br />您输入的生日必须在一个有效的时间段内"; } } // 如果将用户输入的生日字符串转换成日期出现异常,表明输入不合法 catch (Exception e) { errStr += "<br />您输入的生日必须是yyyy-MM-DD格式!"; } // 如果错误字符串为空,表明没有出现任何异常 if (errStr.equals("")) { // 将类型转换后的值封装成UserBean值对象 UserBean user = new UserBean(name , pass , age , birth); // 用Servlet直接输出 response.setContentType("text/html;charset=GBK"); // 获得页面输出流 PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); // 输出页面标题 out.println(" <title>类型转换页面</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>类型转换页面</h1>"); // 下面输出值对象user的4个属性值 out.println("用户的用户名:" + user.getName() + "<br />"); out.println("用户的密码:" + user.getPass() + "<br />"); out.println("用户的年龄:" + user.getAge() + "<br />"); out.println("用户的生日:" + user.getBirth() + "<br />"); out.println("</body>"); out.println("</html>"); } // 否则,校验失败 else { // 将错误字符串设置成一个HttpServletRequest属性 request.setAttribute("error" , errStr); // 将请求转发到登录页面 request.getRequestDispatcher("/regist.jsp") .forward(request , response); } } }
通过上面的粗体字代码为Servlet增加了校验规则后,系统的输入校验才算真正完整。此时,即使恶意浏览者绕过客户端校验,直接向该Servlet发送请求,该Servlet一样可以应付。
如果试图绕过客户端校验输入,直接向该Servlet发送非法的请求参数,该Servlet会检测到这些非法数据,并将请求转向到系统的/regist.jsp页面。如果向该Servlet提交非法数据,将看到如图5.3所示的页面。

图5.3 服务器端校验失败的页面
提示:
上面的登录页面可以显示服务器端校验失败的提示,因为上面的 regist.jsp 页面使用了表达式语言来输出HttpServletRequest请求中的error属性值。
在上面的输入校验代码中,有一些输入校验是通过类型转换实现的,也就是说,输入校验和类型转换的关系是非常紧密的。