在简单的应用中会单独有自己一套用户存储和管理系统,但是在企业级应用中每个应用程序单独维护一套这样的系统会带来繁重的维护工作和应用扩展难度等问题。LDAP协议由于其标准性,相应的LDAP服务器可以作为企业级应用的用户存储和管理平台。这样,任何系统的所需的用户信息都在LDAP服务中,应用本身可以手动从中获取用户信息进行认证。不过,在J2EE规范中规定了web容器必须实现的认证和授权模块。也就是说,在以往的应用中,用户是否有权限登录进入系统访问相关资源需要应用本身来完成,而在J2EE规范下,这些内容可以交给容器去完成。J2EE提供了多种认证方式,下图展现了基于表单方式的认证过程。
上述的认证过程为基于表单的(Form-based)认证,从图中可以看出其有一个核心的类j_security_check,它是J2EE规定的一个servlet,所有web容器都对这个servlet进行了实现。当我们使用容器进行认证时,向服务器发送受保护资源请求时,web容器会检测请求是否具备相关权限,如果没有权限,web容器会将配置的登录页面发送回来进行登录信息确认,而后经过j_security_check servlet进行认证,认证成功则返回相关资源,认证失败则返回配置的错误页面。(这个过程仅展现了大意,远不能说明J2EE认证规范和单点登录内容)
使用基于表单的认证需要进行一下步骤:
1)配置web容器与某个安全域(security realm)进行配合工作,用于登录验证(多数企业应用的这块内容已经配置完毕)
2)建立登录页面
3)建立认证失败页面
4)配置应用为web容器认证及认证页面和认证失败页面
其中对于第三点所说的页面没有任何要求,但是登录页有些要求必须遵守。表单的action需要指定到j_security_check上,用户名标签Id为j_username,密码标签id为j_password。登录页面核心代码如下所示:
<form action="j_security_check" method="post">
<div>
<input type="hidden" id="hide" />
<input type="text" id="j_username" name="j_username"
class="nameClass" /> <input type="password" id="j_password"
name="j_password" class="passwordClass" />
<input type="submit" value="登录" class="btn2"/>
<input type="reset" value="重置" onClick="resetBtn_click()" class="btn1" />
</div>
</form>
认证失败后web容器会将返回错误页,对错误页的编写没有任何限定。一般仍旧具备登录表单的内容,但多出认证失败的提示信息。WAS上的不同的应用其登录页和认证失败页不同,每种应用受保护和不受保护的资源也不相同,这些信息配置在web.xml上,如下所示:
<security-constraint>
<display-name>SecurityConstraint</display-name>
<web-resource-collection>
<web-resource-name>SecurityConstraint</web-resource-name>
<url-pattern>/</url-pattern>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>All Authenticated</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<display-name>unSecurityConstraint</display-name>
<web-resource-collection>
<web-resource-name>unSecurityConstraint</web-resource-name>
<url-pattern>/index.jsp</url-pattern>
<url-pattern>/login.jsp</url-pattern>
<url-pattern>/WorkplaceFx.html</url-pattern>
<url-pattern>/lotusforms/*</url-pattern>
<url-pattern>/assets/*</url-pattern>
<url-pattern>/css/*</url-pattern>
<url-pattern>/com/*</url-pattern>
<url-pattern>/cache/*</url-pattern>
<url-pattern>/history/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>Everyone</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/index.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>All Authenticated</role-name>
</security-role>
<security-role>
<role-name>Everyone</role-name>
</security-role>
在上述代码中的两组security-constraint标签内分别定义了受保护资源和非受保护资源,在login-config标签中定义了该应用所采取的认证方式及认证的登录页和认证失败页面。
上文中提到过使用基于表单的认证需要四个步骤,前面已经简单描述了登陆页、错误页以及相关配置(受保护资源、不受保护资源、登陆页和错误页、认证方式等)的内容,那么应用程序服务器如何才能知道向什么地方去进行认证呢。在此假设应用程序服务器使用的是WebSphere,用户存储和管理平台使用的是LDAP服务器。下面以对WAS6.1及以后版本的“联合存储库”添加LDAP存储库为例简述配置WAS的认证库和启动过程。以下操作在WAS控制台进行。
1) 安全性 > 安全管理、应用程序和基础结构 > 联合存储库 > 管理存储库,选择按钮“添加”新的外部存储库。
2)在接下来的“新建”页面中录入如下数据:
数据库标识:选取合适的容易说明即可,可以是服务器主机名称、英文说明,都可以。
目录类型:选择 IBM Tivoli Directory Server 6,也可以是 Domino、微软活动目录等等。
主要主机名:主目录服务器主机名。
故障转移主机名:当主目录服务器出故障 out of service 的时候,WAS V6.1 会自动切换使用备份的目录服务器。
登录属性:缺省是 uid,如果你想让用户能够以其他 LDAP 属性登录,比如用户可以用 uid 或者 cn 或者 email 都能登录,那么你可以录入 uid;cn;email。
支持到其它 LDAP 服务器的指向信息:LDAP 服务器的数据可以通过 referral 指向其他 LDAP 服务器。
需要 SSL 通信:配置 WAS 和 LDAP 服务器之间的通信要走 SSL。
证书映射:如果 J2EE Web 应用使能 SSL 证书登录,那么 WAS 缺省会将客户浏览器提供的 SSL 证书中的 Subject 提取出来,在 LDAP 服务器中和 inetOrgPerson 类对象的 DN 属性进行比对,如果相同,那么这个人的证书认证就算通过了,用户的身份就是其个人对象 DN 属性和 SSL 证书 Subject 相同的用户。
3)新建了 LDAP 外部存储库以后,这个库还没有真正被“联合存储库”引用,仍需要做一个简单的引用管理配置。选择安全性 > 安全管理、应用程序和基础结构 > 联合存储库,选择按钮“将基本条目添加至域”。
4)配置完毕“联合存储库”以后,还需要分别“启用管理安全性”和“启用应用程序安全性”。
所有配置完成后,联合存储库中可能已经配置了多个用户信息存储库,WAS停止和重启后,在访问J2EE web应用时,WAS会在包括刚配置的存储库在内的所有存储库查找用户信息进行认证。
上面的内容可以说都是J2EE规范中的内容,只是针对不同的容器其具体实现有些差异。对于用户来讲有登录过程就会有注销过程,“注销”在J2EE规范中是没有规定的,我们可以采取其他方式来达到注销的目的。
在J2EE中规定了j_security_check servlet来进行登录认证,虽然没有规定注销的servlet但是在WAS中定义了这样的servlet,即ibm_security_logout,其使用过程类似前面讲的j_security_check的使用,代码展示如下所示:
<form method="post" action="ibm_security_logout" name=”logout”>
<input type="submit" name="logout" value="Logout">
<!—logoutExitPage 标识着在注销之后应用会转向的页面-->
<input type="hidden" name="logoutExitPage" value="/login.jsp">
</form>
对于多数应用程序服务器并没有实现这样的servlet,但我们可以手动模仿WAS的注销过程,原理是先获取HttpSession,而后再使session失效,大致代码如下所示:
HttpSession session = request.getSession ();
Session.invalidate ();
这样我们的登录体现大致可以合格了,不过在认证前后我们可能会做一些其他事情,比如记录日志等等,由于J2EE规范中的认证过程,其原理就是将用户信息提交到一个被web容器实现了的servlet——j_security_check——上进行验证(但这个servlet的地址不能被我们手工使用,它只能像前面的代码在form的action中使用),所以我们可以像日常访问java类一样在其访问时加入过滤器filter。
Filter代码:
package com.chwd;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class LoginFilter implements Filter {
private String initParameter = "";
public void init(FilterConfig filterConfig) throws ServletException {
//获取初始化参数
//这个参数是通过web.xml中相关filter标签中的init-param进行配置的
//在WAS认证前后的处理中可能使用到此参数
initParameter = filterConfig.getInitParameter("initParameter");
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
//WAS认证之前要做的处理可以在这个地方进行
//......
//进行J2EE规范中的WAS实现的j_security_check认证
filterChain.doFilter(request, response);
//WAS认证后的处理可以在这个地方进行
//......
}
public void destroy() {
}
}
上面代码中能够看出FilterConfig类具备获取初始化参数的能力,参数在web.xml中进行配置,在Filter的配置代码中注意观察其url-pattern为/j_security_check,也就是处理验证过程的servlet,虽然这个地址不能被我们手动使用(解释之一:不能将用户名和密码手动传给这个servlet,只能使用表单提交的方式),但我们可以使用filter简介加入自己的处理。 配置代码如下所示:
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>com.chwd.LoginFilter</filter-class>
<init-param>
<param-name>initParameter</param-name>
<param-value>WAS认证前后处理所需参数</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/j_security_check</url-pattern>
</filter-mapping>
至此本文简单讲述了J2EE安全体系基于表单的认证机制,并通过实例代码讲述了完成表单认证所需要的步骤,此外本文还简述了配置WAS将LDAP作为其用户存储库的过程以及与登录认证项配合的使用的注销处理与登录过滤器(Filter)的使用。这仅仅是J2EE安全系统机制中的一种,针对不同应用程序服务器其实现与操作细节也不尽相同,但此文引导着向J2EE的迈进。