{{ v.name }}
{{ v.cls }}类
{{ v.price }} ¥{{ v.price }}
cas是yale大学发起的一个开源项目,旨在为web应用系统提供一种可靠的单点登录方法,cas在2004年12月正式成为ja-sig的一个项目。cas具有以下特点:
从结构上看,cas包含两个部分:casserver和casclient。casserver需要独立部署,主要负责对用户的认证工作;casclient负责处理对客户端受保护资源的访问请求,需要登录时,重定向到casserver。图1是cas最基本的协议过程:
cas基础协议
casclient与受保护的客户端应用部署在一起,以filter方式保护受保护的资源。对于访问受保护资源的每个web请求,casclient会分析该请求的http请求中是否包含serviceticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的casserver登录地址,并传递service(也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第3步中输入认证信息,如果登录成功,casserver随机产生一个相当长度、唯一、不可伪造的serviceticket,并缓存以待将来验证,之后系统自动重定向到service所在地址,并为客户端浏览器设置一个ticketgrantedcookie(tgc),casclient在拿到service和新产生的ticket过后,在第5,6步中与casserver进行身份合适,以确保serviceticket的合法性。
在该协议中,所有与cas的交互均采用ssl协议,确保,st和tgc的安全性。协议工作过程中会有2次重定向的过程,但是casclient与casserver之间进行ticket验证的过程对于用户是透明的。
另外,cas协议中还提供了proxy(代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考cas官方网站上的相关文档。
服务器版本选用https://github.com/apereo/cas/tree/v4.0.0
客户端选用https://github.com/jasig/phpcas
casserver是一套基于java实现的服务,该服务以一个javawebapplication单独部署在与servlet2.3兼容的web服务器上,另外,由于client与casserver之间的交互采用https协议,因此部署casserver的服务器还需要支持ssl协议。当ssl配置成功过后,像普通web应用一样将casserver部署在服务器上就能正常运行了,不过,在真正使用之前,还需要扩展验证用户的接口。
在tomcat上部署一个完整的casserver主要按照以下几个步骤:
如果希望tomcat支持https,主要的工作是配置ssl协议,其配置过程和配置方法可以参考tomcat的相关文档。不过在生成证书的过程中,会有需要用到主机名的地方,cas建议不要使用ip地址,而要使用机器名或域名。
casserver是一个web应用包,将前面下载的cas-server-3.1.1-release.zip解开,把其中的cas-server-webapp-3.1.1.war拷贝到tomcat的webapps目录,并更名为cas.war。由于前面已配置好tomcat的https协议,可以重新启动tomcat,然后访问:https://localhost:8443/cas,如果能出现正常的cas登录页面,则说明casserver已经部署成功。
虽然casserver已经部署成功,但这只是一个缺省的实现,在实际使用的时候,还需要根据实际概况做扩展和定制,最主要的是扩展认证(authentication)接口和casserver的界面。
casserver负责完成对用户的认证工作,它会处理登录时的用户凭证(credentials)信息,用户名/密码对是最常见的凭证信息。casserver可能需要到数据库检索一条用户帐号信息,也可能在xml文件中检索用户名/密码,还可能通过ldapserver获取等,在这种情况下,cas提供了一种灵活但统一的接口和实现分离的方式,实际使用中cas采用哪种方式认证是与cas的基本协议分离开的,用户可以根据认证的接口去定制和扩展。
扩展authenticationhandler
cas提供扩展认证的核心是authenticationhandler接口,该接口定义如清单1下:
清单1.authenticationhandler定义
publicinterfaceauthenticationhandler{/***methodtodetermineifthecredentialssuppliedarevalid.*@paramcredentialsthecredentialstovalidate.*@returntrueifvalid,returnfalseotherwise.*@throwsauthenticationexceptionanauthenticationexceptioncancontain*detailsaboutwhyaparticularauthenticationrequestfailed.*/booleanauthenticate(credentialscredentials)throwsauthenticationexception;/***methodtocheckifthehandlerknowshowtohandlethecredentials*provided.itmaybeasimplecheckofthecredentialsclassorsomething*morecomplicatedsuchasscanningtheinformationcontainedinthe*credentialsobject.*@paramcredentialsthecredentialstocheck.*@returntrueifthehandlersupportsthecredentials,falseothewrise.*/booleansupports(credentialscredentials);}
该接口定义了2个需要实现的方法,supports()方法用于检查所给的包含认证信息的credentials是否受当前authenticationhandler支持;而authenticate()方法则担当验证认证信息的任务,这也是需要扩展的主要方法,根据情况与存储合法认证信息的介质进行交互,返回boolean类型的值,true表示验证通过,false表示验证失败。
cas3中还提供了对authenticationhandler接口的一些抽象实现,比如,可能需要在执行authenticate()方法前后执行某些其他操作,那么可以让自己的认证类扩展自清单2中的抽象类:
清单2.abstractpreandpostprocessingauthenticationhandler定义
publicabstractclassabstractpreandpostprocessingauthenticationhandlerimplementsauthenticatehandler{protectedloglog=logfactory.getlog(this.getclass());protectedbooleanpreauthenticate(finalcredentialscredentials){returntrue;}protectedbooleanpostauthenticate(finalcredentialscredentials,finalbooleanauthenticated){returnauthenticated;}publicfinalbooleanauthenticate(finalcredentialscredentials)throwsauthenticationexception{if(!preauthenticate(credentials)){returnfalse;}finalbooleanauthenticated=doauthentication(credentials);returnpostauthenticate(credentials,authenticated);}protectedabstractbooleandoauthentication(finalcredentialscredentials)throwsauthenticationexception;}
abstractpreandpostprocessingauthenticationhandler类新定义了preauthenticate()方法和postauthenticate()方法,而实际的认证工作交由doauthentication()方法来执行。因此,如果需要在认证前后执行一些额外的操作,可以分别扩展preauthenticate()和ppstauthenticate()方法,而doauthentication()取代authenticate()成为了子类必须要实现的方法。
由于实际运用中,最常用的是用户名和密码方式的认证,cas3提供了针对该方式的实现,如清单3所示:
清单3.abstractusernamepasswordauthenticationhandler定义
publicabstractclassabstractusernamepasswordauthenticationhandlerextendsabstractpreandpostprocessingauthenticationhandler{...protectedfinalbooleandoauthentication(finalcredentialscredentials)throwsauthenticationexception{returnauthenticateusernamepasswordinternal((usernamepasswordcredentials)credentials);}protectedabstractbooleanauthenticateusernamepasswordinternal(finalusernamepasswordcredentialscredentials)throwsauthenticationexception;protectedfinalpasswordencodergetpasswordencoder(){returnthis.passwordencoder;}publicfinalvoidsetpasswordencoder(finalpasswordencoderpasswordencoder){this.passwordencoder=passwordencoder;}...}
基于用户名密码的认证方式可直接扩展自abstractusernamepasswordauthenticationhandler,验证用户名密码的具体操作通过实现authenticateusernamepasswordinternal()方法达到,另外,通常情况下密码会是加密过的,setpasswordencoder()方法就是用于指定适当的加密器。
从以上清单中可以看到,doauthentication()方法的参数是credentials类型,这是包含用户认证信息的一个接口,对于用户名密码类型的认证信息,可以直接使用usernamepasswordcredentials,如果需要扩展其他类型的认证信息,需要实现credentials接口,并且实现相应的credentialstoprincipalresolver接口,其具体方法可以借鉴usernamepasswordcredentials和usernamepasswordcredentialstoprincipalresolver。
jdbc认证方法
用户的认证信息通常保存在数据库中,因此本文就选用这种情况来介绍。将前面下载的cas-server-3.1.1-release.zip包解开后,在modules目录下可以找到包cas-server-support-jdbc-3.1.1.jar,其提供了通过jdbc连接数据库进行验证的缺省实现,基于该包的支持,我们只需要做一些配置工作即可实现jdbc认证。
jdbc认证方法支持多种数据库,db2,oracle,mysql,microsoftsqlserver等均可,这里以db2作为例子介绍。并且假设db2数据库名:castest,数据库登录用户名:db2user,数据库登录密码:db2password,用户信息表为:usertable,该表包含用户名和密码的两个数据项分别为username和password。
1.配置datastore
打开文件%catalina_home%/webapps/cas/web-inf/deployerconfigcontext.xml,添加一个新的bean标签,对于db2,内容如清单4所示:
清单4.配置datastore
="casdatasource"class="org.apache.commons.dbcp.basicdatasource">="driverclassname">
其中id属性为该datastore的标识,在后面配置authenticationhandler会被引用,另外,需要提供datastore所必需的数据库驱动程序、连接地址、数据库登录用户名以及登录密码。
2.配置authenticationhandler
在cas-server-support-jdbc-3.1.1.jar包中,提供了3个基于jdbc的authenticationhandler,分别为bindmodesearchdatabaseauthenticationhandler,querydatabaseauthenticationhandler,searchmodesearchdatabaseauthenticationhandler。其中bindmodesearchdatabaseauthenticationhandler是用所给的用户名和密码去建立数据库连接,根据连接建立是否成功来判断验证成功与否;querydatabaseauthenticationhandler通过配置一个sql语句查出密码,与所给密码匹配;searchmodesearchdatabaseauthenticationhandler通过配置存放用户验证信息的表、用户名字段和密码字段,构造查询语句来验证。
使用哪个authenticationhandler,需要在deployerconfigcontext.xml中设置,默认情况下,cas使用一个简单的username=password的authenticationhandler,在文件中可以找到如下一行:="org.jasig.cas.authentication.handler.support.simpletestusernamepasswordauthenticationhandler">,我们可以将其注释掉,换成我们希望的一个authenticationhandler,比如,使用querydatabaseauthenticationhandler或searchmodesearchdatabaseauthenticationhandler可以分别选取清单5或清单6的配置。
清单5.使用querydatabaseauthenticationhandler
="org.jasig.cas.adaptors.jdbc.querydatabaseauthenticationhandler">="datasource"ref="casdatasource">="sql"value="selectpasswordfromusertablewherelower(username)=lower(?)">
清单6.使用searchmodesearchdatabaseauthenticationhandler
="searchmodesearchdatabaseauthenticationhandler"class="org.jasig.cas.adaptors.jdbc.searchmodesearchdatabaseauthenticationhandler"abstract="false"singleton="true"lazy-init="default"autowire="default"dependency-check="default">="tableusers">
另外,由于存放在数据库中的密码通常是加密过的,所以authenticationhandler在匹配时需要知道使用的加密方法,在deployerconfigcontext.xml文件中我们可以为具体的authenticationhandler类配置一个property,指定加密器类,比如对于querydatabaseauthenticationhandler,可以修改如清单7所示:
清单7.添加passwordencoder
="org.jasig.cas.adaptors.jdbc.querydatabaseauthenticationhandler">="datasource"ref="casdatasource">="sql"value="selectpasswordfromusertablewherelower(username)=lower(?)">="passwordencoder"ref="mypasswordencoder">
其中mypasswordencoder是对清单8中设置的实际加密器类的引用:
清单8.指定具体加密器类
="passwordencoder"class="org.jasig.cas.authentication.handler.mypasswordencoder">
这里mypasswordencoder是根据实际情况自己定义的加密器,实现passwordencoder接口及其encode()方法。
3.部署依赖包
在以上配置完成以后,需要拷贝几个依赖的包到cas应用下,包括:
cas提供了2套默认的页面,分别为“default”和“simple”,分别在目录“cas/web-inf/view/jsp/default”和“cas/web-inf/view/jsp/simple”下。其中default是一个稍微复杂一些的页面,使用css,而simple则是能让cas正常工作的最简化的页面。
在部署cas之前,我们可能需要定制一套新的casserver页面,添加一些个性化的内容。最简单的方法就是拷贝一份default或simple文件到“cas/web-inf/view/jsp”目录下,比如命名为newui,接下来是实现和修改必要的页面,有4个页面是必须的:
cas的页面采用spring框架编写,对于不熟悉spring的使用者,在修改之前需要熟悉该框架。
页面定制完过后,还需要做一些配置从而让cas找到新的页面,拷贝“cas/web-inf/classes/default_views.properties”,重命名为“cas/web-inf/classes/newui_views.properties”,并修改其中所有的值到相应新页面。最后是更新“cas/web-inf/cas-servlet.xml”文件中的viewresolver,将其修改为如清单9中的内容。
清单9.指定cas页面
="viewresolver"class="org.springframework.web.servlet.view.resourcebundleviewresolver"p:order="0">="basenames">
部署客户端应用
php客户端下载地址:http://downloads.jasig.org/cas-clients/php/,目前最新版本为cas-1.2.0.orc2
新建项目:phpcasclient.将cas文件夹和cas.php复制到工程中,修改cas/client.php,将其中的https改为http,新建php文件:user.php,此文件用于处理单点登陆,内容如下:
="code"class="php">="code"class="php">"http://localhost/phpcasclient/user.php?a=login");phpcas::logout($param);}/***@desclogincas()单点登陆*/publicfunctionlogincas(){header('p3p:cp="idcdspcoradmdevitaiipsapsdivaiivdiconihisourindcnt"');//引人casinclude'cas-1.2.0/cas.php';//initializephpcas//phpcas::client(cas_version_2_0,'服务地址',端口号,'cas的访问地址');phpcas::client(cas_version_2_0,"192.168.142.1","80","/cas",true);//可以不用,用于调试,可以通过服务端的cas.log看到验证过程。//phpcas::setdebug();//登陆成功后跳转的地址--登陆方法中加此句phpcas::setserverloginurl("https://192.168.142.1:80/cas/login?embed=true&cssurl=http://localhost/phpcasclient/style/login.css&service=http://localhost/phpcasclient/user.php?a=logincas");//nosslvalidationforthecasserver不使用ssl服务校验phpcas::setnocasservervalidation();//这里会检测服务器端的退出的通知,就能实现php和其他语言平台间同步登出了phpcas::handlelogoutrequests();if(phpcas::checkauthentication()){//获取登陆的用户名$username=phpcas::getuser();//用户登陆成功后,采用js进行页面跳转echo"="javascript">parent.location.href='http://localhost/phpcasclient/home.php';";}else{//访问cas的验证phpcas::forceauthentication();}exit;}}?>
新建视图层,login.html,该页面即为单点登陆页面,内容如下:
="http: www.w3.org="" 1999="" xhtml"="">="content-type"content="text html;charset="utf-8"/">
注意:php配置文件php.ini需要开启php_curl,即找到;extension=php_curl.dll,将该句前面的分号去掉即可,改为extension=php_curl.dll
#yuminstallphp-xml重启一下apache#servicehttpdrestar
dom/xml
enabled
dom/xmlapiversion
20031129
libxmlversion
2.6.26
htmlsupport
enabled
xpathsupport
enabled
xpointersupport
enabled
schemasupport
enabled
relaxngsupport
enabled
此时,访问login.html,便可以看到单点登陆的界面,登陆成功后,页面跳转到home.php中.
点击登出,控制层请求user.php的logout方法,处理登出请求.登出成功后,页面跳转到login.html,提示用户登陆
至此,php使用cas的单点登陆,登出已经操作完毕.