一、概述
前面是扯犊子的,JSP/SERVLET基于HTTP规范,提供了几种安全支持,BASIC、DIGEST、FORM、CLIENT-CERT,本文主要介绍一下FORM的安全支持开发,文末有一个小demo用于展示。
因为FORM方式比较少用,因此本文介绍的原理点到即止,并没有特别特别深入,见谅。
更多详情,请参见《JSP/SERVLET核心编程》(新浪微盘,您找PDF的天堂···)
二、详细
1. 原理简述
FROM声明式安全基于表单验证。
简述:用户想要访问某一个受保护的资源,如果用户未登路,则将用户导向一个特定格式的表单进行身份验证,验证通过则继续访问资源;验证不通过,则将用户导向一个提示页面。
关键点:
a. 对受保护资源进行配置。本例是基于URL进行限制保护,因此需要在web.xml中配置受保护的URL;
b. 特定格式的表单写法。主要是三部分的固定写法,一是表单action必须为j_security_check,用户名输入input的name必须为j_username,用户密码的输入input的name必须为j_password;
c. 只要用户登录成功,并且支持cookie(会话cookie,跟session搭一块干活的那个cookie:JSESSIONID),那么这个用户名和密码就会被保存到当前用户的session中,后续只要用户的会话cookie存在并且服务器session未过期,则一直验证通过,否则验证失败,更多原理,请往下看;
FROM验证的安全性基于cookie/session机制的安全性,并且可以配置通过SSL传输,因此安全性还是比较高的,至少比BASIC的安全性高了不止一个等级。之前写的一个小demo,手动实现BASIC验证(不是基于JSP/SERVLET提供的封装了的BASIC,而是其底层原汁原味id实现):点击打开链接
d. 局限性。用户的身份验证,是基于服务器提供的用户角色,以tomcat为例,所有基于FORM的用户安全验证,都是基于tomcat user的安全验证。也就是说,能够通过用户验证的用户配置,都是属于tomcat_dir/conf/tomcat-user.xml中的<role rolename="xxx"/>配置和<user username="ooo" password="ooo" roles="xxx"/>。因此,基于表单验证FORM的局限性在于程序的可移植性,尽管代码不需要重写,但是不同的服务器的用户规则不一样,因此需要重新配置用户;
2. demo讲解
业务逻辑:首页index.jsp有一个超链,访问一个受保护的路径,如果用户未被验证,则自动跳转到认证页面,否则直接访问资源;如果认证失败,则自动跳转到一个失败提示页面。
整个FORM验证的所有核心在于配置文件web.xml的配置,因此在理解demo业务逻辑的基础上,本节的所有内容将围绕配置文件展开。
2.1 首页
即index.jsp,这个页面上有一个超链,用于访问受保护资源,内容如下所示。
<a href="${pageContext.request.contextPath}/gif">查看群相册</a>
2.2 配置文件
此时,因为超链的访问路径“/gif”是受保护的,因此会判断用户是否已经验证通过,判断的条件是根据会话cookie找到对应的session,看session里面是否已经存在j_username和j_password。下面分别来讲解。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>春田花花同学会</display-name>
<!--
一下配置都是基于FROM表单身份验证的角度来说明的
为了更好的阅读,请重下网上看配置文件,谢谢合作!
-->
<!--
和form-error-page不同的是,当在授权页面进行验证的时候,如果username是一个合法的认证用户
即username所在的role存在tomcat-users.xml中,但是这个role不具备访问该资源的权限,则会返回403,跳转到location指定的页面
而form-error-page是认证失败:1. 用户名或密码错误;2. username不存在tomcat-users.xml中
-->
<error-page>
<error-code>403</error-code>
<location>/security/fail.jsp</location>
</error-page>
<!--
login-config:
1. 当访问受限资源但是还未验证,则跳转到form-login-page页面,这个页面的编写需要固定的格式(理出为不变格式化的内容,其他可选自填),形如
<form action="j_security_check">
<input type="text" name="j_username"/>
<input type="password" name="j_password"/>
</form>
如果有密码,则表单最好是post提交
2. 当验证失败,即用户名和密码错误或用户不在授权列表中,则直接跳转到form-error-page指定页面
这个页面随便编写,目的是提示用户
-->
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/security/login.jsp</form-login-page>
<form-error-page>/security/logfail.jsp</form-error-page>
</form-login-config>
</login-config>
<!--
security-role:对应着tomcat-users.xml中的role配置
可选,有无均可以,配置主要是方便项目开发的人查看
-->
<security-role>
<role-name>user-auth</role-name>
</security-role>
<!-- 安全约束配置:
display-name:可选,表示受保护的资源名
web-resource-collection:必选,至少一个,配置受保护的资源
本处配置的受保护资源为:/gif
auth-constraint:授权约束,可选,role-name的value应该是在tomcat-users.xml中配置的<role rolename="xxx"/>
如果配置了auth-constraint,但是没有设置value,则表示任何人都不能访问
-->
<security-constraint>
<display-name>R_file</display-name>
<web-resource-collection>
<web-resource-name>aiyou_gif</web-resource-name>
<url-pattern>/gif</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user-auth</role-name>
</auth-constraint>
</security-constraint>
<!-- 首页,这个页面的超链请求到一个受保护的资源路径(/gif,即上文中security-constraint中配置),于是被拦截到一个登陆认证的页面 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
2.3 实现原理
查看form-lgoin-page页面的请求头和响应头,推测j_username和j_password是保存于session中,但是查看sessionScope是空的,未能进一步验证。但是根据HTTP的迹象来推测,应该是介样的,有大神能进一步解答,请不吝赐教!
General
Remote Address:[::1]:8080
Request URL:http://localhost:8080/web02/j_security_check
Request Method:POST
Status Code:302 Found
Response Headers
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location: http://localhost:8080/web02/gif
Content-Length: 0
Date: Tue, 16 Jun 2015 09:54:39 GMT
Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:36
Content-Type:application/x-www-form-urlencoded
Cookie:JSESSIONID=168CB17F9CF19F3615845B438332B7CD
Host:localhost:8080
Origin:http://localhost:8080
Referer:http://localhost:8080/web02/gif
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 Safari/537.36
Form Data
view parsed
j_username=wangxinyan&j_password=123
随即验证通过,进行/gif的资源访问,此时会设置新的会话cookie,实际上在访问授权页面的时候,也会生成新的会话cookie。
General
Remote Address:[::1]:8080
Request URL:http://localhost:8080/web02/gif
Request Method:GET
Status Code:200 OK
Response Headers
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=6F12E8122EB68186270DC626974E24FE; Path=/web02/; HttpOnly
Cache-Control: private
Expires: Thu, 01 Jan 1970 08:00:00 CST
Content-Type: text/html;charset=UTF-8
Content-Length: 306
Date: Tue, 16 Jun 2015 09:54:39 GMT
Request Headers
GET /web02/gif HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 Safari/537.36
Referer: http://localhost:8080/web02/gif
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=168CB17F9CF19F3615845B438332B7CD
基于Form的身份验证,是需要浏览器支持cookie,否则机制会瘫痪。但是整个验证的流程中HTTP透露的信息来看,并没有看到set-cookie除了会话cookie JSESSIONID之外的其他cookie,因此并不是将认证信息存放于cookie中;但是整个流程中都存在会话cookie,因此猜测FORM机制是将认证信息存放于session,每次访问cookie都会带着session id到浏览器,如果访问受保护资源,则浏览器通会过id找到session,session保存了认证信息,因此用户可以直接访问资源。
并且,为了辅助验证,做了session.invalidate();的测试,发现当session失效以后,必须要重新验证才能访问受保护的资源,因此可以确定验证信息是借助于会话cookie/session机制来实现的,因此其安全性依赖于session和会话cookie的安全性,如果需要进一步提高安全性,在security-constraint中还可以配置SSL进行数据传输,因此,这种验证方式还是比较安全的!
三、demo下载
示例demo百度云地址:点击打开链接
(该demo仅用于展示原理,其中还有很多不完善的地方,仅仅能作为一个参考,谢谢!)
附注:
参考资料:《JSP/SERVLET核心编程(第二版)》
本文如有错漏,烦请不吝指正,谢谢!