開發人員遇到的一個老問題是:如何使資源不完全暴露在大庭廣眾下,而只讓那些適當的人和程序有完全的權限來訪問他們需要的資源?至少有三個好方法可以解決這個問題。
作為一個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" %>
|
現在你應該了解了:你有三種不同的方法來保護你的資源,以及如何實現這些方法。