CAS单点登陆系统

CAS 介绍

CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登陆方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目。CAS 具备如下特色:javascript

  • 开源的企业级单点登陆解决方案。
  • CAS Server 为须要独立部署的 Web 应用。
  • CAS Client 支持很是多的客户端(这里指单点登陆系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

CAS 原理和协议

从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 须要独立部署,主要负责对用户的认证工做;CAS Client 负责处理对客户端受保护资源的访问请求,须要登陆时,重定向到 CAS Server。图1 是 CAS 最基本的协议过程:php

图 1. CAS 基础协议css

CAS 基础协议

CAS Client 与受保护的客户端应用部署在一块儿,以 Filter 方式保护受保护的资源。对于访问受保护资源的每一个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket,若是没有,则说明当前用户还没有登陆,因而将请求重定向到指定好的 CAS Server 登陆地址,并传递 Service (也就是要访问的目的资源地址),以便登陆成功事后转回该地址。用户在第 3 步中输入认证信息,若是登陆成功,CAS Server 随机产生一个至关长度、惟1、不可伪造的 Service Ticket,并缓存以待未来验证,以后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 事后,在第 5,6 步中与 CAS Server 进行身份合适,以确保 Service Ticket 的合法性。html

在该协议中,全部与 CAS 的交互均采用 SSL 协议,确保,ST 和 TGC 的安全性。协议工做过程当中会有 2 次重定向的过程,可是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的。java

另外,CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍能够参考 CAS 官方网站上的相关文档。git

 

服务器版本选用 https://github.com/apereo/cas/tree/v4.0.0 github

客户端选用    https://github.com/Jasig/phpCASweb

 

部署 CAS Server

CAS Server 是一套基于 Java 实现的服务,该服务以一个 Java Web Application 单独部署在与 servlet2.3 兼容的 Web 服务器上,另外,因为 Client 与 CAS Server 之间的交互采用 Https 协议,所以部署 CAS Server 的服务器还须要支持 SSL 协议。当 SSL 配置成功事后,像普通 Web 应用同样将 CAS Server 部署在服务器上就能正常运行了,不过,在真正使用以前,还须要扩展验证用户的接口。spring

在 Tomcat 上部署一个完整的 CAS Server 主要按照如下几个步骤:sql

配置 Tomcat 使用 Https 协议

若是但愿 Tomcat 支持 Https,主要的工做是配置 SSL 协议,其配置过程和配置方法能够参考 Tomcat 的相关文档。不过在生成证书的过程当中,会有须要用到主机名的地方,CAS 建议不要使用 IP 地址,而要使用机器名或域名。

部署 CAS Server

CAS Server 是一个 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 登陆页面,则说明 CAS Server 已经部署成功。

虽然 CAS Server 已经部署成功,但这只是一个缺省的实现,在实际使用的时候,还须要根据实际概况作扩展和定制,最主要的是扩展认证 (Authentication) 接口和 CAS Server 的界面。

扩展认证接口

CAS Server 负责完成对用户的认证工做,它会处理登陆时的用户凭证 (Credentials) 信息,用户名/密码对是最多见的凭证信息。CAS Server 可能须要到数据库检索一条用户账号信息,也可能在 XML 文件中检索用户名/密码,还可能经过 LDAP Server 获取等,在这种状况下,CAS 提供了一种灵活但统一的接口和实现分离的方式,实际使用中 CAS 采用哪一种方式认证是与 CAS 的基本协议分离开的,用户能够根据认证的接口去定制和扩展。

扩展 AuthenticationHandler

CAS 提供扩展认证的核心是 AuthenticationHandler 接口,该接口定义如清单 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 个须要实现的方法,supports ()方法用于检查所给的包含认证信息的Credentials 是否受当前 AuthenticationHandler 支持;而 authenticate() 方法则担当验证认证信息的任务,这也是须要扩展的主要方法,根据状况与存储合法认证信息的介质进行交互,返回 boolean 类型的值,true 表示验证经过,false 表示验证失败。

CAS3中还提供了对AuthenticationHandler 接口的一些抽象实现,好比,可能须要在执行authenticate() 方法先后执行某些其余操做,那么可让本身的认证类扩展自清单 2 中的抽象类:

清单 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 类新定义了 preAuthenticate() 方法和 postAuthenticate() 方法,而实际的认证工做交由 doAuthentication() 方法来执行。所以,若是须要在认证先后执行一些额外的操做,能够分别扩展 preAuthenticate()和 ppstAuthenticate() 方法,而 doAuthentication() 取代 authenticate() 成为了子类必需要实现的方法。

因为实际运用中,最经常使用的是用户名和密码方式的认证,CAS3 提供了针对该方式的实现,如清单 3 所示:

清单 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;
    }
...
}

基于用户名密码的认证方式可直接扩展自 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, Microsoft SQL Server 等都可,这里以 DB2 做为例子介绍。而且假设DB2数据库名: CASTest,数据库登陆用户名: db2user,数据库登陆密码: db2password,用户信息表为: userTable,该表包含用户名和密码的两个数据项分别为 userName 和 password。

1. 配置 DataStore

打开文件 %CATALINA_HOME%/webapps/cas/WEB-INF/deployerConfigContext.xml,添加一个新的 bean 标签,对于 DB2,内容如清单 4 所示:

清单 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 会被引用,另外,须要提供 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,在文件中能够找到以下一行:<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePassword
AuthenticationHandler" />,咱们能够将其注释掉,换成咱们但愿的一个 AuthenticationHandler,好比,使用QueryDatabaseAuthenticationHandler 或 SearchModeSearchDatabaseAuthenticationHandler 能够分别选取清单 5 或清单 6 的配置。

清单 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>

另外,因为存放在数据库中的密码一般是加密过的,因此 AuthenticationHandler 在匹配时须要知道使用的加密方法,在 deployerConfigContext.xml 文件中咱们能够为具体的 AuthenticationHandler 类配置一个 property,指定加密器类,好比对于 QueryDatabaseAuthenticationHandler,能够修改如清单7所示:

清单 7. 添加 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"/>

这里 MyPasswordEncoder 是根据实际状况本身定义的加密器,实现 PasswordEncoder 接口及其 encode() 方法。

3. 部署依赖包

在以上配置完成之后,须要拷贝几个依赖的包到 cas 应用下,包括:

  • 将 cas-server-support-jdbc-3.1.1.jar 拷贝到 %CATALINA_HOME%/webapps/cas/ WEB-INF/lib 目录。
  • 数据库驱动,因为这里使用 DB2,将 %DB2_HOME%/java 目录下的 db2java.zip (改名为 db2java.jar), db2jcc.jar, db2jcc_license_cu.jar 拷贝到 %CATALINA_HOME%/webapps/cas/WEB-INF/lib 目录。对于其余数据库,一样将相应数据库驱动程序拷贝到该目录。
  • DataStore 依赖于 commons-collections-3.2.jar, commons-dbcp-1.2.1.jar, commons-pool-1.3.jar,须要到 apache 网站的 Commons 项目下载以上 3 个包放进 %CATALINA_HOME%/webapps/cas/WEB-INF/lib 目录。

扩展 CAS Server 界面

CAS 提供了 2 套默认的页面,分别为“ default ”和“ simple ”,分别在目录“ cas/WEB-INF/view/jsp/default ”和“ cas/WEB-INF/view/jsp/simple ”下。其中 default 是一个稍微复杂一些的页面,使用 CSS,而 simple 则是能让 CAS 正常工做的最简化的页面。

在部署 CAS 以前,咱们可能须要定制一套新的 CAS Server 页面,添加一些个性化的内容。最简单的方法就是拷贝一份 default 或 simple 文件到“ cas/WEB-INF/view/jsp ”目录下,好比命名为 newUI,接下来是实现和修改必要的页面,有 4 个页面是必须的:

  • casConfirmView.jsp: 当用户选择了“ warn ”时会看到的确认界面
  • casGenericSuccess.jsp: 在用户成功经过认证而没有目的Service时会看到的界面
  • casLoginView.jsp: 当须要用户提供认证信息时会出现的界面
  • casLogoutView.jsp: 当用户结束 CAS 单点登陆系统会话时出现的界面

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 页面

<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>

部署客户端应用

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,此文件用于处理单点登录,内容以下:

 

</pre><p><pre name="code" class="php"><pre name="code" class="php">
<?php
class user
{
    /**
     * Logs out the current user and redirect to homepage.
     */
    public function logout()
    {
        session_start();
        //启用单点登陆的时候还要到统一认证中心注销  && isset($_SESSION['phpCAS']) && $_SESSION['phpCAS']['auth_checked'] == '1'   

        //引人cas  
        include_once '/CAS-1.2.0/CAS.php';

        // initialize phpCAS              
        //phpCAS::client(CAS_VERSION_2_0,'服务地址',端口号,'cas的访问地址');  
        phpCAS::client(CAS_VERSION_2_0,"192.168.142.1","80","/cas");

        //方法一:登出成功后跳转的地址 -- 登出方法中加此句  
        /* 
        phpCAS::setServerLoginUrl("https://192.168.142.1:80/cas/logout?embed=true&service=http://localhost/phpCasClient/user.php?a=login"); 
        //no SSL validation for the CAS server 
        phpCAS::setNoCasServerValidation(); 
        phpCAS::logout();*/

        //方法二:退出登陆后返回地址 -- 登出方法中加此句  
        phpCAS::setNoCasServerValidation();$param=array("service"=>"http://localhost/phpCasClient/user.php?a=login");
        phpCAS::logout($param);
    }

    /**
     * @desc LoginCas()单点登录
     */
    public function loginCas(){
        Header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
        //引人cas  
        include 'CAS-1.2.0/CAS.php';
        // initialize phpCAS   
        //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");
        //no SSL validation for the CAS server 不使用SSL服务校验  
        phpCAS::setNoCasServerValidation();
        //这里会检测服务器端的退出的通知,就能实现php和其余语言平台间同步登出了  
        phpCAS::handleLogoutRequests();

        if(phpCAS::checkAuthentication()){
            //获取登录的用户名  
            $username=phpCAS::getUser();
            //用户登录成功后,采用js进行页面跳转  
            echo "<script language=\"javascript\">parent.location.href='http://localhost/phpCasClient/home.php';</script>";
        }else{
            // 访问CAS的验证  
            phpCAS::forceAuthentication();
        }
        exit;
    }
}
?>

新建视图层,login.html,该页面即为单点登录页面,内容以下:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>单点登录界面</title>
</head>
<body>
<div class="view">
    <table style="width:600; height:333; border:1; align:center; cellpadding:4; bordercolor:#CCCCCC;">
        <tr>
            <td style="width: 50%;align:center;">
                请输入登录信息:
            </td>
            <td style="width: 50%;">
                <div id="maincol" style="border: 1px solid #ccc;float: left;width: 400px;height: 219px; overflow: hidden;">
                    <iframe id="auth-login-iframe" name="auth-login-iframe" style="width:100%; height:100%; marginwidth:0; marginheight:0; frameborder:0; scrolling:no;" src="http://localhost/phpCasClient/user.php?a=loginCas"></iframe>
                </div>
            </td>
        </tr>
        <tr>
            <span style="white-space:pre"> </span><td><a href="http://localhost/phpCasClient/user.php?a=logout">登出</a></td>
        </tr>
    </table>
</div>
</body>
</html>

注意:php配置文件php.ini须要开启php_curl,即 找到 ;extension=php_curl.dll ,将该句前面的分号去掉便可,改成 extension=php_curl.dll

安装解析xml的扩展

什么是DOM?


文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口。它给文档提供了一种结构化的表示方法,能够改变文档的内容和呈现方式。咱们最为关心的是,DOM把网页和脚本以及其余的编程语言联系了起来。


什么是PHP DOM Extension ?


DOM Extension容许php使用DOM处理xml文档。相关手册说明:
http://www.php.net/manual/en/intro.dom.php


如何安装PHP DOM Extension?

 


在CentOS系统下是很好安装的,运行

#yum install php-xml
重启一下apache
#service httpd restart

 


如何检查系统已经安装好了PHP DOM Extension ?

 


使用phpinfo()函数测试
#vi phpinfo.php
<?
   phpinfo();
?>
http://localhost/phpinfo.php会看到PHP测试页面。中间有如下几行说明已安装好。

DOM/XML

enabled

DOM/XML API Version

20031129

libxml Version

2.6.26

HTML Support

enabled

XPath Support

enabled

XPointer Support

enabled

Schema Support

enabled

RelaxNG Support

enabled

 

 

此时,访问login.html,即可以看到单点登录的界面,登录成功后,页面跳转到home.php中.

点击 登出,控制层请求user.php的logout方法,处理登出请求.登出成功后,页面跳转到login.html,提示用户登录

 

至此,php使用CAS的单点登录,登出已经操做完毕.

相关文章
相关标签/搜索