zoukankan      html  css  js  c++  java
  • CAS 单点登陆

    一、Tomcat配置SSL

    1. 生成 server key

    以命令方式换到目录%TOMCAT_HOME%,在command命令行输入如下命令: 
    keytool -genkey -alias tomcat_key -keyalg RSA -storepass changeit -keystore server.keystore -validity 3600 
    用户名输入域名,如localhost(开发或测试用)或 hostname.domainname(用户拥有的域名),其它全部以Enter跳过,最后确认,此时会在%TOMCAT_HOME%下生成server.keystore文件。

    2. 将证书导入的JDK的证书信任库中

    这步对于Tomcat的SSL配置不是必须,但对于CAS SSO是必须的,否则会出现如下错误: 
    edu.yale.its.tp.cas.client.CASAuthenticationException: Unable to validate ProxyTicketValidate. 
    导入过程分2步,第一步是导出证书,第二步是导入到证书信任库,命令如下: 
    keytool -export -trustcacerts -alias tomcat_key -file server.cer -keystore server.keystore -storepass changeit 
    keytool -import -trustcacerts -alias tomcat_key -file server.cer -keystore D:/”Program Files”/Java/jdk1.8.0_60/jre/lib/security/cacerts -storepass changeit 
    如果有提示,输入Y就可以了。 
    其它有用keytool命令(列出信任证书库中所有已有证书,删除库中某个证书): 
    keytool -list -keystore %JAVA_HOME%/jre/lib/security/cacerts >t.txt 
    keytool -delete -trustcacerts -alias tomcat_key -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit 
    注意:CAS 建议不要使用 IP 地址,而要使用机器名或域名。

    3.配置Tomcat

    在Tomcat的server.xml配置文件中加入: 

    二、部署CAS Server到Tomcat

    1. 到cas官网下载cas-server http://developer.jasig.org/cas/(我下载的是4.0.0)
    2. 解压压缩文件,在解压后的文件夹内找到/modules/cas-server-webapp-4.0.0.war。将其复制到%Tomcat_Home%webapps下并改名为cas.war
    3. 启动Tomcat,并测试 https://localhost:8443/cas 看是否访问正常(默认输入用户名和密码一致就可以)。 
      注:CAS Server 4.0.0 默认登陆验证方式是 AcceptUsersAuthenticationHandler (老版本好像是SimpleTestUsernamePasswordAuthenticationHandler),默认用户名/密码为 casuser/Mellon(cas/WEB-INF/deployerConfigContext.xml 中找到 id=primaryAuthenticationHandler 的bean查看,里面的map也可以自己增加更多个)。我们通常需要从数据库中取出用户名和密码进行验证,所以我们需要修改 deployerConfigContext.xml,配置我们自己的服务认证方式。

    三、配置服务认证

    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 表示验证失败。

    CAS中还提供了对 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() 成为了子类必须要实现的方法。

    由于实际运用中,最常用的是用户名和密码方式的认证,CAS 提供了针对该方式的实现,如清单 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-{version}-release.zip 包解开后,在 modules 目录下可以找到包 cas-server-support-jdbc-{version}.jar,其提供了通过 JDBC 连接数据库进行验证的缺省实现,基于该包的支持,我们只需要做一些配置工作即可实现 JDBC 认证。

    JDBC 认证方法支持多种数据库,DB2, OracleMySQL, Microsoft SQL Server 等均可,这里以 DB2 作为例子介绍。并且假设DB2数据库名: CASTest,数据库登录用户名: db2user,数据库登录密码: db2password,用户信息表为: userTable,该表包含用户名和密码的两个数据项分别为 userName 和 password。

    1. 配置 DataSource

    打开文件 cas/WEB-INF/deployerConfigContext.xml,添加一个新的 bean 标签,以mysql为例,内容如清单 4 所示:

    <!-- Data source definition --> 
    <bean id="casDataSource" class="org.apache.commons.dbcp.BasicDataSource"> 
        <property name="driverClassName"> 
            <value>com.mysql.jdbc.Driver</value> 
        </property> 
        <property name="url"> 
            <value>jdbc:mysql://localhost:3306/test</value> 
        </property> 
        <property name="username"> 
            <value>sites</value> 
        </property> 
        <property name="password"> 
            <value>123456</value> 
        </property>
    </bean>

    其中 casDataSource 在后面配置 AuthenticationHandler 会被引用,添加数据库驱动程序、连接地址、数据库登录用户名以及登录密码。

    2. 配置 AuthenticationHandler

    CAS 4.0.0 提供了 3 个基于 JDBC 的 AuthenticationHandler,分别为 BindModeSearchDatabaseAuthenticationHandler, QueryDatabaseAuthenticationHandler, SearchModeSearchDatabaseAuthenticationHandler。 
    - BindModeSearchDatabaseAuthenticationHandler 是用所给的用户名和密码去建立数据库连接,根据连接建立是否成功来判断验证成功与否; 
    - QueryDatabaseAuthenticationHandler 通过配置一个 SQL 语句查出密码,与所给密码匹配; 
    - SearchModeSearchDatabaseAuthenticationHandler 通过配置存放用户验证信息的表、用户名字段和密码字段,构造查询语句来验证。

    使用哪个 AuthenticationHandler,需要在 deployerConfigContext.xml 中设置,默认情况下,CAS 4.0.0 使用 AcceptUsersAuthenticationHandler(上面已经提到,需要在 deployerConfigContext.xml 中查看)。

    在 deployerConfigContext.xml 文件中可以找到如下配置:

    <bean id="primaryAuthenticationHandler"
        class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
        <property name="users">
            <map>
                <entry key="casuser" value="Mellon"/>
            </map>
        </property>
    </bean>

    我们可以将其注释掉,换成我们希望的一个 AuthenticationHandler,比如,使用QueryDatabaseAuthenticationHandler 或 SearchModeSearchDatabaseAuthenticationHandler 可以分别选取清单 5 或清单 6 的配置。 
    清单 5. 使用 QueryDatabaseAuthenticationHandler

    <bean id="primaryAuthenticationHandler" 
        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="passwordEncoder" />
    </bean>
    <!-- 密码加密器(可以指定自己实现的加密器) -->
    <bean id="passwordEncoder"
        class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" >
        <constructor-arg name="encodingAlgorithm" value="MD5"/>
        <property name="characterEncoding" value="UTF-8"/>
    </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所示:

    <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 中设置的实际加密器类的引用:

    <bean id="passwordEncoder" class="org.jasig.cas.authentication.handler.MyPasswordEncoder"/>

    这里 MyPasswordEncoder 是根据实际情况自己定义的加密器,实现 PasswordEncoder 接口及其 encode() 方法。 
    注意:DataSource 依赖于 commons-collections-3.2.jar、commons-dbcp-1.2.1.jar、commons-pool-1.3.jar、数据库驱动包、cas对jdbc的支持包cas-server-support-jdbc-4.0.0.jar,需要找到这几个jar包放进 %TOMCAT_HOME%/webapps/cas/WEB-INF/lib 目录。

    然后便可以启动Tomcat使用数据库中的用户名和密码进行登录测试了。

    四、扩展 CAS Server 界面

    最直接的方法,下载官方源码,直接修改 ^_^ 
    我想大家很有必要具备“任何开源项目都可以自己动手,使用通过源码构建项目并运行”的能力,万一你需要在源码上加点东西呢。 
    CAS 4.0 默认的页面存放在 cas/WEB-INF/view/jsp/default 下面,如果是源码项目页面存放在 cas-server-webappsrcmainwebappWEB-INFviewjspdefaultui 目录中。 
    我们来看一下 casWEB-INFclassesdefault_views.properties 文件,如果是源码项目,文件位置为:cas-server-webappsrcmain esourcesdefault_views.properties

    ### Login view (/login)
    casLoginView.(class)=org.springframework.web.servlet.view.JstlView
    casLoginView.url=/WEB-INF/view/jsp/default/ui/casLoginView.jsp
    
    ### Display login (warning) messages
    casLoginMessageView.(class)=org.springframework.web.servlet.view.JstlView
    casLoginMessageView.url=/WEB-INF/view/jsp/default/ui/casLoginMessageView.jsp
    
    ### Login confirmation view (logged in, warn=true)
    casLoginConfirmView.(class)=org.springframework.web.servlet.view.JstlView
    casLoginConfirmView.url=/WEB-INF/view/jsp/default/ui/casConfirmView.jsp
    
    ### Logged-in view (logged in, no service provided)
    casLoginGenericSuccessView.(class)=org.springframework.web.servlet.view.JstlView
    casLoginGenericSuccessView.url=/WEB-INF/view/jsp/default/ui/casGenericSuccess.jsp
    
    ### Logout view (/logout)
    casLogoutView.(class)=org.springframework.web.servlet.view.JstlView
    casLogoutView.url=/WEB-INF/view/jsp/default/ui/casLogoutView.jsp
    
    ### CAS error view
    viewServiceErrorView.(class)=org.springframework.web.servlet.view.JstlView
    viewServiceErrorView.url=/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp
    
    viewServiceSsoErrorView.(class)=org.springframework.web.servlet.view.JstlView
    viewServiceSsoErrorView.url=/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp
    
    ### CAS statistics view
    viewStatisticsView.(class)=org.springframework.web.servlet.view.JstlView
    viewStatisticsView.url=/WEB-INF/view/jsp/monitoring/viewStatistics.jsp
    
    ### Expired Password Error message
    casExpiredPassView.(class)=org.springframework.web.servlet.view.JstlView
    casExpiredPassView.url=/WEB-INF/view/jsp/default/ui/casExpiredPassView.jsp
    
    ### Locked Account Error message
    casAccountLockedView.(class)=org.springframework.web.servlet.view.JstlView
    casAccountLockedView.url=/WEB-INF/view/jsp/default/ui/casAccountLockedView.jsp
    
    ### Disabled Account Error message
    casAccountDisabledView.(class)=org.springframework.web.servlet.view.JstlView
    casAccountDisabledView.url=/WEB-INF/view/jsp/default/ui/casAccountDisabledView.jsp
    
    ### Must Change Password Error message
    casMustChangePassView.(class)=org.springframework.web.servlet.view.JstlView
    casMustChangePassView.url=/WEB-INF/view/jsp/default/ui/casMustChangePassView.jsp
    
    ### Bad Hours Error message
    casBadHoursView.(class)=org.springframework.web.servlet.view.JstlView
    casBadHoursView.url=/WEB-INF/view/jsp/default/ui/casBadHoursView.jsp
    
    ### Bad Workstation Error message
    casBadWorkstationView.(class)=org.springframework.web.servlet.view.JstlView
    casBadWorkstationView.url=/WEB-INF/view/jsp/default/ui/casBadWorkstationView.jsp

    我们根据文件清单对应的文件修改即可。 
    如果你想保留这些页面,拷贝一份default 目录,然后修改其中对应的文件后,在这个配置文件中将对应路径修改便可。

    除此之外,CAS 4.0 还提供了一些协议登录的方式,也就是在不通过登录页面使用账号密码的方式登录,自动授权登录。 
    比如我们可以从其他平台直接免登陆进入原来需要SSO登录后才可以看到的系统,或者做个U盾,插入U盾的电脑,可以直接访问系统,无需登录。 
    官方文档:http://jasig.github.io/cas/4.0.x/protocol/CAS-Protocol.html#cas-protocol

    五、部署客户端应用

    单点登录的目的是为了让多个相关联的应用使用相同的登录过程,本文在讲解过程中构造 2个简单的应用,分别以 casTest1 和 casTest2 来作为示例,它们均只有一个页面,显示欢迎信息和当前登录用户名。这 2 个应用使用同一套登录信息,并且只有登录过的用户才能访问,通过本文的配置,实现单点登录,即只需登录一次就可以访问这两个应用。 
    1. 与 CAS Server 建立信任关系 
    - CAS Server 部署在服务器A 
    - 客户端应用部署在服务BC 
    由于客户端应用与 CAS Server 的通信采用 SSL,因此需要A与BC的jre之间建立信任关系。 
    CAS Server 我们在文章的前面已经配置好的 SSL,现在我们只需要在客户端BC上添加服务器A的证书,操作方法:

    网上找一个这个文件下载下来 InstallCert.java 
    然后在客户端服务器上 
    编译:javac InstallCert.java 
    运行:Java InstallCert serverA:8443 其中8443为SSL端口,serverA为服务器名称或域名 
    并且在接下来出现的询问中输入 1。这样,就将 A 添加到了 B 的 trust store 中(其他客户端服务器一样操作)。

    这里要说一下的是 serverA:8443 这里不要写 {IP地址}:8443,要以域名的方式,如本机 localhost:8443 ,或域名方式 www.baidu.com:443

    2. 配置 CAS Filter 
    在Client工程WEB-INF/lib下添加 cas-client-core-3.3.3.jar 包。 
    修改web.xml如下:

        <!-- ======================== 单点登录/登出 ======================== -->
    
        <!-- 该过滤器用于实现单点登出功能,可选配置。 -->
        <!--    登出地址 https://casserver:8443/cas/logout -->
        <filter>
            <filter-name>CAS Single Sign Out Filter</filter-name>
            <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
        </filter>
    
        <!-- 该过滤器负责用户的认证工作,必须启用它 -->
        <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>https://localhost:8443/cas/login</param-value>
            </init-param>
            <init-param>
                <param-name>serverName</param-name>
                <param-value>http://localhost:8080</param-value>
            </init-param>
        </filter>
    
        <!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
        <filter>
            <filter-name>CAS Validation Filter</filter-name>
            <filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class>
            <init-param>
                <param-name>casServerUrlPrefix</param-name>
                <param-value>https://localhost:8443/cas</param-value>
            </init-param>
            <init-param>
                <param-name>serverName</param-name>
                <param-value>http://localhost:8080</param-value>
            </init-param>
            <init-param>
                <param-name>redirectAfterValidation</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
    
        <!-- 该过滤器负责实现HttpServletRequest请求的包装, 比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 -->
        <filter>
            <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
            <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
        </filter>
    
        <!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。 -->
        <filter>
            <filter-name>CAS Assertion Thread Local Filter</filter-name>
            <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
        </filter>
    
        <filter-mapping>
            <filter-name>CAS Single Sign Out Filter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CAS Authentication Filter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CAS Validation Filter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CAS Assertion Thread Local Filter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <listener>
            <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
        </listener>
        <!-- ======================== 单点登录/登出结束 ======================== -->

    运行Client工程,首次访问任一页面就会跳转到https://localhost:8443/cas/login进行认证。 
    把你的退出链接设置为:https://localhost:8443/cas/logout 即可实现单点登出,如果需要在系统退出后返回指定页面,我们可以修改源码在logout后面增加参数来实现。

    3. 登录用户名的获取

    // 1.
    request.getRemoteUser();
    // 2.
    AssertionHolder.getAssertion().getPrincipal().getName()

    六、单点登录测试截图

    我创建了2个测试项目,首页访问路径分别为: 
    http://localhost:8080/cas-client-test1/index.jsp 
    http://localhost:8080/cas-client-test2/index.jsp

    1、访问 http://localhost:8080/cas-client-test1/index.jsp 自动跳转到认证页面。

    这里写图片描述

    2、登录成功后,自动转到 http://localhost:8080/cas-client-test1/index.jsp 页面。

    这里写图片描述

    3、访问 http://localhost:8080/cas-client-test2/index.jsp 因为已经被认证,所以可以直接打开不用再登录。

    这里写图片描述

    4、退出后打开退出页面,实际项目中可以通过 httpclient 请求登出地址来进行登出,然后跳转到登录页面。

    这里写图片描述

  • 相关阅读:
    一分钟去博客园广告
    浅谈歌词文件(LRC、QRC、KRC)
    css语法
    自定义浏览文件控件
    cookie的使用
    div背景图片拉伸
    URL传中文参数问题
    OC原理之GCD定时器
    深入理解Javascript闭包(closure) [转载]
    基于asp.net的自定义ajax框架(可传对象)
  • 原文地址:https://www.cnblogs.com/chenliangcl/p/7346228.html
Copyright © 2011-2022 走看看