使用CAS框架快速实现单点登录

作者:金云龙,如要转发,请注明出处作者

1CAS示例程序配置

在CAS的主页上,可以看到CAS服务器,和客户端配置的完整过程,根据提示,完全可以配置成功服务器和客户端。同时,在CAS也可以找到服务器端的程序和客户端的程序,都是已经配置好的,对于初步学习来说,完全可以直接取来配置测试。以下就是直接使用CAS官方提供的示例服务器和示例客户端配置一个单点登录的示例:

Jasig主页:http://www.jasig.org/cas/download

使用CAS框架快速实现单点登录_第1张图片

 

第一步:先下载cas的服务器端支持包:cas-server3.xx.zip

    

第二步:下载cas有客户端支持包。Cas-client.zip

      

使用CAS框架快速实现单点登录_第2张图片

第三步:下载客户端的示例程序

以下是下载的超连接:

https://wiki.jasig.org/display/CASC/JA-SIG+Java+Client+Simple+WebApp+Sample

使用CAS框架快速实现单点登录_第3张图片

第四步:解压cas-server.zip如下图,并从中找到服务器端war文件

服务器端程序一般不用我们完成,但需要做一点小小的修改,cas的服务器端程序由spring+Spring web flow+cas写成。全部使用spring配置文件。它就是一个用户登录验证、发售票据的中心。相当于Ticket Office(售票处)

使用CAS框架快速实现单点登录_第4张图片

第五步:分别建立三个虚拟主机。

      一个是服务器主机,和两个客户端虚拟主机

      1、修改etc/hosts文件:

      使用CAS框架快速实现单点登录_第5张图片

      2、修改tomcat/conf/server.xml配置文件,增加虚拟主机目录,同时将服务器的端口修改成80。

      使用CAS框架快速实现单点登录_第6张图片

      3、在tomcat的根目录下,分别建立三个目录,即server、bbs、news。

            在三个目录下,分别都建立一个ROOT(ROOT是tomcat的主默认主页目录)文件夹。

            将cas-server.xx.war解压后散放到/tomcat/server/ROOT目录下。如下图:

           使用CAS框架快速实现单点登录_第7张图片注意目录结构,是散放到ROOT的目录下。

第六步:先测试服务器是否可以正常使用

 启动tomcat,在地址栏输入:

 http://www.server.com

 使用CAS框架快速实现单点登录_第8张图片

 登录:

使用CAS框架快速实现单点登录_第9张图片

请先保证在单个服务器上登录可以登录成功。如果不能登录成功,请重复前面的配置。

第七步:配置两个客户端

      将下载的文件分别解压到tomcat/bbs/ROOT目录下和tomcat/news/ROOT目录下。注意是散放到ROOT目录下。

由于在mywebapp.war中并没有放置依赖的jar文件,所以,还需要我们添加它所依赖的jar文件,为此我为大家准备了已经放放置好的

mywebapp.war文件。

放置好的目录结构如下:

使用CAS框架快速实现单点登录_第10张图片(bbs程序同,不再上图)

WEB-INF/lib目录下的包如下:

这两个包,在cas-client.rar文件中都可以找到。

此处,你可以启动一个tomcat,如果启动成功,则进入下一步。

第八步:修改客户端的配置文件

      当使用登录客户端受保护的资源时,如果发现还没有登录,则会重定向到服务器(售票处)请求登录验证,登录成功后即会获取一张票据,服务器会携带这张票据再重定向到客户端页面

修改客户端的web.xml配置文件,让它在登录时,知道去哪台服务器:

注意将里面的https全部修改成http。

修改的部分主要分为两块:

1:修改登录重定向过虑器,它用于保护受保护的资源,如果发面用户在访问受保护的资源时,用户还没有登录,则会重定向到服务器,要求用户登录:

使用CAS框架快速实现单点登录_第11张图片

2、修改验证过虑器,它的主要作用是接收服务器发送的Ticket,进行验证

使用CAS框架快速实现单点登录_第12张图片

其他没有说明的部分,请不要修改。

请参考上面的实现配置另一个项目的web.xml文件。

第九步:测试登录

      目前还不能实现单点登录。但可以对任意的一个客户端进行登录验证。

1、在地址栏输入

http://www.news.com

使用CAS框架快速实现单点登录_第13张图片

点击访问受保护的页面:got to protected area

将重定向到服务器请求登录:

使用CAS框架快速实现单点登录_第14张图片

登录成功后即重定回原请求页面:

使用CAS框架快速实现单点登录_第15张图片

第十步:配置可以单点登录

      Cas服务器都是用spring配置文件配置而成。且使用了cookie技术。在ticketGrantingTicketCookieGenerator.xml文件中,保存了cookie的生成方式及有效时间。

使用CAS框架快速实现单点登录_第16张图片注意,这是在server服务器上的spring配置文件。

打开此文件,修改成以下内容:

说明:false是指支持http协议登录。默认为true,支持https登录。

       3600中cookie保存在本地的时间,默认为-1即浏览器缓存。

   cookiePath是cookie的path设置。

第十一步:单点登录测试

      修改了上面文件后,即可测试是否可以从一个点的登录,即可以访问两个网站时都显示先登录的姓名:

      先输入http://www.news.com

使用CAS框架快速实现单点登录_第17张图片

      登录

使用CAS框架快速实现单点登录_第18张图片

登录成功:

使用CAS框架快速实现单点登录_第19张图片

在地址栏直接输入:www.bbs.com

使用CAS框架快速实现单点登录_第20张图片可以看到,显示的是news用户名,即之前在www.news.com上登录的用户名,即实现单点登录。

 

2、在Eclipse环境中开发CAS

      为了文件书写代码,我们需要将cas-server导入到eclipse环境中:

1建立一个新的web项目:

使用CAS框架快速实现单点登录_第21张图片

2将cas-server.war文件中的文件放到Eclipse web项目的相关目录下:

使用CAS框架快速实现单点登录_第22张图片使用CAS框架快速实现单点登录_第23张图片注意:src下的文件需要到cas-server/WEB-INF/classes下单独copy.

 

3、重新设置虚拟目录

将原来的虚拟主机目录删除。配置新的虚拟目录,并通过<Context/>形式指定Eclipse下的项目为项目的根目录,如下:

Host的name保先原地址不变,这样就没有必要修改hosts文件了。在tomcat的根目录下,创建一个casServer目录,里面什么也不用放即可。

通过<Context path=//>指定根目录为Eclipse中的项目。

测试是否可以访问。

 

4、建立客户端项目-两个

使用CAS框架快速实现单点登录_第24张图片项目中使用了jstl.jar包,应该放到WEB-INF/lib目录下。

 

5、配置两个项目的虚拟目录

      将原来配置的两个虚拟主机目录删除,然后使用<Context/>配置新的项目。如下:

     使用CAS框架快速实现单点登录_第25张图片

6、准备cas-client.jar包

7、声明受保护的资源并在web.xml中配置

      此处可以完全参考原有客户端项目的配置。(略)

运行测试进入下一步。

3、在客户端面获取用户的基本信息-name(重点)

      1、Cas在登录成功后会通过socket向客户端传递用户信息。一般情况下就是用户名而已。

      2、cas客户端的request对象是经过包装的org.jasig.cas.client.util.HttpServletRequestWrapperFilter$CasHttpServletRequestWrapper

      3、在用户成功后,客户端的过虑器,会将用户的信息封装成java.security.Principal的子封装到Assertion后放到Session中。

      关于Assertion对象的结构图,请见后面的部分。

以下是在客户端的页面上如何获取用户名信息的多种方式:

使用CAS框架快速实现单点登录_第26张图片

那么,如何才可以返回更多用户的信息呢。如返回用户和用户名和id?后面的章节将会讲到。

 

4、修改服务器的登录验证规则

1、使用配置的用户名和密码

目前服务器的验证方式为用户和密码相同即为登录成功。这并不符合我们的业务需求,大多数应用都是通过查询数据库获取用户名和密码的。那我们又如何设置从数据库获取用户名和密码呢?

      为了便于理解,我先将服务器的登录方式修改为配置的。而后再修改成通过数据库进行验证的。

      修改配置文件:/WEB-INF/deployerConfigContext.xml -

   在这个配置文件中,保存了多个用户认证登录的验证方式,只要有一种验证通过即可以登录成功。

      <property name= "authenticationHandlers">...属性内部通过配置若干的AuthenticationHandler的子类可以改变登录认证方式。也可以增加认证方式,只要在用户登录时,有一种登录方式是可行的,即可以登录成功。

   认证类的继承关系如下:

使用CAS框架快速实现单点登录_第27张图片

   默认已经配置了最后一个类即SimpleTestUsernamePasswordAuthenticationHandler。此类验证用户名和密码是否一致。

   Cas为了我们提供了可以直接配置用户名和密码类:org.jasig.cas.adaptors.generic.AcceptUsersAuthenticationHandler。此类并没有包含到默认的服务器代码中,需要到cas-server/models/中查找名为:cas-server-support-generic-3.4.11.jar的jar文件,并添加到WEB-INF/lib目录下。

如下图所示,正是名为generic的jar包。(后面如果需要数据库连接的,还需要jdbc的jar包)

使用CAS框架快速实现单点登录_第28张图片

将此jar包放到lib目录下后,类的层次关系如下:

使用CAS框架快速实现单点登录_第29张图片

此类接收一个map当作用户名和密码的集合列表:

/WEB-INF/deployerConfigContext.xml配置文件中的<property name= "authenticationHandlers">元素中,删除原来的用户名与密码相同的认证,即:

<!-- <bean

               class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /> -->

然后在相同的位置配置:AcceptUsersAuthenticationHandler类:

配置如下:

使用CAS框架快速实现单点登录_第30张图片

登录测试:

使用CAS框架快速实现单点登录_第31张图片

直接在配置文件中配置用户名和密码:可选的加密

<!-- 通用的认证管理器,通过一个文件或是一个map配置用户名和密码 -->

            <beanclass="org.jasig.cas.adaptors.generic.AcceptUsersAuthenticationHandler">

               <propertyname="users">

                  <map>

                     <!-- 1234 MD5:4321 -->

                     <entrykey="tom"value="81dc9bdb52d04dc20036dbd8313ed055"/>

                     <!-- 4321: md5 : -->

                     <entrykey="Jack"value="d93591bdf7860e1e4ee2fca799911215"></entry>

                  </map>

               </property>

               <!-- 可选的使用md5进行加密 -->

               <propertyname="passwordEncoder">

                  <beanclass="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">

                     <constructor-argvalue="MD5"></constructor-arg>

                     <propertyname="characterEncoding"value="UTF-8"></property>

                  </bean>

               </property>

            </bean>      

2、自己书写验证规则-颠倒的用户名和密码

      1、所有的认证类都是AuthenticationHandler的子类,于是我们可以自己开发基于任何规则的验证。

  2、基于用户名和密码的认证,则应该继承AbstractUsernamePasswordAuthenticationHandler

   3、代码如下:

package cn.itcast.handler;

import org.jasig.cas.authentication.handler.AuthenticationException;

import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;

import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;

/**

 * 颠倒的用户名和密码即可以登录

 *@author 传智播客-金云龙

 *credentials:凭据

 */

publicclass MyReverseUsernamePasswordHandler

         extends AbstractUsernamePasswordAuthenticationHandler {

   protectedboolean authenticateUsernamePasswordInternal(

         UsernamePasswordCredentials credentials)

         throws AuthenticationException {

      String name = credentials.getUsername();

      String pwd = credentials.getPassword();

      if(name.equals(new StringBuffer(pwd).reverse().toString())){

         System.err.println("登录成功.....");

         returntrue;

      }

      System.err.println("登录不成功。。。");

      returnfalse;

   }

}

   4、配置如下:

   将原有的配置用户名和密码的方式删除,然后只配置上面的MyReverseUsernamePasswordHandler类:

使用CAS框架快速实现单点登录_第32张图片

 

 

5配置使用JDBC的登录 ,认证句柄AuthenticationHandler

      这儿需要添加新的包:,需要同时添加mysql-connection.jar文件,以连接数据库。

      添加此包以后,AuthenticationHandler的层次结构为:

使用CAS框架快速实现单点登录_第33张图片

通过配置可以修改CAS的登录认证方式默认的登录方式为用户名与密码一致即可以登录。

      <property name= "authenticationHandlers">...属性内部通过配置若干的AuthenticationHandler的子类可以改变登录认证方式。也可以增加认证方式,只要在用户登录时,有一种登录方式是可行的,即可以登录成功。

   所有的配置方式,在CAS的官方网站上均有详细的说明。

   修改配置文件:/WEB-INF/deployerConfigContext.xml

   在这个配置文件中,保存了多个用户认证登录的验证方式,只要有一种验证通过即可以登录成功。

   以下是在配置文件中直增加用户名和密码的登录的方式,其中设置了MD5对密码进行加密。默认的加密方式为PlainTextPasswordEncoder.即不加密。

1使用数据库指定表名,字段名的登录认证方式

      A:创建数据库:

createdatabase ssocharacterset UTF8;

use sso;

createtable users(

  id varchar(32),

  namevarchar(30),

  pwd varchar(32)

);

insertinto usersvalues('U001','Jack','1234');

insertinto usersvalues('U002','Rose','4321');

B:在/WEB-INF/deployerConfigContext.xml文件的最下面,创建数据连接:

<beanid="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">

      <propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>

      <propertyname="url"value="jdbc:mysql:///sso?characterEncoding=UTF-8"/>

      <propertyname="username"value="root"/>

      <propertyname="password"value="1234"/>

 </bean>

C:使用dataSource数据源,因为后面的查询需要一个数据源的支持:

<!-- 使用数据库验证,且数据库的密码是经过md5加密的 -->

            <beanclass="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">

               <propertyname="dataSource"ref="dataSource"/>

               <propertyname="tableUsers"value="users"/>

               <propertyname="fieldUser"value="name"/>

               <propertyname="fieldPassword"value="pwd"/>

               <propertyname="passwordEncoder"> <!--可选的加密-->

                  <beanclass="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">

                     <constructor-argvalue="MD5"/>

                     <propertyname="characterEncoding"value="UTF-8"></property>

                  </bean>

               </property>

         </bean>

MySql的表结构如下:

使用CAS框架快速实现单点登录_第34张图片

 

2使用查询sql验证用户登录的方式

<!-- 使用sql语句 -->

            <beanclass="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">

               <propertyname="dataSource"ref="dataSource"/>

               <propertyname="sql"value="select u2_pwd from users2 where u2_name=?"/>

               <propertyname="passwordEncoder">

                  <beanclass="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">

                     <constructor-argvalue="MD5"/>

                     <propertyname="characterEncoding"value="UTF-8"></property>

                  </bean>

               </property>

            </bean>

表结构如下:

其实,上面的MD5验证,完全可以使用同一个。

 

上面的程序非常简单,完全可以通过查看源代码和继承结构图来了解认证和登录方式。且不需要书写任何Java代码。

 

3、增加一个自己的类,接收用户名和密码,且必须要倒序的用户名与密码相同时才可以登录

      只是简单的做一个示例,没有具体用途:

package cn.itcast.pubs;

import org.jasig.cas.authentication.handler.AuthenticationException;

import org.jasig.cas.authentication.handler.AuthenticationHandler;

import org.jasig.cas.authentication.principal.Credentials;

import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

 * 实现自己的登录验证器

 *@author金云龙

 *@version 1.0 2012-2-18

 */

publicclass MyAuthenticationHandlerimplements AuthenticationHandler{

   Loggerlog = LoggerFactory.getLogger(MyAuthenticationHandler.class);

   /**

   * 只有supports方法返回真的情况下,才会进入此方法进行验证。

   */

   publicbooleanauthenticate(Credentials credentials)

         throws AuthenticationException {

      UsernamePasswordCredentials cre =   //将凭据强转成用户名和密码的凭据

            (UsernamePasswordCredentials) credentials;

      String name = cre.getUsername();      //获取用户名

      String pwd = cre.getPassword();      //获取密码

      name =new StringBuffer(name).reverse().toString();//反转一个字符串

      if(name.equals(pwd)){

         log.info("验证通过:"+name+","+pwd);

         returntrue;

      }

      returnfalse;

   }

   /**

   * 必须要继承supports方法,即是否支持这种凭据

   */

   publicboolean supports(Credentials credentials) {

      boolean boo = credentials.getClass() //验证是否是相同的类

                     ==UsernamePasswordCredentials.class;

      boo = boo ||      //验证是否是某种类的的子类

            UsernamePasswordCredentials.class.

            isAssignableFrom(credentials.getClass());

      log.info("是否是用户名密码的认证器:"+boo);

      return boo;

   }

}

将上面的类,也一样配置到<property name=authencationHandlers>的<list>元素中:

<beanclass="cn.itcast.pubs.MyAuthenticationHandler"/>

即可以实现以下用户名密码的登录:用户名:abc,密码: cba。

 

6返回用户的更多信息

1、如返回用户名和用户ID

 

配置返回用户的ID而不是用户名用户凭据Credentials用户对象(被代理人) Principal

            Credentials :用于定义用户以什么样的凭据登录普通的凭据为用户名和密码凭据。

            Principal :用户登录成功以后,使用Credentials转换成Principal。Principal中包含了用户的用户名(默认)及一些其他属性信息。

Credentials及Pincipal关系的图示,及Principal内部结构图示:

 

 

使用CAS框架快速实现单点登录_第35张图片

使用CAS框架快速实现单点登录_第36张图片      


Principal的源代码如下:(注意此Principal不是java.security.Principal类。而是由cas自己定义的一个接口)

package org.jasig.cas.authentication.principal;

import java.io.Serializable;

import java.util.Map;

publicinterfacePrincipalextends Serializable {

   String getId();

   Map<String, Object> getAttributes();

}

为了可以返回用户的ID,我们可以修改deployerConfigContext.xml文件中的<property name=credentialsToPrincipalResolvers>中的<list/>元素中的Bean。

此时,为了可以返回用户的ID,我们必须要手工创建一个CredentialsToPrincipalResolver的子类。

具体代码如下:

package cn.itcast.pubs;

import javax.sql.DataSource;

import org.jasig.cas.authentication.principal.Credentials;

import org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver;

import org.jasig.cas.authentication.principal.Principal;

import org.jasig.cas.authentication.principal.SimplePrincipal;

import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;

import org.springframework.jdbc.core.JdbcTemplate;

publicclass MyCredentialsToPrincipalResolverimplementsCredentialsToPrincipalResolver {

   private DataSourcedataSource;//查询数据库用

   @Override

   public PrincipalresolvePrincipal(Credentials credentials) {

      UsernamePasswordCredentials up =//强制类型转换

            (UsernamePasswordCredentials) credentials;

      String name = up.getUsername();

      String pwd = up.getPassword();

      String sql ="select id from users2 where u2_name=? and u2_pwd=?";//查询id - 一般只根据用户查询即可

      String id =null;

      try{

         id=new JdbcTemplate(getDataSource()).queryForObject(sql, String.class, name,pwd);

         if(id!=null){

            Principal p =newSimplePrincipal(id);//封装成包含id的Principal对象

            return p;

         }

      }catch(Exception e){

         e.printStackTrace();

      }

      returnnull;

   }

 

   @Override

   publicboolean supports(Credentials credentials) {

      boolean boo = //判断是否是用户和密码凭据

            UsernamePasswordCredentials.class.isAssignableFrom(credentials.getClass());

      return boo;

   }

   public DataSource getDataSource() {

      returndataSource;

   }

 

   publicvoid setDataSource(DataSource dataSource) {

      this.dataSource = dataSource;

   }

}

(在使用了ID的查询以后,应该只使用数据库进行验证。)

在配置文件中配置如下:

      配置文件为/WEB-INF/deployerConfigContext.xml

<propertyname="credentialsToPrincipalResolvers">

         <list>

            <bean class="cn.itcast.pubs.MyCredentialsToPrincipalResolver">

               <property name="dataSource" ref="dataSource"></property>

            </bean>

            <!--

            <bean

               class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />

            -->

            <bean

               class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver"/>

         </list>

      </property>

测试并运行,此时返回的应该是用户的id了。

2、返回更多用户的信息

   服务器,在返回给客户端用户信息时,默认只返回用户名(我们已经修改成ID).但有时我们需要更多的属性信息,如用户名。

则应做如下修改:

 

用户登录成功以后,CAS使用一个credentialsToPrincipalResolverscredentials转成Principal对象,此对象只有一个实现类如下:

SimplePrincipal的构造方法接收两个参数,一个是用户的id,一个为用户的其他属性。用户的ID默认为用户登录时使用的用户名,前面第4点已经讲过如何将用户的name换成用户的id返回给客户端。为了给客户端返回更多的属性,我们必须要给Principal的构造方法传递第二个参数,它是一个Map<String,Object>类型。

具体代码如下:

使用CAS框架快速实现单点登录_第37张图片

上图通过给SimplePrincipal传递第二个构造参数设置了更多的属性。

但,它并不会马上显示到客户端,如果要显示到客户端,因为服务器验证成功以后,是通过xml形式将结果传递给客户端的,xml的生成由casServiceValidationSuccess.jsp文件负责。它的具体构造应该是以下形式:

<cas:serviceResponse

   xmlns:cas='http://www.yale.edu/tp/cas'>

   <cas:authenticationSuccess>

      <cas:user>U001</cas:user>

      <cas:attributes>

            <cas:pwd>1234</cas:pwd>

            <cas:username>Jack</cas:username>

      </cas:attributes>

   </cas:authenticationSuccess>

</cas:serviceResponse>

在上面的代码中,cas:attributes元素是我自己添加的客户端的的Filter在接收到上述的XML以后,会将css:attributes中的属性解析出来,放到AttirubtePrincipal的attributes属性中去(或是放到Asseration的attributes中去,两个只会放一个)。

默认情况下,将所有属性信息放到AttributePrincipal中去,所以在客户端的页面上可以通过以下方式获取值:

使用CAS框架快速实现单点登录_第38张图片

 

所以,组成上面的<cas :attributes>元素中的内容,就成了如何传递更多属性的关键,在修改了MyCredentialsToPrincipalResolver的代码以后,然后还必须要修改casServiceValidationSuccess.jsp的代码如下:

以下是源代码:

<%@pagesession="false"contentType="text/xml; charset=UTF-8"%><%@taglib

   prefix="c"uri="http://java.sun.com/jsp/jstl/core"%><%@taglib

   uri="http://java.sun.com/jsp/jstl/functions"prefix="fn"%><cas:serviceResponse

   xmlns:cas='http://www.yale.edu/tp/cas'>

   <cas:authenticationSuccess>

      <cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>

      <c:iftest="${not empty pgtIou}">

         <cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>

      </c:if>

      <c:iftest="${fn:length(assertion.chainedAuthentications) > 1}">

         <cas:proxies>

            <c:forEachvar="proxy"items="${assertion.chainedAuthentications}"

               varStatus="loopStatus"begin="0"

               end="${fn:length(assertion.chainedAuthentications)-2}"step="1">

               <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>

            </c:forEach>

         </cas:proxies>

      </c:if>

      <cas:attributes>

         <c:forEach

            items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"

            var="attr">

            <cas:${attr.key}>${attr.value}</cas:${attr.key}>

         </c:forEach>

      </cas:attributes>

   </cas:authenticationSuccess>

</cas:serviceResponse>

 

然后修改deployerConfigContext.xml文件将最后一个配置项:serviceRegistryDao中的所有属性全部删除或是注销。

这个bean中的RegisteredServiceImpl的ignoreAttributes属性将决定是否添加attributes属性内容,默认为false:不添加,只有去掉这个配置,
cas server才会将获取的用户的附加属性添加到认证用的Principal的attributes中去

 

然后即可以在页面上通过以下方式获取用户的其他属性:

   <%

      Assertion assertion = AssertionHolder.getAssertion();

      AttributePrincipal ap = assertion.getPrincipal();  //获取AttributePrincipal对象,这是客户端对象

      String name = ap.getName();

      Map<String,Object> att =ap.getAttributes();     //获取属性值,为一个Map类型。

      out.print("<br/>"+name);

      out.print("<br/>"+att);

%>

7、处理中文

casServiceValidationSuccess.jsp页面默认编码格式为ISO-8859-1,且在表单提交到客户端页面时,也使用IS0进行编码,为了处理中文,可以在页面上使用URLEncoder对需要传递的中文时行UTF-8编码,然后从客户端取得数据时,再做URLDecoder解码:

casServiceValidationSuccess.jsp页面,真是一个奇怪的页面,由于cas使用手工解析(没有使用任何dom解析,硬编码识别标标签的开始和标签的结束)xml的方式解析xml文件,所有,在修改此文件时,一定要加以注意:

使用CAS框架快速实现单点登录_第39张图片

上图的红框部分,必须要紧凑一些,否则会出现解析错误。

以下是源代码

<%@pagesession="false"contentType="text/xml; charset=UTF-8" import="java.net.URLEncoder"%>

<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>

<%@tagliburi="http://java.sun.com/jsp/jstl/functions"prefix="fn"%>

<cas:serviceResponsexmlns:cas='http://www.yale.edu/tp/cas'>

   <cas:authenticationSuccess>

      <cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>

      <c:iftest="${not empty pgtIou}">

         <cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>

      </c:if>

      <c:iftest="${fn:length(assertion.chainedAuthentications) > 1}">

         <cas:proxies>

            <c:forEachvar="proxy"items="${assertion.chainedAuthentications}"

               varStatus="loopStatus"begin="0"

               end="${fn:length(assertion.chainedAuthentications)-2}"step="1">

               <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>

            </c:forEach>

         </cas:proxies>

      </c:if>

      <cas:attributes>

         <c:forEachitems="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"var="attr">

               <c:setvar="val"value="${attr.value}"/>

               <cas:${attr.key}><%=URLEncoder.encode((String)pageContext.getAttribute("val"),"UTF-8")%></cas:${attr.key}>

         </c:forEach>

      </cas:attributes>

   </cas:authenticationSuccess>

</cas:serviceResponse>

 

经过编码以后的XML数据如下

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>

   <cas:authenticationSuccess>

      <cas:user>U003</cas:user>

      <cas:attributes>

               <cas:pwd>1111</cas:pwd>

               <cas:username>%E5%BC%A0%E4%B8%89</cas:username>

      </cas:attributes>

   </cas:authenticationSuccess>

</cas:serviceResponse>

 

可见,对中文进行了UTF-8编码。

 

在客户端使用URLDecoder进行解码:

以下:

   <%

      Assertion assertion = AssertionHolder.getAssertion();

      AttributePrincipal ap = assertion.getPrincipal();

      String id = ap.getName();

      Map<String,Object> att =ap.getAttributes();

      out.print("<br/>"+id);

      out.print("<br/>"+att);

      String name = URLDecoder.decode(""+att.get("username"), "UTF-8");

      out.println("<br/>"+name);

   %>

显示效果如下:


 

8、通过监听器将从服务器上返回的数据封装成自己的对象放到Session中去

   

   从服务器返回信息成功后,将以_const_cas_assertion_为key将Assertion对象放到Session中去。知道了这一点,即可以监听Session的属性添加事件:

源代码:

package cn.itcast.listener;

import java.net.URLDecoder;

import java.util.Map;

import javax.servlet.http.HttpSessionAttributeListener;

import javax.servlet.http.HttpSessionBindingEvent;

import org.jasig.cas.client.util.AbstractCasFilter;

import org.jasig.cas.client.validation.Assertion;

/**

 * 通过监听器将从服务器上返回的信息放到Session中

 *@author 传智播客-金云龙

 */

publicclass AssertionListenerimplements HttpSessionAttributeListener {

   publicvoid attributeAdded(HttpSessionBindingEvent se) {

      if (se.getName().equals(AbstractCasFilter.CONST_CAS_ASSERTION)) {

         System.err.println("添加了某属性.....");

         //不可以使用工具类,只可以使用session获取对象

         Assertion ass = (Assertion)se.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);

         Map<String, Object> user = ass.getPrincipal().getAttributes();

         se.getSession().setAttribute("user", user);

         try {

            for (String key : user.keySet()) {

               //因为服务器上转码,所以此外解码

               user.put(key,

                     URLDecoder.decode("" + user.get(key), "UTF-8"));

            }

         }catch (Exception e) {

            e.printStackTrace();

         }

      }

   }

   publicvoid attributeRemoved(HttpSessionBindingEvent se) {

   }

   publicvoid attributeReplaced(HttpSessionBindingEvent se) {

   }

}

配置到客户端的web.xml中去:

<listener>

      <listener-class>cn.itcast.listener.AssertionListener</listener-class>

</listener>

然后即可以在客户端的页面上通过以下方式获取值:


 

9认定对象Assertion

      里面又包含了Principal对象及创建时间、过期时间、和用户的ID或是名称。

      首先,用户的ID或是用户名,在CAS的客户端程序中,可以通过request.getRemoteUser()的方式获取得到。Spring是通过在过虑器中,包装HttpRequest的方式实现的,其实,仍然是从Assertion中获取得到的数据。

      在CAS服务端,用户注册成功以后,CAS服务器端是通过POST方式给客户端传递一个XML数据的方式获取得到数据的。

Assertion的源代码如下:

publicinterfaceAssertionextends Serializable {

 

   /**

    * The date from which the assertion is valid from,有效时间从什么时间开始

    */

   Date getValidFromDate();

 

   /**

    * The date which the assertion is valid until,在效时间,到什么时间结束。

    */

   Date getValidUntilDate();

 

   /**

    * The key/value pairs associated with this assertion,一组属性值

    */

   Map getAttributes();

 

   /**

    * The principal for which this assertion is valid,被代理的对象,此对象中,又包含了一个ID和一组属性值

    */

   AttributePrincipal getPrincipal();

}

通过上面的源代码,可以知道Assertion和Principal的关系如下:

使用CAS框架快速实现单点登录_第40张图片

 

 


 

 

 



从Assertion中获取信息,可以查看示例客户端的getpt.jsp页面上的代码:

方法1、从Session中获取Assertion对象

Assertion assertion1 = (Assertion) session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);

方法2、通过AssertionHolder的静态方法获取Assertion

Assertion assertion2 = AssertionHolder.getAssertion();

 

10、单点注销

在一个客户端注销以后,应该将其他所有站点的登录Session全部注销:

 1、发出注销申请

使用CAS框架快速实现单点登录_第41张图片

 

 

 

 

 

 

 

 

 

 

在CAS的客户端,CAS使用一个Map维护了所有登录用户的Session和TG(凭据)。CAS服务器将依次将客户端发送请求,被CAS客户端的注销过虑器拦截到,注销过虑器完成客户端Session的注销工作。

所以,为了实现单点注销,必须要将客户端的单点注销过虑器也打开:

      找到cas客户端web.xml文件,启用以下代码:

      <!-- Sign out not yet implemented,单点注销 -->

      <filter>

            <filter-name>CAS Single Sign Out Filter</filter-name>

            <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>

      </filter>

      <filter-mapping>

            <filter-name>CAS Single Sign Out Filter</filter-name>

            <url-pattern>/*</url-pattern>

      </filter-mapping>

只要打开上面的过虑器,即可以实现单点注销。

 

测试:在CAS服务器端输入 :http://localhost/casServer/logout即可以完成单点注销。

注意,应该是服务器的地址,即http://服务器地址/logout

 

单点注销:

TicketGrantingTicketImpl.logOutOfService方法将会获取所有Ticket,通过遍历,然后调用下面的方法。

AbstractWebApplicationService. logOutOfService方法将会出送一段XML文本给每一个客户端。

 

以下是TicketGrantingTicketImpl的片段代码:

privatevoid logOutOfServices() {

       for (final Entry<String, Service> entry :this.services.entrySet()) {//遍历所有注册过的客户端

 

           if (!entry.getValue().logOutOfService(entry.getKey())) {  //调用注销服务

               LOG.warn("Logout message not sent to [" + entry.getValue().getId() +"]; Continuing processing...");  

           }

       }

}

以下是AbstractWebApplicationService片段代码:

publicsynchronizedbooleanlogOutOfService(final String sessionIdentifier) {

       if (this.loggedOutAlready) {

           returntrue;

       }

 

       LOG.debug("Sending logout request for: " + getId());

      //组织一段XML文本

       final String logoutRequest ="<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\""

           + GENERATOR.getNewTicketId("LR")

           + "\" Version=\"2.0\" IssueInstant=\"" + SamlUtils.getCurrentDateAndTime()

           + "\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@</saml:NameID><samlp:SessionIndex>"

           + sessionIdentifier +"</samlp:SessionIndex></samlp:LogoutRequest>";

       

       this.loggedOutAlready = true;

       

       if (this.httpClient != null) {

           returnthis.httpClient.sendMessageToEndPoint(getOriginalUrl(), logoutRequest, true);  //发送请

       }

       

       returnfalse;

}

 

11CAS单点登录流程图

使用CAS框架快速实现单点登录_第42张图片

 

 

 

 

 

 

 

 

12、完成自己的登录页面

在开始之前,读者必须了解Spring的MVC框架和Spring WebFlow(页面流)。

   然后将完整的cas-server.war包,部署到MyEclipe工作区中,以便于更新和修改。为此,应该将原来classes目录下的cas的原类打包,放到lib目录下。

修改以下页面,可以实现自定义登录页面的需求。具体jsp的写法略。

使用CAS框架快速实现单点登录_第43张图片

你可能感兴趣的:(java,cas,单点登录)