??xml version="1.0" encoding="utf-8" standalone="yes"?> 首先必须要通过一个Action再{向那个添加记录的面,转向函数如下. <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> 2.来源http://www2.cnblogs.com/snoopy/articles/54699.html
Struts的TokenQo牌)机制能够很好的解册单重复提交的问题Q基本原理是Q服务器端在处理到达的请求之前,会将h中包含的令牌?/em>与保存在当前用户会话中的令牌D行比较,看是否匹配。在处理完该h后,且在{复发送给客户端之前,会产生一个新的o牌,该o牌除传给客户端以外,也会用户会话中保存的旧的o牌进行替换。这样如果用户回退到刚才的提交面q再ơ提交的话,客户端传q来的o牌就和服务器端的令牌不一_从而有效地防止了重复提交的发生?/p>
q时其实也就是两点,W一Q你需要在h中有q个令牌|h中的令牌值如何保存,其实和我们qx在页面中保存一些信息是一LQ通过隐藏字段来保存,保存的Ş式如Q?
〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN"
value="6aa35341f25184fd996c4c918255c3ae"〉,q个value是TokenProcessorcM的generateToken()获得的,是根据当前用Lsession
id和当前时间的long值来计算的。第二:在客L提交后,我们要根据判断在h中包含的值是否和服务器的令牌一_因ؓ服务器每ơ提交都会生成新的TokenQ所以,如果是重复提交,客户端的Token值和服务器端的Token值就会不一致。下面就以在数据库中插入一条数据来说明如何防止重复提交?/p>
在Action中的addҎ中,我们需要将Token值明的要求保存在页面中Q只需增加一条语句:saveToken(request);Q如下所C:
说明Q在前一个{向提交信息的面需?span style="color: #ff0000; ">saveToken(request); 在保存页面?/font>
if(!isTokenValid(request)) { //重复提交 System.out.println("重复提交"); System.out.println("能得复提?!!"); } else { request.setAttribute("saveInfo", info); logger.debug("save successful"); resetToken(request); //删除session中的令牌 ward=mapping.findForward("notice")Q?/p>
} public ActionForward userSave(ActionMapping mapping, ActionForm form, String path = mapping.findForward("delete").getPath(); //TODO: d本Action所有的h参数Q将path重新构造,加上h参数 ActionForward forward= new ActionForward(path+"&pageId=1"); package com.gpdi.softevaluate.action; import java.io.PrintWriter; import java.util.Iterator; import javax.servlet.http.HttpServletRequest; public class ProjectSetAction extends DispatchAction /** } //ActionForward forward= new ActionForward("project/projectSet.do?do=add&lotId=6"); Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1621900
public ActionForward tokenTest(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
saveToken(request);//把一个token ID保存到Session,q在且要转到的页?br />
//?lt;html:form>中添加一?lt;input type="hideen">的标{?
return mapping.findForward("add");
}
一个输出入面如容如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>
<%@ taglib uri="http://struts.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html:html lang="true">
<head>
<html:base />
<title>tokentest.jsp</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<br>
<logic:present name="error">
<pre style="color:#ff2255"><bean:write name="error"/></pre>
</logic:present>
<center>
<html:form action="/insert.do" method="post">
<table border="0" cellspacing="0" >
<tr>
<td width="30%">用户?lt;/td>
<td width="70%"><html:text property="username"/></td>
</tr>
<tr>
<td>地址:</td>
<td><html:text property="address"/></td>
</tr>
<tr>
<td colspan="2"><html:submit value="提交"/></td>
</tr>
</table>
</html:form>
</center>
</body>
</html:html>
面的处理Action内容如下:
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
TokenTestForm tokenTestForm = (TokenTestForm) form;
if(!isTokenValid(request)){ //重复提交
request.setAttribute("error","不能得复提交!!!");
//saveToken(request); 重新生成tokenid,
return mapping.findForward("return");
}else{
resetToken(request);
}
//执行相关操作
System.out.println(tokenTestForm.getUsername()+"--"+tokenTestForm.getAddress());
return mapping.findForward("ok");
}
x已完?至于原理,p己去查一些资料就完全明白?...
使用Struts的Token机制解决表单的重复提?/a>
前几天被q个问题困扰了,在Google中搜“表单重复提交”Q也搜到不少资料Q但有的讲的不是很清楚,所以走了些弯\Q现在写下来Q不能算原创吧?/h3>
public ActionForward add(ActionMapping mapping, ActionForm
form,
HttpServletRequest request, HttpServletResponse
response)
//前面的处理省?br />
saveToken(request);
return
mapping.findForward("add");
}在Action的insertҎ中,我们Ҏ表单中的Tokeng服务器端的Token值比较,如下所C:
public ActionForward insert(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
if
(isTokenValid(request, true)) {
// 表单不是重复提交
//q里是保存数据的代码
} else
{
//表单重复提交
saveToken(request);
//其它的处理代?br />
}
}
]]>
HttpServletRequest request, HttpServletResponse response) {
UserForm userForm = (UserForm) form;
return mapping.findForward("userSave");
}
HttpServletRequest request, HttpServletResponse response) {
UserForm userForm = (UserForm) form;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
forward.setRedirect(true);
//传参数后q回
return forward;
// return mapping.findForward("userSave");
}
]]>
name:指的是从上一个页面或者action里面传过来的变量Q可以是Mcd?例如再上一|页定义request.setAttribute("requestName", requestName);)
例如Q?
我有一个Bean对象User对象Q这个里面保存了admin理员的个h信息Q姓?name)Q性别(sex)、年?age)那么怎么用呢?
在action里面你可以把User对象传过?
request.setAttribute("user",User);
在页?
<bean:define id="admin" name="user"./>
然后可以和bean:write搭配使用
<bean:write name="admin" property="name"/>
<bean:write name="admin" property="sex"/>
<bean:write name="admin" property="age"/>
]]>
var flagvalue=0;
var rest="";
var resta="";
//实现AJAX验证
var http_request = false;
var infoForm_flag=false;
var lotId="";
function Save()
{
var infoForm=document.projectSetForm;
infoForm_flag=infoForm;
lotId=infoForm.lotId.value;
//表名U?br />
var dc = infoForm.tablename.value;
if(dc==""||dc==null){
alert("表名UC能ؓI?");
infoForm.tablename.focus();
infoForm.tablename.select();
return false;
}
function send_request(url)
{//初始化、指定处理函数、发送请求的函数
http_request = false;
//开始初始化XMLHttpRequest对象
if(window.XMLHttpRequest)
{ //Mozilla 览?br />
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType)
{//讄MiMEcd
http_request.overrideMimeType('text/xml');
}
}
else if (window.ActiveXObject)
{ // IE览?br />
try
{
http_request = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
try
{
http_request = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e)
{
}
}
}
if (!http_request) { // 异常Q创建对象实例失?br />
window.alert("不能创徏XMLHttpRequest对象实例.");
return false;
}
http_request.onreadystatechange = processRequest;
// 定发送请求的方式和URL以及是否同步执行下段代码
http_request.open("GET", url, true);
http_request.send(null);
}
//处理q回信息的函?br />
function processRequest()
{
if (http_request.readyState == 4)
{
//判断对象状?br />
if (http_request.status == 200)
{
//信息已经成功q回Q开始处理信?br />
var str=http_request.responseText;//获得从服务器q回的文本信?br />
if(str=="no")
{
alert("您选择了模型分属不同的模板,请选择同一模板对应是模?");
//return false;
infoForm_flag.action="projectSet.do?do=add&lotId="+lotId;
infoForm_flag.submit();
}
else
{
infoForm_flag.action="projectSet.do?do=creattable&save=-1";
infoForm_flag.submit();
}
} else
{
//面不正?br />
alert("您所h的页面有异常?);
}
}
}
function userCheck()
{
send_request('projectSet.do?do=check&rest='+rest);
}
</script>
2.ProjectSetAction.java
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
{
* <p>模板验证</p>
* @param mapping
* @param form
* @param request
* @param response
* @return
* @throws Exception
*/
public ActionForward check(ActionMapping mapping, ActionForm
form,HttpServletRequest request, HttpServletResponse response)throws
Exception
{
String rest="";
String lotId="";
String retn="";
String flag="";
CommDaoFactory mgObj = new CommDaoFactory();
try
{
rest=request.getParameter("rest");
lotId=request.getParameter("lotId");
}
catch(Exception e)
{
}
if(!rest.equals("0")||!rest.equals("")||rest!=null)
{
flag="no";
}
else
{
flag="ok";
PrintWriter out=response.getWriter();
//服务器返回信?br />
out.write(flag);
out.close();
//d本Action所有的h参数Q将path重新构造,加上h参数
//forward.setRedirect(true);
//传参数后q回
//return forward;
return mapping.findForward(null);
}
public
ActionForward creattable(ActionMapping mapping, ActionForm
form,HttpServletRequest request, HttpServletResponse response)throws
Exception
{
//实现功能代码
return mapping.findForward("create");
}
}
3.struts.config.xml
<action attribute="projectSetForm" name="projectSetForm"
parameter="do" path="/project/projectSet" scope="request"
type="com.gpdi.softevaluate.action.ProjectSetAction" validate="false">
<forward name="list" path="/project/viewProject.jsp" />
<forward name="add" path="/project/addProject.jsp" />
<forward name="create" path="/project/createTable.jsp" />
</action>
]]>
https://dwr.dev.java.net/files/documents/2427/47504/dwr.jar
2.安裝DWR,把dwr.jar攑ֈWEB-INF/lib?
web.xml中加入DWRServlet & ActionServlet
其中<load-on-startup>的部分要特別注意,ActionServlet要先初始?所以數字要比較?
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet
</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
dwr.xml中加入struts的設?其中formBean的參數的value?會對應到struts-config.xml?lt;form-beans>的設?<dwr>
<allow>
<create creator="struts" javascript="testFrm">
<param name="formBean" value="testActionForm"/>
</create>
</allow>
</dwr>
struts-config.xml
<struts-config>
<form-beans>
<form-bean name="testActionForm" type="test.struts.testActionForm" />
</form-beans>
<action-mappings>
<action name="testActionForm" path="/testAction" scope="session" type="test.struts.testAction" validate="false">
<forward name="display" path="/display.jsp" />
</action>
</action-mappings>
<message-resources parameter="ApplicationResources" />
</struts-config>
testActionForm.java,getDate()會透過dwr,取得珑֜最新的日期
package test.struts;
import org.apache.struts.action.*;
import java.util.*;
public class testActionForm extends ActionForm {
private String strDate;
public void setStrDate(String strDate) {
this.strDate = strDate;
}
public String getStrDate() {
return strDate;
}
//dwr public String getDate() {
Date date = new Date();
return date.toString();
}
}
testAction.java
package test.struts;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.Action;
import org.apache.struts.action.*;
public class testAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
testActionForm actionForm = (testActionForm) form;
System.out.println(actionForm.getStrDate());
return mapping.findForward("display");
}
}
date.jsp,在form的部?請用struts ?tag library,我把<html:text property="strDate" size="30" >Ҏ<input type="text" name="strDate">?無法正常的接受到?
<%@ page contentType="text/html; charset=Big5" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html><head>
<title>title</title>
<script type='text/javascript' src='dwr/interface/testFrm.js'></script>
<script type='text/javascript' src='dwr/engine.js'></script>
<script type='text/javascript' src='dwr/util.js'></script>
</head>
<SCRIPT LANGUAGE="JavaScript" type="">
function refreshDate() {
testFrm.getDate(populateDate)
;}
function populateDate(data){
DWRUtil.setValue('strDate', data);
}
</script>
<body>
<html:form action="testAction.do">
dateQ?lt;html:text property="strDate" size="30" ></html:text>
<input type="button" onclick="refreshDate();" value="更新日期"/><br/>
<html:submit>送出 </html:submit>
</html:form></body></html>
display.jsp
<%@ page contentType="text/html; charset=Big5" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
%@page import="test.struts.*"%
<html>
<head>
<title>test</title>
</head><body bgcolor="#ffffff"><h1>您送出的日?<br>
<bean:write name="testActionForm" property="strDate"/></h1>
</body>
</html>
]]>
前期的系l创建、struts、hibernate、spring的集成工作就不用说了Q一路下?#8230;………
主要?span class="hilite1">DWR的应用,它用于表单验证:
1、先看它的配|:
Q?Q在web.xml里加上:
Q?Q再Z个类Q用于获取验证输Z息的属性文Ӟq个文g是参考了良葛??strong>DWR 入門與應用(一Q?/font>
里的Book代码
java 代码
Q?Q编写属性文?span class="hilite1">dwrPro_zh_CN.propertieQ中文)?span class="hilite1">dwr_en.propertiesQ英文)
Q?Q配|?span class="hilite1">dwr.xmlQ里面用了spring的bean 和javac?/p>
spring beanName 的value =“userManager”从applicationContext.xml里取?/p>
q里应该大家都懂Q就不罗嗦了
Q?Q配好了q些Q就可以在页面里用了
ddwr.xml?font face="Arial">userManagerQ然后读取applicationContext.xml?font face="Arial">userManager bean Q进而实?font face="Arial">findUserIsExistҎ?/font>
![]() |
|
U? 别: 初
?
?/a> (zzhangt@cn.ibm.com),
软g工程? IBM
?
U坤 (wangbk@cn.ibm.com),
软g工程? IBM
2008 q? 4 ? 10 ?/p>
单点dQSingle Sign On , U?SSO Q是目前比较行的服务于企业业务整合的解x案之一Q?SSO 使得在多个应用系l中Q用户只需要登录一ơ就可以讉K所有相互信ȝ应用pȝ。CAS(Central Authentication Service)是一ƾ不错的针对 Web 应用的单点登录框Ӟ本文介绍?CAS 的原理、协议、在 Tomcat 中的配置和用,对于采用 CAS 实现轻量U单点登录解x案的入门读者具有一定指g用?/blockquote>CAS ?Yale 大学发v的一个开源项目,旨在?Web 应用pȝ提供一U可靠的单点dҎQCAS ? 2004 q?12 月正式成?JA-SIG 的一个项目。CAS h以下特点Q?/p>
- 开源的企业U单点登录解x案?/li>
- CAS Server 为需要独立部|的 Web 应用?/li>
- CAS Client 支持非常多的客户?q里指单点登录系l中的各?Web 应用)Q包?Java, .Net, PHP, Perl, Apache, uPortal, Ruby {?/li>
从结构上看,CAS 包含两个部分Q?CAS Server ?CAS Client。CAS Server 需要独立部|Ԍ主要负责对用L认证工作QCAS Client 负责处理对客L受保护资源的讉KhQ需要登录时Q重定向?CAS Server。图1 ?CAS 最基本的协议过E:
?1. CAS 基础协议
![]()
CAS Client 与受保护的客L应用部v在一P?Filter 方式保护受保护的资源。对于访问受保护资源的每?Web hQCAS Client 会分析该h?Http h中是否包?Service TicketQ如果没有,则说明当前用户尚未登录,于是请求重定向到指定好?CAS Server d地址Qƈ传?Service Q也是要访问的目的资源地址Q,以便d成功q后转回该地址。用户在W?3 步中输入认证信息Q如果登录成功,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service TicketQƈ~存以待来验证Q之后系l自动重定向?Service 所在地址Qƈ为客L览器设|一?Ticket Granted CookieQTGCQ,CAS Client 在拿?Service 和新产生?Ticket q后Q在W?5Q? 步中?CAS Server q行w䆾合适,以确?Service Ticket 的合法性?/p>
在该协议中,所有与 CAS 的交互均采用 SSL 协议Q确保,ST ?TGC 的安全性。协议工作过E中会有 2 ơ重定向的过E,但是 CAS Client ?CAS Server 之间q行 Ticket 验证的过E对于用h透明的?/p>
另外QCAS 协议中还提供?Proxy Q代理)模式Q以适应更加高、复杂的应用场景Q具体介l可以参?CAS 官方|站上的相关文档?/p>
本文中的例子?tomcat5.5 Zq行讲解Q下载地址Q?/p>
http://tomcat.apache.org/download-55.cgi
?CAS 官方|站下蝲 CAS Server ?ClientQ地址分别为:
http://www.ja-sig.org/downloads/cas/cas-server-3.1.1-release.zip
http://www.ja-sig.org/downloads/cas-clients/cas-client-java-2.1.1.zip
回页?/strong>
CAS Server 是一套基?Java 实现的服务,该服务以一?Java Web Application 单独部v在与 servlet2.3 兼容?Web 服务器上Q另外,׃ Client ?CAS Server 之间的交互采?Https 协议Q因此部|?CAS Server 的服务器q需要支?SSL 协议。当 SSL 配置成功q后Q像普?Web 应用一样将 CAS Server 部v在服务器上就能正常运行了Q不q,在真正用之前,q需要扩展验证用L接口?/p>
?Tomcat 上部|一个完整的 CAS Server 主要按照以下几个步骤Q?/p>
如果希望 Tomcat 支持 HttpsQ主要的工作是配|?SSL 协议Q其配置q程和配|方法可以参? Tomcat 的相x档。不q在生成证书的过E中Q会有需要用C机名的地方,CAS 不要使用 IP 地址Q而要使用机器名或域名?/p>
CAS Server 是一?Web 应用包,前面下载的 cas-server-3.1.1-release.zip 解开Q把其中?cas-server-webapp-3.1.1.war 拯? tomcat?webapps 目录Qƈ更名?cas.war。由于前面已配置?tomcat ?https 协议Q可以重新启? tomcatQ然后访问:https://localhost:8443/cas Q如果能出现正常?CAS d面Q则说明 CAS Server 已经部v成功?/p>
虽然 CAS Server 已经部v成功Q但q只是一个缺省的实现Q在实际使用的时候,q需要根据实际概况做扩展和定Ӟ最主要的是扩展认证 (Authentication) 接口?CAS Server 的界面?/p>
CAS Server 负责完成对用L认证工作Q它会处理登录时的用户凭?(Credentials) 信息Q用户名/密码Ҏ最常见的凭证信息。CAS Server 可能需要到数据库检索一条用户帐号信息,也可能在 XML 文g中检索用户名/密码Q还可能通过 LDAP Server 获取{,在这U情况下QCAS 提供了一U灵zMl一的接口和实现分离的方式,实际使用? CAS 采用哪种方式认证是与 CAS 的基本协议分d的,用户可以Ҏ认证的接口去定制和扩展?/p>
扩展 AuthenticationHandler
CAS 提供扩展认证的核心是 AuthenticationHandler 接口Q该接口定义如清?1 下:
清单 1. AuthenticationHandler定义
public interface AuthenticationHandler {
/**
* Method to determine if the credentials supplied are valid.
* @param credentials The credentials to validate.
* @return true if valid, return false otherwise.
* @throws AuthenticationException An AuthenticationException can contain
* details about why a particular authentication request failed.
*/
boolean authenticate(Credentials credentials) throws AuthenticationException;
/**
* Method to check if the handler knows how to handle the credentials
* provided. It may be a simple check of the Credentials class or something
* more complicated such as scanning the information contained in the
* Credentials object.
* @param credentials The credentials to check.
* @return true if the handler supports the Credentials, false othewrise.
*/
boolean supports(Credentials credentials);
}
该接口定义了 2 个需要实现的ҎQsupports ()Ҏ用于查所l的包含认证信息的Credentials 是否受当?AuthenticationHandler 支持Q?authenticate() Ҏ则担当验证认证信息的dQ这也是需要扩展的主要ҎQ根据情况与存储合法认证信息的介质进行交互,q回 boolean cd的|true 表示验证通过Qfalse 表示验证p|?/p>
CAS3中还提供了对AuthenticationHandler 接口的一些抽象实玎ͼ比如Q可能需要在执行authenticate() Ҏ前后执行某些其他操作Q那么可以让自己的认证类扩展自清?2 中的抽象c:
清单 2. AbstractPreAndPostProcessingAuthenticationHandler定义
public abstract class AbstractPreAndPostProcessingAuthenticationHandler
implements AuthenticateHandler{
protected Log log = LogFactory.getLog(this.getClass());
protected boolean preAuthenticate(final Credentials credentials) {
return true;
}
protected boolean postAuthenticate(final Credentials credentials,
final boolean authenticated) {
return authenticated;
}
public final boolean authenticate(final Credentials credentials)
throws AuthenticationException {
if (!preAuthenticate(credentials)) {
return false;
}
final boolean authenticated = doAuthentication(credentials);
return postAuthenticate(credentials, authenticated);
}
protected abstract boolean doAuthentication(final Credentials credentials)
throws AuthenticationException;
}
AbstractPreAndPostProcessingAuthenticationHandler cL定义? preAuthenticate() Ҏ?postAuthenticate() ҎQ而实际的认证工作交由 doAuthentication() Ҏ来执行。因此,如果需要在认证前后执行一些额外的操作Q可以分别扩?preAuthenticate()? ppstAuthenticate() ҎQ?doAuthentication() 取代 authenticate() 成ؓ了子cd要实现的方法?/p>
׃实际q用中,最常用的是用户名和密码方式的认证,CAS3 提供了针对该方式的实玎ͼ如清?3 所C:
清单 3. AbstractUsernamePasswordAuthenticationHandler 定义
public abstract class AbstractUsernamePasswordAuthenticationHandler extends
AbstractPreAndPostProcessingAuthenticationHandler{
...
protected final boolean doAuthentication(final Credentials credentials)
throws AuthenticationException {
return authenticateUsernamePasswordInternal((UsernamePasswordCredentials) credentials);
}
protected abstract boolean authenticateUsernamePasswordInternal(
final UsernamePasswordCredentials credentials) throws AuthenticationException;
protected final PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public final void setPasswordEncoder(final PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
...
}
Z用户名密码的认证方式可直接扩展自 AbstractUsernamePasswordAuthenticationHandlerQ验证用户名密码的具体操作通过实现 authenticateUsernamePasswordInternal() Ҏ辑ֈQ另外,通常情况下密码会是加密过的,setPasswordEncoder() Ҏ是用于指定适当的加密器?/p>
从以上清单中可以看到QdoAuthentication() Ҏ的参数是 Credentials cdQ这是包含用戯证信息的一个接口,对于用户名密码类型的认证信息Q可以直接? UsernamePasswordCredentialsQ如果需要扩展其他类型的认证信息Q需要实现Credentials接口Qƈ且实现相应的 CredentialsToPrincipalResolver 接口Q其具体Ҏ可以借鉴 UsernamePasswordCredentials ? UsernamePasswordCredentialsToPrincipalResolver?/p>
JDBC 认证Ҏ
用户的认证信息通常保存在数据库中,因此本文选用q种情况来介l。将前面下蝲? cas-server-3.1.1-release.zip 包解开后,?modules 目录下可以找到包 cas-server-support-jdbc-3.1.1.jarQ其提供了通过 JDBC q接数据库进行验证的~省实现Q基于该包的支持Q我们只需要做一些配|工作即可实?JDBC 认证?/p>
JDBC 认证Ҏ支持多种数据库,DB2, Oracle, MySql, Microsoft SQL Server {均可,q里?DB2 作ؓ例子介绍。ƈ且假设DB2数据库名Q?CASTestQ数据库d用户名: db2userQ数据库d密码Q? db2passwordQ用户信息表为: userTableQ该表包含用户名和密码的两个数据分别ؓ userName ?password?/p>
1. 配置 DataStore
打开文g %CATALINA_HOME%/webapps/cas/WEB-INF/deployerConfigContext.xmlQ添加一个新? bean 标签Q对?DB2Q内容如清单 4 所C:
清单 4. 配置 DataStore
<bean id="casDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.ibm.db2.jcc.DB2Driver</value>
</property>
<property name="url">
<value>jdbc:db2://9.125.65.134:50000/CASTest</value>
</property>
<property name="username">
<value>db2user</value>
</property>
<property name="password">
<value>db2password</value>
</property>
</bean>
其中 id 属性ؓ?DataStore 的标识,在后面配|?AuthenticationHandler 会被引用Q另外,需要提?DataStore 所必需的数据库驱动E序、连接地址、数据库d用户名以及登录密码?/p>
2. 配置 AuthenticationHandler
?cas-server-support-jdbc-3.1.1.jar 包中Q提供了 3 个基?JDBC ? AuthenticationHandlerQ分别ؓ BindModeSearchDatabaseAuthenticationHandler, QueryDatabaseAuthenticationHandler, SearchModeSearchDatabaseAuthenticationHandler。其? BindModeSearchDatabaseAuthenticationHandler 是用所l的用户名和密码d立数据库q接Q根据连接徏立是否成功来判断验证成功? 否;QueryDatabaseAuthenticationHandler 通过配置一?SQL 语句查出密码Q与所l密码匹配;SearchModeSearchDatabaseAuthenticationHandler 通过配置存放用户验证信息的表、用户名字段和密码字D,构造查询语句来验证?/p>
使用哪个 AuthenticationHandlerQ需要在 deployerConfigContext.xml 中设|,默认情况下,CAS 使用一个简单的 username=password ? AuthenticationHandlerQ在文g中可以找到如下一行:<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePassword
AuthenticationHandler" />Q我们可以将其注释掉Q换成我们希望的一? AuthenticationHandlerQ比如,使用QueryDatabaseAuthenticationHandler ? SearchModeSearchDatabaseAuthenticationHandler 可以分别选取清单 5 或清?6 的配|?/p>
清单 5. 使用 QueryDatabaseAuthenticationHandler
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref=" casDataSource " />
<property name="sql"
value="select password from userTable where lower(userName) = lower(?)" />
</bean>
清单 6. 使用 SearchModeSearchDatabaseAuthenticationHandler
<bean id="SearchModeSearchDatabaseAuthenticationHandler"
class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler"
abstract="false" singleton="true" lazy-init="default"
autowire="default" dependency-check="default">
<property name="tableUsers">
<value>userTable</value>
</property>
<property name="fieldUser">
<value>userName</value>
</property>
<property name="fieldPassword">
<value>password</value>
</property>
<property name="dataSource" ref=" casDataSource " />
</bean>
另外Q由于存攑֜数据库中的密码通常是加密过的,所?AuthenticationHandler 在匹配时需要知道用的加密ҎQ在 deployerConfigContext.xml 文g中我们可以ؓ具体? AuthenticationHandler c配|一?propertyQ指定加密器c,比如对于 QueryDatabaseAuthenticationHandlerQ可以修改如清单7所C:
清单 7. d passwordEncoder
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref=" casDataSource " />
<property name="sql"
value="select password from userTable where lower(userName) = lower(?)" />
<property name="passwordEncoder" ref="myPasswordEncoder"/>
</bean>
其中 myPasswordEncoder 是对清单 8 中设|的实际加密器类的引用:
清单 8. 指定具体加密器类
<bean id="passwordEncoder"
class="org.jasig.cas.authentication.handler.MyPasswordEncoder"/>
q里 MyPasswordEncoder 是根据实际情况自己定义的加密器,实现 PasswordEncoder 接口及其 encode() Ҏ?/p>
3. 部v依赖?/strong>
在以上配|完成以后,需要拷贝几个依赖的包到 cas 应用下,包括Q?/p>
- ?cas-server-support-jdbc-3.1.1.jar 拯?%CATALINA_HOME%/webapps/cas/ WEB-INF/lib 目录?/li>
- 数据库驱动,׃q里使用 DB2Q将 %DB2_HOME%/java 目录下的 db2java.zip Q更名ؓ db2java.jarQ? db2jcc.jar, db2jcc_license_cu.jar 拯? %CATALINA_HOME%/webapps/cas/WEB-INF/lib 目录。对于其他数据库Q同样将相应数据库驱动程序拷贝到该目录?/li>
- DataStore 依赖?commons-collections-3.2.jar, commons-dbcp-1.2.1.jar, commons-pool-1.3.jarQ需要到 apache |站?Commons 目下蝲以上 3 个包放进 %CATALINA_HOME%/webapps/cas/WEB-INF/lib 目录?/li>
CAS 提供?2 套默认的面Q分别ؓ“ default ”?#8220; simple ”Q分别在目录“ cas/WEB-INF/view/jsp/default ”?#8220; cas/WEB-INF/view/jsp/simple ”下。其? default 是一个稍微复杂一些的面Q?CSSQ?simple 则是能让 CAS 正常工作的最化的面?/p>
在部|?CAS 之前Q我们可能需要定制一套新?CAS Server 面Q添加一些个性化的内宏V最单的Ҏ是拯一?default ?simple 文g?#8220; cas/WEB-INF/view/jsp ”目录下,比如命名?newUIQ接下来是实现和修改必要的页面,?4 个页面是必须的:
- casConfirmView.jsp: 当用户选择?#8220; warn ”时会看到的确认界?/li>
- casGenericSuccess.jsp: 在用h功通过认证而没有目的Service时会看到的界?/li>
- casLoginView.jsp: 当需要用h供认证信息时会出现的界面
- casLogoutView.jsp: 当用L?CAS 单点dpȝ会话时出现的界面
CAS 的页面采?Spring 框架~写Q对于不熟悉 Spring 的用者,在修改之前需要熟悉该框架?/p>
面定制完过后,q需要做一些配|从而让 CAS 扑ֈ新的面Q拷?#8220; cas/WEB-INF/classes/default_views.properties ”Q重命名?#8220; cas/WEB-INF/classes/ newUI_views.properties ”Qƈ修改其中所有的值到相应新页面。最后是更新“ cas/WEB-INF/cas-servlet.xml ”文g中的 viewResolverQ将其修改ؓ如清?9 中的内容?/p>
清单 9. 指定 CAS 面
<bean id="viewResolver"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver" p:order="0">
<property name="basenames">
<list>
<value>${cas.viewResolver.basename}</value>
<value> newUI_views</value>
</list>
</property>
</bean>
回页?/strong>
假设 CAS Server 单独部v在一台机?AQ而客L应用部v在机?B 上,׃客户端应用与 CAS Server 的通信采用 SSLQ因此,需要在 A ?B ?JRE 之间建立信Q关系?/p>
首先?A 机器一P要生?B 机器上的证书Q配|?Tomcat ?SSL 协议。其ơ,下蝲http://blogs.sun.com/andreas/entry/no_more_unable_to_find ?InstallCert.javaQ运?#8220; java InstallCert compA:8443 ”命oQƈ且在接下来出现的询问中输? 1。这P将 A dC B ?trust store 中。如果多个客L应用分别部v在不同机器上Q那么每个机器都需要与 CAS Server 所在机器徏立信dpR?/p>
准备好应?casTest1 ?casTest2 q后Q分别部|在 B ?C 机器上,׃ casTest1 和casTest2QB ?C 完全{同Q我们以 casTest1 ?B 机器上的配置做介l,假设 A ?B 的域名分别ؓ domainA ? domainB?/p>
?cas-client-java-2.1.1.zip 改名?cas-client-java-2.1.1.jar q拷贝到 casTest1/WEB-INF/lib目录下,修改 web.xml 文gQ添?CAS FilterQ如清单 10 所C:
清单 10. d CAS Filter
<web-app>
...
<filter>
<filter-name>CAS Filter</filter-name>
<filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
<param-value>https://domainA:8443/cas/login</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
<param-value>https://domainA:8443/cas/serviceValidate</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
<param-value>domainB:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/protected-pattern/*</url-pattern>
</filter-mapping>
...
</web-app>
对于所有访问满?casTest1/protected-pattern/ 路径的资源时Q都要求?CAS Server dQ如果需要整?casTest1 均受保护Q可以将 url-pattern 指定?#8220;/*”?/p>
从清?10 可以看到Q我们可以ؓ CASFilter 指定一些参敎ͼq且有些是必ȝQ?a >? ?1 ?a >? ?2 中分别是必需和可选的参数Q?/p>
表格 1. CASFilter 必需的参?/strong>
参数?/strong> 作用 edu.yale.its.tp.cas.client.filter.loginUrl 指定 CAS 提供d面?URL edu.yale.its.tp.cas.client.filter.validateUrl 指定 CAS 提供 service ticket ?proxy ticket 验证服务?URL edu.yale.its.tp.cas.client.filter.serverName 指定客户端的域名和端口,是指客户端应用所在机器而不?CAS Server 所在机器,该参数或 serviceUrl 臛_有一个必L? edu.yale.its.tp.cas.client.filter.serviceUrl 该参数指定过后将覆盖 serverName 参数Q成为登录成功过后重定向的目的地址
表格 2. CASFilter 可选参?/strong>
参数?/strong> 作用 edu.yale.its.tp.cas.client.filter.proxyCallbackUrl 用于当前应用需要作为其他服务的代理(proxy)时获?Proxy Granting Ticket 的地址 edu.yale.its.tp.cas.client.filter.authorizedProxy 用于允许当前应用从代理处获取 proxy ticketsQ该参数接受以空格分隔开的多?proxy URLsQ但实际使用只需要一个成功即可。当指定该参数过后,需要修?validateUrl ?proxyValidateQ而不再是 serviceValidate edu.yale.its.tp.cas.client.filter.renew 如果指定?trueQ那么受保护的资源每ơ被讉K时均要求用户重新q行验证Q而不之前是否已l通过 edu.yale.its.tp.cas.client.filter.wrapRequest 如果指定?trueQ那?CASFilter 重新包?HttpRequest,q且?getRemoteUser() Ҏq回当前d用户的用户名 edu.yale.its.tp.cas.client.filter.gateway 指定 gateway 属?
CAS 在登录成功过后,会给览器回?CookieQ设|新的到?Service Ticket。但客户端应用拥有各自的 SessionQ我们要怎么在各个应用中获取当前d用户的用户名呢?CAS Client ?Filter 已经做好了处理,在登录成功后Q就可以直接?Session 的属性中获取Q如清单 11 所C:
清单 11. ?Java 中通过 Session 获取d用户?/strong>
// 以下两者都可以
session.getAttribute(CASFilter.CAS_FILTER_USER);
session.getAttribute("edu.yale.its.tp.cas.client.filter.user");
?JSTL 中获取用户名的方法如清单 12 所C:
清单 12. 通过 JSTL 获取d用户?/strong>
<c:out value="${sessionScope[CAS:'edu.yale.its.tp.cas.client.filter.user']}"/>
另外QCAS 提供了一?CASFilterRequestWrapper c,该类l承自HttpServletRequestWrapperQ主要是重写?getRemoteUser() ҎQ只要在前面配置 CASFilter 的时候ؓ其设|?#8220; edu.yale.its.tp.cas.client.filter.wrapRequest ”参数? trueQ就可以通过 getRemoteUserQ) Ҏ来获取登录用户名Q具体方法如清单 13 所C:
清单 13. 通过 CASFilterRequestWrapper 获取d用户?/strong>
CASFilterRequestWrapper reqWrapper=new CASFilterRequestWrapper(request);
out.println("The logon user:" + reqWrapper.getRemoteUser());
回页?/strong>
?casTest1 ?casTest2 中,都有一个简?Servlet 作ؓƢ迎面 WelcomPageQ且该页面必ȝ录过后才能访问,面代码如清?14 所C:
清单 14. WelcomePage 面代码
public class WelcomePage extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Welcome to casTest2 sample System!</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Welcome to casTest1 sample System!</h1>");
CASFilterRequestWrapper reqWrapper=new CASFilterRequestWrapper(request);
out.println("<p>The logon user:" + reqWrapper.getRemoteUser() + "</p>");
HttpSession session=request.getSession();
out.println("<p>The logon user:" +
session.getAttribute(CASFilter.CAS_FILTER_USER) + "</p>");
out.println("<p>The logon user:" +
session.getAttribute("edu.yale.its.tp.cas.client.filter.user") + "</p>");
out.println("</body>");
out.println("</html>");
}
}
在上面所有配|结束过后,分别?AQ?BQ?C上启?casQ?casTest1 ? casTest2Q按照下面步骤来讉K casTest1 ?casTest2Q?/p>
- 打开览器,讉K http://domainB:8080/casTest1/WelcomePage Q浏览器会弹出安全提C,接受后即转到 CAS 的登录页面,如图 2 所C:
?2. CAS d面
![]()
- d成功后,再重定向?casTest1 ?WelcomePage 面Q如? 所C:
?3. d后访?casTest1 的效?/strong>
![]()
可以看到? 中地址栏里的地址多出了一?ticket 参数Q这是 CAS 分配l当前应用的 ST(Service Ticket)?/p>
- 再在同一个浏览器的地址栏中输入 http://domainC:8080/casTest2/WelcomePage Q系l不再提C用L录,而直接出现如?4 所C的面Qƈ且显C在 casTest1 中已l登录过的用戗?/li>
?4. ?casTest1 中登录过后访?casTest2 的效?/strong>
![]()
- 重新打开一个浏览器H口Q先输入 http://domainC:8080/casTest2/WelcomePage Q系l要求登录,在登录成功过后,正确昄 casTest2 的页面。之后再在地址栏重新输?http://domainB:8080/casTest1/WelcomePage Q会直接昄 casTest1 的页面而无需再次d?/li>
回页?/strong>
- 有关 CAS 斚w的信息, 请参?a >CAS 官方|站?
- 览JA-SIG CQ与C分n Java 技术?
张涛QIBM 中国软g开发实验室工程师,目前主要致力于基?Rational q_解决Ҏ的开发?/p>
王秉坤,IBM 中国软g开发实验室工程师,目前主要致力于基?Rational q_解决Ҏ的开发?/p>
]]>