☆蓝色梦想☆

世界总是反反覆覆错错落落地飘去 来不及叹息 生活不是平平淡淡从从容容的东西 不能放弃
posts - 57, comments - 5, trackbacks - 0, articles - 0

限制对Web资源的访问

Posted on 2005-12-06 10:00 ☆蓝色梦想☆ 阅读(448) 评论(0)  编辑  收藏 所属分类: J2EE

开发人员遇到的一个老问题是:如何使资源不完全暴露在大庭广众下,而只让那些适当的人和程序有完全的权限来访问他们需要的资源?至少有三个好方法可以解决这个问题。

作为一个Java Web开发人员,你可能已经对Web应用程序的目录结构很熟悉了(见图1)。在WEB-INF/classes目录下放置了servlet类,在WEB-INF/lib下是Java档案文件,如HTML和图片文件的静态资源直接放在应用程序目录下(或者在应用程序目录下的任何子目录中)。例如,所有图片文件都放在图片目录中(见图1)。JSP页面也放在应用程序目录中。


				图1.
图1. 一个 Servlet/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" %>

现在你应该了解了:你有三种不同的方法来保护你的资源,以及如何实现这些方法。



只有注册用户登录后才能发表评论。


网站导航: