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