Model one
Servlet技术规范规定了两种由容器实现的Java Web应用程序的安全模型。它们分别是:声明性安全模型和程序性安全模型。
程序性安全模型是指可以在部署的时候由部署者为WEB资源配置安全限制。如:将用户放入组中(后面称之为角色role),为特定资源配置身份验证。Web应用程序的部署描述符文件(web.xml文件)就是定义这些操作的位置。程序性安全模型指的是由程序开发者在代码中使用特定的方法来限制某些资源的访问。如果安全规则发生变化之后,那么就需要重新编写代码和重新编译,所以只有在绝对必要的情况下才使用这种安全模型。
下面我用Tomcat5.5.9容器,通过一个例子程序给大家介绍一下以上两种模型。例子环境如下:
Web根目录名称:SecurityWeb
有如下JSP页面:addPet.jsp 增加宠物信息页面和增加兽医信息页面
modifyVet.jsp 修改宠物信息页面和修改兽医信息页面
searchVet.jsp 查询宠物信息页面和查询兽医信息页面
诊所科室三个:挂号室(能查询、添加宠物和兽医信息,但不能修改宠物和兽医信息),档案室(能查询、修改宠物和兽医信息,但不能添加宠物和兽医信息),所长室(能查询、添加、修改宠物和兽医信息)
以上三个科室用拼音表示分别为三个角色:
Guahaoshi 角色有用户: gua1 gua2
Danganshi 角色有用户:dang1 dang2
Suozhangshi 角色有用户:suo1 suo2
建立web应用程序大家自己建立,我就不多说了,但是强调一下,在使用JBuilder的时候,可能会遇到一些麻烦,可能需要你单独部署web应用程序或者更改启动参数才能看到效果。
在web.xml文件中我们做如下的配置: <!--哪些web资源需要保护,需要的身份验证类型,及哪些角色可以访问 -->
<security-constraint>
<!--给该安全约束起一个名字,GUI工具可以使用-->
<display-name>add</display-name>
<web-resource-collection>
<!--给被保护的资源起一个名字-->
<web-resource-name>add Resource</web-resource-name>
<!--被保护的资源-->
<url-pattern>/addPet.jsp</url-pattern>
<url-pattern>/addVet.jsp</url-pattern>
<!--被保护的方法,没有设置为全部方法-->
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<!--授予权限,哪些角色可以访问-->
<auth-constraint>
<role-name>guahaoshi</role-name>
<role-name>suozhangshi</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<display-name>search</display-name>
<web-resource-collection>
<web-resource-name>search Resource</web-resource-name>
<url-pattern>/searchPet.jsp</url-pattern>
<url-pattern>/searchVet.jsp</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>guahaoshi</role-name>
<role-name>suozhangshi</role-name>
<role-name>danganshi</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<!--被保护资源的验证方法,共四种,后面讲述其它三种-->
<auth-method>BASIC</auth-method>
<realm-name>Welcome to Pet hospital</realm-name>
</login-config>
配置好这些还不行,我们还要定义角色及哪些用户属于该角色。默认位置是${CATALINA_HOME}conf \tomcat-user.xml文件,其中${CATALINA_HOME}表示tomcat根目录。在该文件中加入如下信息:<!--定义角色-->
<role rolename="suozhangshi"/>
<role rolename="guahaoshi"/>
<role rolename="danganshi"/>
<!--定义用户,并授予角色中-->
<user username="gua1" password="gua1" roles="guahaoshi"/>
<user username="gua2" password="gua2" roles="guahaoshi"/>
<user username="dang1" password="dang1" roles="danganshi"/>
<user username="dang2" password="dang2" roles="danganshi"/>
<user username="suo1" password="suo1" roles="suozhangshi"/>
<user username="suo2" password="suo2" roles="suozhangshi"/> 部署应用程序,然后启动服务器,在浏览器的地址栏中键入如下的网址: http://localhost:8080/SecurityWeb/addPet.jsp后,可以看到如下图所示:
输入用户名和密码,只有被授权的用户可以访问。至于哪些用户能够访问addPet.jsp页面,我想答案应该是“上帝才知道”。
Model two
上文且说到tomcat-users.xml文件,这个文件中的内容是不依赖于任何一个web应用程序,所以在任何一个web程序中都可以使用,如果这些用户和角色只对你的应用起作用,那么你完全可以放置在你自己的应用程序中,比如WEB-INF中.下面我们将上次加入到tomcat-users.xml文件中的内容, 加入到我们在WEB-INF下新建的myUser.xml文件中,注意此文件应该是以<tomcat-users>为根目录,如下所示:
<tomcat-users>
<!--定义角色-->
<role rolename="suozhangshi"/>
<role rolename="guahaoshi"/>
<role rolename="danganshi"/>
<!--定义用户,并加入到角色中-->
<user username="gua1" password="gua1" roles="guahaoshi"/>
<user username="gua2" password="gua2" roles="guahaoshi"/>
<user username="dang1" password="dang1" roles="danganshi"/>
<user username="dang2" password="dang2" roles="danganshi"/>
<user username="suo1" password="suo1" roles="suozhangshi"/>
<user username="suo2" password="suo2" roles="suozhangshi"/>
</tomcat-users>
这个文件已经建立好了,那么怎么告诉Tomcat加载这个文件呢?我们通过下面这一种方式,在你的Web应用程序中的META-INF文件夹中加入Context.xml 文件,这样当此应用程序部署的时候,就会加载该文件的内容,内容如下:
<Context>
<Realm className="org.apache.catalina.realm.MemoryRealm"
pathname="webapps/SecurityWeb/WEB-INF/myUsers.xml" />
</Context>
Realm标签中className属性定义了使用MemoryRealm类从pathname处加载xml文件。关于Realm的详细信息,我们今后找专题研究。还要注意这里的pathname它是以相对Tomcat根目录的,所以上文件路径应该从webapps开始。最后重新启动服务器再访问你程序,应该能够看到和上次相同的效果。
Module three
上文中myUsers.xml文件的密码都是以明文的方式存放的,这样看起来不是十分的安全,我们可以采用加密的方式来进行保存。本例我们采用MD5算法。
说道MD5算法我们要稍稍多说一点,MD5其实是一种Hash函数,那么什么是hash函数呢?hash函数要做的就是给定一个输入然后计算出一个输出结果,如果输入不同那么结果应该不同的。MD5算法就是一种常用的hash函数,通过MD5算法我们可以给一个字符串计算出一个值,我们把这个值就成为摘要值。如:字符串“coresun”计算出的摘要值是“2dfb75bf781f454fa2e02048d8e23bd3”。理想情况下一个hash函数所采用的算法是只能够通过输入计算出摘要值,但是通过摘要值是不能够推断出原始输入的,其实MD5就是一个非常不错的算法。但是这一点我们大家应该记住,2004年山东大学的王小云教授已将此算法破解,令全世界震惊,大涨国人志气。
那么我们怎么为一个字符串生成它的摘要值呢?在tomcat中有一个类org.apache.catalina.realm.RealmBase这个类在${CATALINA_HOME}serverlibcatalina.jar中,应用此类还要用到一个通用日志类org.apache.commons.logging.LogFactory,此类在commons-logging-1.1.jar中,可以到apache的网站上下载。http://commons.apache.org/logging/,准备好两个jar包之后,将两个jar包放在一个文件下如(D:lib),然后按如下操作:
1.设置path环境变量,也就是java命令所在的命令,这个不用我多说了吧。
2.设置classpath环境变量,如 set classpath=D:catalina.jar;commons-logging-1.1.jar
3.通过下列命令生成coresun字符串的摘要值:java org.apache.catalina.realm.RealmBase -a MD5 coresun
稍等片刻,就会看到coresun:2dfb75bf781f454fa2e02048d8e23bd3
按照同样的方式将myUsers.xml文件中的密码都通过此方式计算摘要值。计算完之后用摘要值替换原始值,文件内容如下:
<tomcat-users>
<role rolename="suozhangshi"/>
<role rolename="guahaoshi"/>
<role rolename="danganshi"/>
<user username="gua2" password="7af209faf16bf686ece05b75f9131080" roles="guahaoshi"/>
<user username="dang2" password="908aa608050f6a7a7caf0c44c295845b" roles="danganshi"/>
<user username="suo1" password="f5dc9fddbe2dc1fd2389bc16e096fd51" roles="suozhangshi"/>
<user username="dang1" password="9bc8a8e63dce56f669b0247a47742ca8" roles="danganshi"/>
<user username="suo2" password="09d063a79ac6aba2290fcb8e31dd4c81" roles="suozhangshi"/>
<user username="gua1" password="f4adbb43c78e73a9dcf0dc1a8bb8bb93" roles="guahaoshi"/>
</tomcat-users>
更改完之后,再将META-INF文件夹下的Context.xml 文件填入下列内容,
<Context>
<Realm className="org.apache.catalina.realm.MemoryRealm"
pathname="webapps/SecurityWeb/WEB-INF/myDigestUsers.xml"
digest="MD5" />
</Context>
记住web.xml文件的内容不需要更改。重新部署并重新启动服务器,效果可以是一样的。
Module four
验证方法我们前面已经提到了,一共有四种分别是BASIC,DIGEST,FORM,CLIENT-CERT。下面我们分别来介绍一下:
1、BASIC验证
这种验证方式是比较流行的。当你访问受保护的资源时会弹出一个对话框,提示你输入用户名和口令。用户名以明文的方式传送,而密码则采用一种很容易实现的Base64编码。由于Base64容易实现,所以这种验证方法的安全性最低。
2、DIGEST验证
这种验证是将口令以加密的数字摘要进行传递,使用单向散列算法,在服务器端预先存放经过散列处理的口令,与传送过来的进行对比。由于在实际传输的时候传送的是摘要值,而它又是经过单向散列得来的,所谓单向散列就是通过摘要值是很难推断出原始值的,所以这种验证方式要比BASIC验证方式安全。
3、FORM验证
这种验证是采用自定义的一个登录和错误页面来处理验证。登录页面就是一个普通的表单,采用POST方式传送,所以这种验证方式安全性也比较低。下面通过一个简单的实例来了解一下怎样使用这种验证方式,如下代码:
更改后的web.xml文件
<login-config>
<auth-method>FORM</auth-method>
<realm-name>Welcome to Pet hospital</realm-name>
<form-login-config>
<form-login-page>/Login.jsp</form-login-page>
<form-error-page>/Error.jsp</form-error-page>
</form-login-config>
</login-config>
登录页面Login.jsp如下:
<form action="j_security_check" method="post">
userName:<input type="text" name="j_username"><br>
password:<input type="text" name="j_password"><br>
<input type="submit" value="authenticate">
</form>
注意这里的action值应该是j_security_check,用户名是j_username,口令是j_password.
错误页面没有什么特别的,随便写一些提示信息就可以了。重新部署和启动服务器测试一下效果,看是不是已经调用了你的登录页面。
4、CLIENT-CERT验证
数字签名
刚刚我们已经知道了如果消息改变了,那么改变后的消息与原消息的消息摘要一定是不同的。我们将消息和消息摘要分别传送,如果消息摘要没有被截获那么我们还是可以知道消息是否被更改了,但是如果消息和消息摘要都被截获的话,那么我们也不会知道消息是否被篡改了。下面我们看一下数字签名如何解决这个问题。
为了了解数字签名的工作原理,我们有必要先了解一下公共密钥加密技术。公共密钥加密技术是基于公共密钥和私有密钥这两个概念的。它的设计思想是你可以将公共密钥告诉世界上的任何人,但是只有你才拥有私有密钥,最重要的是你一定要保护好你的私有密钥,不要将它告诉任何人。公共密钥和私有密钥是有数学关系的,那你可能会担心,很多人都知道我的公共密钥,他们根据公钥密钥会不会推断出我的私有密钥呢?你的担心是有道理的,但是你大可放心,几乎是不可能的?这似乎令人难以相信,但直至今日,还没有人能够找到一种能够通过公共密钥来推断私有密钥的方法。所以在目前看来这种技术是绝对安全的,应该完全去信任它。那么我们怎么去使用这两个密钥呢?非常简单,如果消息用公共密钥去加密,那么只能通过私有密钥去解密。如果消息用私有密钥去加密,那么只能通过公有密钥去解密。看起来非常简单,的确它就是这么简单。基于这两句话,公共密钥加密技术有两个非常有用的应用:
1、数据加密
如果某人给你发送数据,他用你的公共密钥加密数据,那么这个数据就只有你能够看得懂,因为只有你才拥有私有密钥。其他人根本无法解密数据。
2、数据认证
如果你用私有密钥加密一个数据,那么任何持有你公有密钥的人都可以解密数据,当然这个时候并不是为了保密数据,而是为了证明这个数据是不是你发送的。你想想看,如果某人用你的公共密钥解开了数据,他就会知道这个数据一定是你发送的,因为通过其他人的公共密钥是不可能解开数据的。
通过上面的介绍你应该知道了公共密钥加密技术的用途了,那么怎么产生公共密钥和私有密钥呢?有很多的算法,其中比较有名的是RSA和DSA。
好了,现在我们就可以通过使用公共密钥加密技术的数字签名来彻底解决上面的问题了,我们还是用刚才的字母来举例:
1) 你的朋友通过某种软件产生一对密钥(公共密钥和私有密钥)。
2)然后你的朋友和你联系,将他的公共密钥告诉你和其他的人。
3)通过产生的私有密钥对A加密,产生加密数据,这个加密的数据我们就称之为数字签名,我们用AS表示。
4)你的朋友将A和AS发送给你,如果发送过程中被窃取了,窃取者将A改为了 B,现在戏剧性的事情发生了,我们来看看这个窃取者怎么伪造数字签名?数字签名是用私有密钥来加密数据产生的,这个窃取者根本就没有你朋友的私有密钥,因为私有密钥没有在网络上传送。如果窃取者也通过一个软件产生了一对密钥,然后用他的私有密钥对B加密产生了新的签名BS,然后他将B和BS一起发送给你。
5)这时我们就能够完全判断出这个数据的真伪了。怎么办?很简单,我们用你朋友的公共密钥去解密数据?结果呢?不能解密?因为这个数据是用窃取者的私有密钥加密的,要解密当然得使用窃取者的公共密钥了。
6)如果A和AS没有被窃取,那么你用你朋友的公共密钥解密,应该是能够顺利解密的,如果不能解密,那么数据肯定是已经被修改了。
通过上面的说明大家应该知道,要想安全的传输数据,其实也并不是什么难的事情,所以我们应该大胆的去使用数字签名。