Posts Tagged ‘定制’

用定制标签库和配置文件实现对JSP页面元素的访问控制

星期一, 06月 2nd, 2008

控制客户端访问是开发一个基于B/Sde架构de系统de开发者必须考虑de问题.JSPSERVLET规范de基于配置文件de安全策略对资源de控制是以文件为单位de,即只可以定义某个视图全部可以或全部不能被访问.一个比较复杂de系统往往要要求对视图de一部分(如JSP页面里de一个按钮)提供访问控制,只允许被某种角色de用户访问.如果采用可编程de安全策略,因为对用户角色和操作de定义在开发时不能定义,而且这种策略加大了程序员de工作量,它可能不是一种好de办法.

我采用定制标签库和和配置文件来解决这个问题:把要权限控制deJSP页面元素如BUTTON,作为标签de内容.为受保护de内容起一个唯一de名称,把这个名称作为标签de一个属性.某个角色对某个页面元素或一组页面元素是否有权限,在XML配置文件中描述.

例如,下面deJSP页面有“详细”和“修改”两个按钮.

<%@ taglib uri=”http://mytag” prefix=”custTag” %>

<html>

<head>

<title>test</title>

</head>

<body >

<form name=”form1″ >

<table width=”600″ border=”0″ cellspacing=”0″ cellpadding=”2″ >

<tr>

<td>

<custTag:JspSecurity elementName=”employeedetail” >

<input type=”button” name=”detail” value=”详细” >

</custTag:JspSecurity>

<custTag:JspSecurity elementName=”employeemodify” >

<input type=”button” name=”modify” value=”修改” >

</custTag:JspSecurity>

</td>

</tr>

</table>

<br>

</form>

</body>

下面XML配置文件内容表示对角色为commonde用户,只对名为employeedetail de页面元素即“详细”按钮有权限,对角色为“admin”de用户,对名为employeedetail employeemodifyde页面元素即两个按钮都有权限.

<?xml version=”1.0″ encoding=”GB2312″?>

<security>

<htmlElement name=”employeedetail” >

<roleName name=”common” />

<roleName name=”admin” />

</htmlElement>

<htmlElement name=”employeemodify” >

<roleName name=”admin” />

</htmlElement>

</security>

定制标签类JspSecurityTag继承了BodyTagSupport类.BodyTagSupport有一个变量bodyContent指向起始标志和结束标志之间de内容.JspSecurityTagde私有静态变量roleList保存从XML文件中取到角色和页面元素de对应集合,私有变量ElementName对应页面元素de名称.当解析该定制标签时,首先先取到页面元素de名称,再取到当前用户de角色,如果角色有该页面元素de权限,就显示标签正文(即页面元素),否则不显示.

Pagekage com.presentation.viewhelper.JspSecurityTag;

import javax.servlet.jsp.tagext.*;

import javax.servlet.jsp.*;

import java.util.*;

import org.xml.sax.*;

import org.xml.sax.helpers.*;

import org.w3c.dom.*;

import java.io.*;

import javax.xml.parsers.*;

public class JspSecurityTag extends BodyTagSupport {

//保存从XML文件中取到角色和页面元素de对应集合

private static ArrayList roleList;

//页面元素de名称

private String elementName;

public void setElementName(String str)

{

this.elementName=str;

}

public int doAfterBody() throws JspException{

if(roleList==null)

{

roleList=getList();

}

try{

//如果认证通过就显示标签正文,否则跳过标签正文,就这么简单

if(isAuthentificated(elementName))

{

if(bodyContent != null){

JspWriter out=bodyContent.getEnclosingWriter();

bodyContent.writeOut(out);

}else

{

}

}

}catch(Exception e){

throw new JspException();

}

return SKIP_BODY;

}

//XML配置文件中取到角色和页面元素de对应,保存到静态deArrayList

private ArrayList getList()

{

DocumentBuilderFactory dbf =

DocumentBuilderFactory.newInstance();

DocumentBuilder db = null;

Document doc=null;

NodeList childlist = null;

String elementName;

String roleName;

int index;

ArrayList theList = new ArrayList();

try{

db = dbf.newDocumentBuilder();

}catch(Exception e)

{

e.printStackTrace();

}

try{

doc = db.parse(new File(”security.xml”));

}catch(Exception e)

{

e.printStackTrace();

}

//读取页面元素列表

NodeList elementList = doc.getElementsByTagName(”htmlElement”);

for(int i=0;i<elementList.getLength();i )

{

Element name = ((Element)elementList.item(i));

//页面元素de名称

elementName = name.getAttribute(”name”);

//该页面元素对应de有权限de角色de列表

NodeList rolNodeList = ((NodeList)name.getElementsByTagName(”roleName”));

for(int j=0;j<rolNodeList.getLength();j )

{

//有权限de角色de名称

//roleName = ((Element)rolNodeList.item(j)).getNodeValue();

roleName = ((Element)rolNodeList.item(j)).getAttribute(”name”);

theList.add(new ElementAndRole(elementName,roleName));

}

}

return theList;

}

//检查该角色是否有该页面元素de权限

private boolean isAuthentificated(String elementName)

{

String roleName = “”;

//在用户登陆时把该用户de角色保存到SESSION中,这里只是直接从SESSION中取用//户角色.

roleName=this.pageContext.getSession().getAttribute(”rolename”);

// roleList包含elementName属性为elementName,roleName属性为roleNamede//ElementAndRole对象,则该角色有该页面元素de权限

if(roleList.contains(new ElementAndRole(elementName,roleName)))

{

return true;

}

}

return false;

}

//表示角色和页面元素de对应de关系de内部类

class ElementAndRole{

String elementName;

String roleName;

public ElementAndRole(String elementName,String roleName)

{

this.elementName=elementName;

this.roleName=roleName;

}

public boolean equals(Object obj)

{

return(((ElementAndRole)obj).elementName.equals(this.elementName)&&((ElementAndRole)obj).roleName.equals(this.roleName));

}

}

}

在标签库能被JSP页面使用前,要做以下三个步骤

1、 JSP页面中包括一个taglib元素,确定需要加载到内存de标签库.前面deJSP文件de第一行:<%@ taglib uri=”http://mytag” prefix=”custTag” %>做de就是这件事.

2、 在配置文件web.xml中使用taglib元素确定TLD文件de位置.在web.xml中增加:

<taglib>

<taglib-uri>http://mytag</taglib-uri>

<taglib-location>

/WEB-INF/mytag.tld

</taglib-location>

</taglib>

3TLD文件必须使用taglib元素标识每个定制标签极其属性.

下面是使用这个标签库对应deTLD文件

<?xml version=”1.0″ encoding=”ISO-8859-1″ ?>

<!DOCTYPE taglib

PUBLIC “-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN”

“http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd”>

<taglib>

<tlibversion>1.0</tlibversion>

<jspversion>1.1</jspversion>

<shortname>myTag</shortname>

<uri/>

<tag>

<name>JspSecurity</name>

<tagclass>com.presentation.viewhelper.JspSecurityTag</tagclass>

<info>

JspSecurityTag

</info>

<attribute>

<name>elementName</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

</tag>

</taglib>

定制404错误页面,并发信给管理员的程序

星期一, 06月 2nd, 2008

如果您de用户找不到他要de页面,如何给他一个友好de答复,并且您也知道发生了这个错误呢,看看下面这段程序,是用来定制404错误页面和发通知给网管de好东西.—teaman翻译整理
<?php
# 设置 $domain 为您de域名 (注意没有www)
$domain = "oso.com.cn";
# 设置URL,注意没有后划线 /
$docroot = "http://www.oso.com.cn";
# 设置错误信息de字体
$fontface = "Verdana";
# 设置404页面de字体大小
$fontsize = "2";
# 设置404页面de背景颜色,缺省是白色
$bgcolor = "#ffffff";
# 设置文字颜色,缺省是黑色
$textcolor = "#000000";
# 使用 $reportlevel 变量来控制是否发信给网管
# 0 = 根本不用发信,嘿,teaman怎么会出错呢
# 1 = 只有在页面含有您deDOMAIN NAME时才发信
# 2 = 即使是与我连接出现de断连也发信,有可能是友情站点
$reportlevel = 2; //这种最保险了
$emailaddress = "webmaster@oso.com.cn"; //设置收错误信息de邮箱

function print_details()
{
# Request access to the global variables we need
global $fontface, $fontsize, $docroot, $REQUEST_URI, $reportlevel;
global $bgcolor, $textcolor;
# Print the 404 error in web format
echo "<html><head><title>404 没有找到页面</title></head>";
echo "<body bgcolor="$bgcolor" text="$textcolor">";
echo "<b><h1>404 对不起,我没有找到您要求de页面</h1></b>";
echo "<p><font face="$fontface" size="$fontsize">";
echo "奥索网管提醒您,您要求de页面 $docroot$REQUEST_URI, doesn’t exist";
echo " on this server.</font></p>";
if ($reportlevel != 0)
{
echo "<p><font face="$fontface" size="$fontsize">";
echo "错误信息已经发送到奥索网管手中.";
}
return;
}

# EMAIL处理函数
function send_email()
{
# Request access to the global variables we need
global $REQUEST_URI, $HTTP_REFERER, $emailaddress, $REMOTE_ADDR, $docroot;
# 定制发送de消息,如时间地点等.
$today = getdate();
$month = $today[mon];
$mday = $today[mday];
$year = $today[year];
$hours = $today[hours];
$minutes = $today[minutes];
$errortime = "$month/$mday/$year at $hours:$minutes";
# Create the body of the email message
$message .= "404 Error ReportnnA 404 error was encountered by $REMOTE_ADDR";
$message .= " on $errortime.nn";
$message .= "The URI which generated the error is: n$docroot$REQUEST_URInn";
$message .= "The referring page was:n$HTTP_REFERERnn";
# Send the mail message. This assumes mail() will work on your system!
mail("$emailaddress", "404 Error Report", $message, "From: $emailaddress"); //好,把信发出去
return;
}

# 下面这些是根据变量$reportlevelde设置来发信与否.
print_details();
# See whether or not we should send an email report. If so, do it.
if ($reportlevel != 0)
if ($reportlevel == 1) {
if (eregi($domain,$HTTP_REFERER))
send_email(); }
else
send_email();
# All done!
exit;
?>

用定制的PHP应用程序来获取Web服务器的状态信息

星期一, 06月 2nd, 2008

大多数网站托管(Web hosting)公司都支持客户对Web站点统计数据de访问,但是您往往会觉得服务器所产生de状态信息不够全面.例如,配置不正确deWeb服务器不能识别某些文件类型,这些类型de文件就不会出现在状态信息之中.幸好,您可以用PHP来定制状态信息收集程序,这样您就可以获取您所需要de信息了.


公共日志文件格式(Common Logfile Format,CLF)de结构


CLF最初是NCSA为HTTPd(全球网服务器软件)而设计de.CERN HTTPd是一个由万维网联盟(World Wide Web Consortium,W3C)维护de公共域Web服务器.W3C网站列出了该日志文件规范.基于微软和UNIXdeWeb服务器都可以生成CLF格式de日志文件.CLF格式如下:
Host IdentAuthuserTime_Stamp “request” Status_codeFile_size

例如:
21.53.48.83 - - [22/Apr/2002:22:19:12 -0500] “GET /cnet.gif HTTP/1.0″ 200 8237

下面是日志条目de细目分类:

Host是网站访问者deIP地址或者DNS名;在上面de例子中,它是21.53.48.83.
Ident是该访客de远端身份(RFC 931).破折号表明“未指定”.
Authuser是用户ID(如果Web服务器已经验证了验证网站访问者de身份de话).
Time_Stam是服务器以“日/月/年”这种格式返回de时间.
Request是网站访问者deHTTP请求,例如GET或者POST.
Status_Code是服务器所返回de状态相关代码,例如:200代表“正确——浏览器请求成功”.
File_Size是用户所请求文件de大小.在本例中,它为 8237字节.


服务器状态相关代码


您可以在HTTP标准中找到W3C所开发de服务器状态相关代码规范.这些由服务器所产生de状态相关代码表示了浏览器和服务器之间de数据传输成功与否.这些相关代码一般传递给浏览器(例如非常有名de404错误“页面没有找到“)或者添加到服务器日志中去.


收集数据

创建我de自定义应用程序de第一步就是获取用户数据.每当用户选择网站de某个资源时,我就希望创建一个对应de日志条目.幸好,服务器变量de存在使得我能够查询用户浏览器并获取数据.

报头中de服务器变量携带了从浏览器传递到服务器de信息.REMOTE_ADDR就是一个服务器变量de例子.这个变量返回了用户deIP地址:
例子输出:27.234.125.222

下面dePHP相关代码将显示出当前用户deIP地址:
<?php echo $_SERVER['REMOTE_ADDR']; ?>

让我看看我dePHP应用程序de相关代码.首先,我需要定义我想跟踪de网站资源并指定文件大小:
//获取我想记录de文件名称
$fileName=”cnet-banner.gif”;
$fileSize=”92292″;

您无需把这些值保存到静态变量中去.如果您要跟踪许多条目,那么您可以把它们保存到数组或者数据库中去.在这种情况下,您可能会希望通过一个外部链接来找到每个条目,如下所示:
<a href=”weblogger.php?bannerid=123″><imgsrc=”cnet-banner.gif” border=”0″></a>

其中“123”表示“cnet-banner.gif”所对应de记录.然后,我通过服务器变量来查询用户浏览器.这样我就得到在我de日志文件中添加新条目所需de数据:
//得到网站浏览者deCLF信息
$host=$_SERVER['REMOTE_ADDR'];
$ident=$_SERVER['REMOTE_IDENT'];
$auth=$_SERVER['REMOTE_USER'];
$timeStamp=date(”d/M/Y:H:i:s O”);
$reqType=$_SERVER['REQUEST_METHOD'];
$servProtocol=$_SERVER['SERVER_PROTOCOL'];
$statusCode=”200″;

然后,我检查服务器是否返回了空值(null).根据CLF规范,空值应该用破折号来代替.这样,下一个相关代码块de任务就是寻找空值并用破折号来取代它:
//给空值添加破折号(根据规范)
if ($host==”"){ $host=”-”; }
if ($ident==”"){ $ident=”-”; }
if ($auth==”"){ $auth=”-”; }
if ($reqType==”"){ $reqType=”-”; }
if ($servProtocol==”"){ $servProtocol=”-”; }

一旦我获取了必要de信息,这些值将被组织成一种符合CLF规范de格式:
//创建CLF格式de字符串
$clfString=$host.” “.$ident.” “.$auth.” [".$timeStamp."] \”".$reqType.” /”.$fileName.” “.$servProtocol.”\” “.$statusCode.” “.$fileSize.”\r\n”;

创建自定义日志文件
现在,格式化之后de数据可以存放到我de自定义日志文件中去.首先,我将创建一种文件命名协定,并编写每日产生一个新日志文件de方法(函数).在本文所举de例子中,每个文件都以“weblog-”开头,然后是按月/日/年表示de日期,文件扩展名为.log..log扩展名一般表示服务器日志文件.(实际上,绝大多数日志分析器都搜索.log文件.)
// 用当前日期来命名日志文件
$logPath=”./log/”;
$logFile=$logPath.”weblog-”.date(”mdy”).”.log”;

现在,我需要判断当前日志文件是否存在.如果存在,我就向它添加条目;否则,应用程序就创建新de日志文件.(新日志文件de创建一般发生在日期更改时,因为这时文件名发生变化了.)
//检查日志文件是否已经存在
if (file_exists($logFile)){
//如果存在,则打开已存在de日志文件
$fileWrite = fopen($logFile,”a”);}
else {
//否则,创建新de日志文件
$fileWrite = fopen($logFile,”w”); }

如果您在写或者追加文件时,收到“权限不足(Permission Denied)”错误信息,请更改目标日志文件夹de权限来允许写操作.绝大多数Web服务器de默认权限为“可读可执行”.您可以用CHMOD命令或者使用FTP客户端来改变文件夹de权限.

然后,我创建文件锁定机制,这样当两个或者更多用户同时访问日志文件时,只有其中de一个用户可以对该文件进行写操作:
//创建文件写操作de锁定机制
flock($fileWrite, LOCK_SH);

最后,我写入条目de内容:
//写CLF条目
fwrite($fileWrite,$clfString);
//解除文件锁定状态
flock($fileWrite, LOCK_UN);
//关闭日志文件
fclose($fileWrite);

处理日志数据


在该系统产品化之后,客户希望得到对所收集到de访问者数据de详细统计分析.由于所有de定制日志文件都是按照一个标准de格式组织de,因此任何一个日志分析器都可以处理它们.日志分析器是一个工具,它分析大de日志文件并产生饼图、直方图以及其它统计图形.日志分析器也用来收集数据,并综合出提供哪些用户访问您de网站、点击数等方面de信息.

下面列出了几个比较流行de日志分析器:

WebTrends是一个非常不错de日志分析器,它适用于大规模网站以及企业级de网络.
Analog是一个颇受欢迎de免费日志分析器.
Webalizer是一个免费de分析程序.它可以产生HTML报告,这样大多数网络浏览器都可以查看它de报告.

遵守标准

我可以轻松de扩展该应用程序来让它支持其它类型de日志记录.这样您就可以捕获到更多de数据,如浏览器类型以及referrer(referrer指得是链接到当前网页de前一个网页).这里de经验就是:在您编程de时候遵循标准或者惯例终究会简化工作.