开发人员遇到的一个老问题是:如何使资源不完全暴露在大庭广众下,而只让那些适当的人和程序有完全的权限来访问他们需要的资源?至少有三个好方法可以解决这个问题。
作为一个Java Web开发人员,你可能已经对Web应用程序的目录结构很熟悉了(见图1)。在WEB-INF/classes目录下放置了servlet类,在WEB-INF/lib下是Java档案文件,如HTML和图片文件的静态资源直接放在应用程序目录下(或者在应用程序目录下的任何子目录中)。例如,所有图片文件都放在图片目录中(见图1)。JSP页面也放在应用程序目录中。
幸运的是,放在WEB-INF目录下的资源是安全的。用户不可以简单地在浏览器的Location或Address中输入一个URL来调用它。
这就是为什么许多程序员都把他们的资源文件(如果它们是和应用程序放在一起的话)放在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:http://localhost:8080/myJSPApp/Login.jsp 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
|
注意两者的主要区别是在第二个结果中出现了referer header。因此,如果你的应用程序规定,当你直接将URL输入到浏览器的Location或Address中来调用一个资源时,你不能看到这个资源(在这个例子中,就是myPage1.jsp),那么你可以把这个代码添加到myPage1.jsp的顶部:
if (request.getHeader("referer")==null)
response.sendRedirect(somewhereElse);
|
其中somewhereElse是你想让用户进入的页面的URL。
或者,如果你想确信资源来自一个特定的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" %>
|
现在你应该了解了:你有三种不同的方法来保护你的资源,以及如何实现这些方法。