开发人员遇到的一个老问题是:如何使资源不完全暴露在大庭广众下,而只让那些适当的人和程序有完全的权限来访问他们需要的资源?至少有三个好方法可以解决这个问题。
作为一个Java Web开发人员,你可能已经对Web应用程序的目录结构很熟悉了(见图1)。在WEB-INF/classes目录下放置了servlet类,在WEB-INF/lib下是Java档案文件,如HTML和图片文件的静态资源直接放在应用程序目录下(或者在应用程序目录下的任何子目录中)。例如,所有图片文件都放在图片目录中(见图1)。JSP页面也放在应用程序目录中。
应用程序的目录结构 |
这就是为什么许多程序员都把他们的资源文件(如果它们是和应用程序放在一起的话)放在WEB-INF目录中的原因。WEB-INF之外的任何资源都可以通过输入URL来查看。HTML文件和JSP文件一般都会被调用,所以它们通常存储在WEB-INF之外。
然而,你可能想限制对WEB-INF目录之外的文件的访问。你可以用三种方法:通过运用referer HTTP request header(是“referer”,不是“referrer”);通过检查用户的session对象中的一个属性;或者通过将那些资源放在WEB-INF中,并在适当的时候参照它们。下面是运用这三种方法的指南。
运用Referer HTTP Request Header
Referer HTTP request header指定了一个URI (Uniform Resource Identifier),该URI包含链接到被请求资源的页面的地址。例如,下面是对一个叫做myPage1.jsp的JSP页面的请求,该页面来自一个叫做Login.jsp的文件:
http://domainName/appName/Login.jsp |
如果直接在Web浏览器的Address或Location中输入URL来调用myPage1.jsp,就不会有一个referer header。
下面的例子运用了一个叫做DisplayRequestHeaders.jsp的JSP页面:
<%@ page import="java.util.Enumeration" %> <% Enumeration headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String header = (String) headers.nextElement(); out.println(header + ":" + request.getHeader(header) + "<br>"); } %> |
如果你直接将URL输入到浏览器的Address或Location来调用这个页面,结果如下:
accept:*/* accept-language:en-us accept-encoding:gzip, deflate user-agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705) host:localhost:8080?connection:Keep-Alive cookie:JSESSIONID=65D28447DFE4F58D1D806EAA933E9DD7 |
然而,如果通过点击另一个页面(如Login.jsp)中的一个链接来请求DisplayRequestHeaders.jsp,你可以看到如下的结果:
accept:image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, */* |
注意两者的主要区别是在第二个结果中出现了referer header。因此,如果你的应用程序规定,当你直接将URL输入到浏览器的Location或Address中来调用一个资源时,你不能看到这个资源(在这个例子中,就是myPage1.jsp),那么你可以把这个代码添加到myPage1.jsp的顶部:
if (request.getHeader("referer")==null) response.sendRedirect(somewhereElse); |
或者,如果你想确信资源来自一个特定的URL,可以在前面的代码后面添加下面的代码:
if (request.getHeader("referer")==null) response.sendRedirect(somewhereElse); if (request.getHeader("referer")!=aURL) response.sendRedirect(somewhereElse); |
然而,你需要注意,运用referer header只适合于简单的控制流管理。因为它依赖于来自用户的request headers,所以它并不是100%的安全。了解socket programming的聪明的用户常常可以确信有一个包含期望值的referer header。
检查一个Session对象的属性
另一种限制对一个特定页面的访问的方法就是通过检查用户的session对象中出现的一个特定的属性。例如,如果你想让ABC.jsp页面只能被登录后的用户看到,你可以在用户成功登录后,在用户session对象中设置一个叫做loggedIn的属性。然后在ABC.jsp页面的顶部,你可以查看loggedIn属性是否出现在用户的session对象中:
<% if (session.getAttribute("loggedIn")==null) { %> <jsp:forward page="Login.jsp"/> <% } else { %> display the page content here. |
这种方法的缺点是你在每一页都需要额外的代码,而且还有另外的维护工作。更重要的是,受限制的页面需要参预session管理,而这正是你在一些应用程序中可能想避免的事情。
在WEB-INF目录下存储资源
在你不想让用户调用你的JSP页面时,第三种方法——将资源放在WEB-INF下——会很有用。实现Model 2结构的应用程序和Struts应用程序运用JSP页面作为Model-View-Controller中的View。这些JSP页面并不打算让用户从浏览器直接调用。作为替代,它们从控制器servlet内部来调用。对这些应用程序来说,将JSP页面放在WEB-INF目录外——就像许多程序员所做的那样——需要你添加额外的代码来限制对这些资源的直接访问。
另一方面,把它们放在WEB-INF中可以保证它们只能从那个应用程序的一个servlet来访问。但如果你决定把JSP页面存在WEB-INF中,你就需要知道如何参照这些页面。幸运的是,这并不难。做法如下。
通过运用一个RequestDispatcher对象,一个servlet可以包含(include)或转送(forward)一个JSP页面。下面的例子forward并include一个叫做Included.jsp的JSP页面:
RequestDispatcher rd = request.getRequestDispatcher("/WEB- INF/Included.jsp"); rd.forward(request, response); |
但有时侯,你需要显示的是HTML文件,而不是JSP页面。在一些Web容器(containers)中,前面代码中的request dispatchers并不能用于静态的资源(如果静态资源不在WEB-INF下,它可以用)。对这些资源来说,你可以运用javax.servlet.ServletContext接口的getResourceAsStream方法。该方法的定义如下:
public java.io.InputStream getResourceAsStream(java.lang.String path) |
要运用这个方法,你需要把一个路径传送到你想包含的静态资源。该方法返回一个InputStream。然后,你可以用InputStream的read方法来得到静态资源的内容。
例如,下面的这个JSP页面包含一个叫做getResourceContent的函数,你可以用它以字符串形式返回一个静态资源的内容:
<%@ page import="java.io.InputStream"%> <%! public String getResourceContent(InputStream in) { if (in==null) return null; StringBuffer sb = new StringBuffer(2048); try { int i = in.read(); while (i != -1) { sb.append((char)i); i = in.read(); } in.close(); } catch (Exception e) { } return sb.toString(); } %> <html> <head> <title> </title> </head> <body> <br> <% String path = "/WEB-INF/header.html"; InputStream in = application.getResourceAsStream(path); out.println(getResourceContent(in)); %> </body> </html> |
注意,在JSP页面中,应用程序暗示的对象代表ServletContext对象。
该JSP页面显示了如何包含位于WEB-INF目录中的header.html文件的内容。如果你把header.html文件放在这儿,它就不能直接从浏览器调用。
通过运用一些缓冲策略,你可以优化JSP页面中的getResourceContent方法。在这里,我只是解释了得到放在WEB-INF目录下的一个静态资源的内容的方法。
你可以从一个servlet内部,也可以从一个JSP页面运用getResourceContent方法。从一个JSP页面,你可以运用指示符include,如下面的代码所示:
<%@ include file="/WEB-INF/header.html" %> |
现在你应该了解了:你有三种不同的方法来保护你的资源,以及如何实现这些方法。
提示:关于保护应用程序的更多信息,查看我的关于安全配置的文章。