cas简介

韦棋
2023-12-01

一、概述

central authentication service,中央认证服务。耶鲁大学的一个开源项目,目的是为web应用系统提供一种可靠的单点登录方法。

cas包括两个部分,cas server和cas client。cas server负责对用户的认证;cas client负责处理对客户端受保护资源的访问请求,当登录时,重定向到cas server。

二、使用示例

从官网下载cas-server和cas-client的发行包。在cas-server包里的module目录下有cas-server.war的示例项目。网上搜索cas-client的示例项目mywebapp.war,不太好找。

示例war使用步骤

1. 配置tomcat虚拟主机

在tomcat中server.xml配置3个虚拟主机,两个客户端www.gougou.com和www.maomao.com,一个服务端www.server.com。

<Host name="www.server.com" appBase="server" autoDeploy="true" unpackWARs="true">

</Host>

<Host name="www.gougou.com" appBase="gougou" autoDeploy="true" unpackWARs="true">

</Host>

<Host name="www.maomao.com" appBase="maomao" autoDeploy="true" unpackWARs="true">

</Host>

在window下,将这3个域名和127.0.0.1做映射。在tomcat配置的虚拟主机的目录下,分别放置相应的war包,启动tomcat自动部署项目,然后关闭tomcat,将项目包的文件夹改名为ROOT,这样可以直接访问。在客户端的项目的lib目录下复制入cas-client-core的jar和commons-loggings的jar。启动tomcat,测试3个域名是否可以访问。可能出现java版本和cas-server版本的兼容问题,一般是java版本过高,考虑降低java版本。

2. 修改客户端的配置文件

修改客户端的web.xml

         <filter>

                   <filter-name>CAS Authentication Filter</filter-name>

                   <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>

                   <init-param>

                            <param-name>casServerLoginUrl</param-name>

                            <param-value>http://www.server.com:8081/login</param-value>

                   </init-param>

                   <init-param>

                            <param-name>serverName</param-name>

                            <param-value>http://www.maomao.com:8081</param-value>

                   </init-param>

                   <init-param>

                            <param-name>renew</param-name>

                            <param-value>false</param-value>

                   </init-param>

                   <init-param>

                            <param-name>gateway</param-name>

                            <param-value>false</param-value>

                   </init-param>

         </filter>

        

         <filter>

                   <filter-name>CAS Validation Filter</filter-name>

                   <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>

                   <init-param>

                            <param-name>casServerUrlPrefix</param-name>

                            <param-value>http://www.server.com:8081</param-value>

                   </init-param>

                   <init-param>

                            <param-name>serverName</param-name>

                            <param-value>http://www.maomao.com:8081</param-value>

                   </init-param>

                   <!-- <init-param>

                            <param-name>proxyCallbackUrl</param-name>

                            <param-value>https://localhost:8443/mywebapp/proxyCallback</param-value>

                   </init-param>

                   <init-param>

                            <param-name>proxyReceptorUrl</param-name>

                            <param-value>/mywebapp/proxyCallback</param-value>

                   </init-param> -->

         </filter>

3. 修改服务端配置文件,设置为单点登录

cas服务器使用spring配置文件,使用cookie技术。ticketGrantingTicketCookieGenerator.xml设置cookie相关的。

         <bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"

                   p:cookieSecure="false"

                   p:cookieMaxAge="3066"

                   p:cookieName="CASTGC"

                   p:cookiePath="/" />

修改cookie后,测试单点登录。

三、在项目开发中使用cas

使用eclipse开发,创建3个项目。一个casServer,和两个web客户端服务项目。分别配置两个客户端项目的web.xml中,的验证登录网址,以及服务端配置文件中cookie的配置,见上。

1. 在客户端获取登录用户名

4种方式

         <dd>

                   方式1:

                   <%= request.getRemoteUser() %>

         </dd>

         <dd>

                   方式2:

                   <%  

                            Principal p = request.getUserPrincipal();

                            out.print(p);

                   %>

         </dd>

         <dd>

                   方式3:

                   <%

                            Assertion assertion = AssertionHolder.getAssertion();

                            p = assertion.getPrincipal();

                            String user = p.getName();

                            out.print(user);

                   %>

         </dd>

         <dd>

                   方式4:

                   <%

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

                            p = assertion.getPrincipal();

                            user = p.getName();

                            out.print(user);

                   %>

         </dd>

2. 修改服务器登录验证规则

在服务器cas的deployerConfigContext.xml中,配置用户认证登录方式,可以配置多个,只要其中之一通过就通过。

3. 使用配置的用户名和密码

                   <property name="authenticationHandlers">

                            <list>

                                     <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"

                                               p:httpClient-ref="httpClient" />

                                     <bean

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

                                     <bean class="org.jasig.cas.adaptors.generic.AcceptUsersAuthenticationHandler">

                                               <property name="users">

                                                        <map>

                                                                 <entry key="maozi" value="tom"></entry>

                                                                 <entry key="gouzi" value="jim"></entry>

                                                        </map>

                                               </property>

                                     </bean>

                            </list>

                   </property>

4. 自定义验证规则

所有的认证类都继承自AuthenticationHandler。自定义的基于用户名和密码的验证类继承AbstractUsernamePasswordAuthenticationHandler。

编写MyUsernamePasswordAuthenticationHandler类

/**

 * 自定义的用户名密码认证类

 * 认证规则,密码是用户名的后两位,用户名长度需要大于3位

 * @author Administrator

 *

 */

public class MyUsernamePasswordAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {



         @Override

         protected boolean authenticateUsernamePasswordInternal(UsernamePasswordCredentials credentials)

                            throws AuthenticationException {

                   // TODO Auto-generated method stub

                   //参数合法性判断

                   if(credentials == null){

                            return false;

                   }

                   //获取用户名和密码

                   final String username = credentials.getUsername();

                   final String password = credentials.getPassword();

                   //用户名长度合法性判断

                   if(username.length() < 3){

                            return false;

                   }

                   //密码是否与用户名后两位等同

                   if(password.length() > 0 && password.equals(username.substring(username.length() - 2))){

                            return true;

                   }

                   return false;

         }



}

将自定的认证类配置到配置文件

                   <property name="authenticationHandlers">

                            <list>

                                     <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"

                                               p:httpClient-ref="httpClient" />

                                     <bean class="com.cas.authentication.MyUsernamePasswordAuthenticationHandler"></bean>

                            </list>

                   </property>

5. 使用jdbc连接数据库认证登录

前提导入cas-server-support-jdbc-3.4.11.jar和mysql-connector的jar。配置数据库环境,创建数据库cas,添加表user,插入两个数据,密码采用md5加密。

在deployerConfigContext.xml的最后配置数据库连接dataSource。

    <!-- 配置数据库连接 -->

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

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

    <property name="url" value="jdbc:mysql://localhost:3306/cas?characterEncoding=UTF-8" />

    <property name="username" value="root" />

    <property name="password" value="xxxx" />

    </bean>

可以使用两种方式查询数据库,需要配置在认证处理器里面 

                  <property name="authenticationHandlers">

                            <list>

                                     <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"

                                               p:httpClient-ref="httpClient" />

                                     <!-- <bean

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

                                     <bean class="org.jasig.cas.adaptors.generic.AcceptUsersAuthenticationHandler">

                                               <property name="users">

                                                        <map>

                                                                 <entry key="maozi" value="tom"></entry>

                                                                 <entry key="gouzi" value="jim"></entry>

                                                        </map>

                                               </property>

                                     </bean> -->

                                     <bean class="com.cas.authentication.MyUsernamePasswordAuthenticationHandler"></bean>

                                     <!-- 数据库认证方式1,配置用户登录认证查询的表结构 -->

                                <!-- <bean class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">

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

                                <property name="tableUsers" value="user" />

                                <property name="fieldUser" value="username" />

                                <property name="fieldPassword" value="password" />

                                <property name="passwordEncoder"> 可选的加密

                                                        <bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">

                                                                 <constructor-arg value="MD5"/>

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

                                                        </bean>

                                               </property>

                                </bean> -->

                                  <!-- 数据库认证方式2,使用sql语句查询数据库的方式 -->

                                  <bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">

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

                                            <property name="sql" value="select password from user where username = ?" />

                                            <property name="passwordEncoder">

                                                        <bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">

                                                                 <constructor-arg value="MD5"/>

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

                                                        </bean>

                                               </property>

                                           

                                  </bean>

                            </list>

                   </property>

6. 获取其他用户信息

1)返回用户的id

原始的cas服务器中,通过deployerConfigContext.xml中的<property name="credentialsToPrincipalResolvers">中的bean来将服务器中的credentials转换为pricinpal返回给客户端,为了返回id,需要自定义一个MyCredentialsToPrincipalResolver实现CredentialsToPrincipalResolver接口,并配置到这个文件中。

/**

 * 自定义的credentials转pricinpal的类

 * @author Administrator

 *

 */

public class MyCredentialsToPrincipalResolver implements CredentialsToPrincipalResolver {



         private DataSource dataSource;

        

         public DataSource getDataSource() {

                   return dataSource;

         }



         public void setDataSource(DataSource dataSource) {

                   this.dataSource = dataSource;

         }



         @Override

         public Principal resolvePrincipal(Credentials credentials) {

                   // TODO Auto-generated method stub

                   //非空判断

                   if(credentials == null){

                            return null;

                   }

                   UsernamePasswordCredentials upCredentials = (UsernamePasswordCredentials)credentials;

                   final String username = upCredentials.getUsername();

                   final String password = upCredentials.getPassword();

                   String md5pwd = MD5Util.getPassword(password);

                   String sql = "select id from user where username = ? and password = ?";

                   int id;

                   JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSource());

                   id = jdbcTemplate.queryForInt(sql, username, md5pwd);

                   if(id > 0){

                            Principal principal = new SimplePrincipal(id+"");

                            return principal;

                   }

                   return null;

         }



         @Override

         public boolean supports(Credentials credentials) {

                   // TODO Auto-generated method stub

                   return credentials != null

                                     && UsernamePasswordCredentials.class

                                                        .isAssignableFrom(credentials.getClass());

         }



}

配置文件 

                  <property name="credentialsToPrincipalResolvers">

                            <list>

                                     <!-- <bean

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

                                     <bean class="com.cas.credentials.MyCredentialsToPrincipalResolver">

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

                                     </bean>

                                     <bean

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

                            </list>

                   </property>

7. 获取更多用户信息

基本同上,在服务器返回自定义的principal时,将用户信息以map传入SimplePrincipal的第二个参数。

/**

 * 自定义的credentials转pricinpal的类

 * @author Administrator

 *

 */

public class MyCredentialsToPrincipalResolver implements CredentialsToPrincipalResolver {



         private DataSource dataSource;

        

         public DataSource getDataSource() {

                   return dataSource;

         }



         public void setDataSource(DataSource dataSource) {

                   this.dataSource = dataSource;

         }



         @Override

         public Principal resolvePrincipal(Credentials credentials) {

                   // TODO Auto-generated method stub

                   //非空判断

                   if(credentials == null){

                            return null;

                   }

                   UsernamePasswordCredentials upCredentials = (UsernamePasswordCredentials)credentials;

                   final String username = upCredentials.getUsername();

                   final String password = upCredentials.getPassword();

                   Map<String, Object> map = new HashMap<>();

                   map.put("username", username);

                   map.put("password", password);

                   String address = "上海";

                   map.put("address", address);

                   String md5pwd = MD5Util.getPassword(password);

                   String sql = "select id from user where username = ? and password = ?";

                   int id;

                   JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSource());

                   id = jdbcTemplate.queryForInt(sql, username, md5pwd);

                   if(id > 0){

                            map.put("id", id);

                            Principal principal = new SimplePrincipal(id+"", map);

                            return principal;

                   }

                   return null;

         }



         @Override

         public boolean supports(Credentials credentials) {

                   // TODO Auto-generated method stub

                   return credentials != null

                                     && UsernamePasswordCredentials.class

                                                        .isAssignableFrom(credentials.getClass());

         }



}

修改服务器的casServiceValidationSuccess.jsp文件

<%@ page session="false" %>

<%@ 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:if test="${not empty pgtIou}">

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

                   </c:if>

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

                            <cas:proxies>

                                     <c:forEach var="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文件,将最后一个配置InMemoryServiceRegistryDaoImpl这个bean的所有属性注释掉。

注意,如果出现中文乱码,可以使用URLEncoder和URLDecoder在服务端用utf-8编码,在客户端用utf-8解码。

8. 使用监听器将服务器返回的数据封装成自定义对象放到session中

服务器返回数据后,以_const_cas_assertion_为key将Assertion对象session中去,利用这个,可以通过监听session,将Assertion对象封装成自定义对象放入session返回。

在客户端创建一个User类

略。

在客户端创建一个监听器类

/**

 * 自定义监听session信息的类

 * @author Administrator

 *

 */

public class MySessionListener implements HttpSessionAttributeListener {



         @Override

         public void attributeAdded(HttpSessionBindingEvent event) {

                   // TODO Auto-generated method stub

                   String key = event.getName();

                   if(key.equals(AbstractCasFilter.CONST_CAS_ASSERTION)){

                            //监听获取Assertion对象

                            Assertion assertion = (Assertion) event.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);

                            //获取在服务器端配置的map

                            Map<String, Object> map = assertion.getPrincipal().getAttributes();

                            //遍历map将其中的值放入User对象

                            User user = new User();

                            user.setId(Integer.parseInt((String)map.get("id")));

                            user.setUsername((String)map.get("username"));

                            user.setPassword((String)map.get("password"));

                            user.setAddress((String)map.get("address"));

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

                   }

         }



         @Override

         public void attributeRemoved(HttpSessionBindingEvent event) {

                   // TODO Auto-generated method stub



         }



         @Override

         public void attributeReplaced(HttpSessionBindingEvent event) {

                   // TODO Auto-generated method stub



         }



}

在客户端的web.xml文件中配置这个监听器  

       <listener>

                   <listener-class>com.cas.client.listener.MySessionListener</listener-class>

         </listener>

测试。

9. 单点注销

在cas的客户端,使用一个map维护所有登陆用户的session和tg凭据。cas服务器将依次将客户端发送请求,被cas客户端的注销过滤器拦截到,注销过滤器完成客户端的注销工作。实现单点注销,需要将客户端的单点注销过滤器打开。

<!--

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

-->

使用单点登录服务器的地址www.server.com/logout测试注销。

 类似资料: