Cas(07)——建立使用Cas进行单点登录的应用 - 军军小站|张军博客

Cas(07)——建立使用Cas进行单点登录的应用

系统 1836 0

建立使用 Cas 进行单点登录的应用

 

目录

1.1 加入 cas-client-core-xxx.jar classpath

1.2 配置 Filter

1.2.1 AuthenticationFilter

1.2.2 TicketValidationFilter

1.2.3 HttpServletRequestWrapperFilter

1.2.4 AssertionThreadLocalFilter

1.2.5 基于 Spring Filter 配置

1.3 添加证书到信任库

 

       根据之前的描述我们知道, Cas 由两部分组成, Cas Server Cas Client Cas Server Cas 自己的服务端,而 Cas Client Cas 客户端,其需要与我们自己的应用进行集成。

 

1.1      加入 cas-client-core-xxx.jar classpath

       在我们下载的 Cas Client 压缩包的 modules 目录下可以找到一个名为 cas-client-core-xxx.jar jar 文件,首先需要将该 jar 包加入我们应用的类路径下,笔者这里使用的是 cas-client-core-3.1.11.jar 。如果用户的应用是使用 Maven 构造的,则可以在应用的 pom.xml 文件中加入如下依赖。

   < dependency >

      < groupId > org.jasig.cas.client </ groupId >

      < artifactId > cas -client-core </ artifactId >

      < version > 3.1.11 </ version >

   </ dependency >

 

1.2      配置 Filter

       然后需要我们在应用的 web.xml 文件中配置四个 Filter ,这四个 Filter 必须按照固定的顺序来进行配置,而且它们必须配置在应用的其它 Filter 之前。它们的先后顺序要求如下:

l   AuthenticationFilter

l   TicketValidationFilter

l   HttpServletRequestWrapperFilter

l   AssertionThreadLocalFilter

 

       这些 Filter 有的必须指定某些参数,有的可以指定某些参数,这些参数可以通过 context-param 来指定,也可以通过 init-param 来指定。 Cas Client 默认会先从 init-param 取,没取到则从 context-param 取,所以当 init-param context-param 都指定了某个参数时, init-param 指定的将拥有更高的优先级。所以当多个 Filter 需要共用一个参数时,我们可以把它定义为 context-param

 

1.2.1     AuthenticationFilter

       AuthenticationFilter 用来拦截所有的请求,用以判断用户是否需要通过 Cas Server 进行认证,如果需要则将跳转到 Cas Server 的登录页面。如果不需要进行登录认证,则请求会继续往下执行。

     AuthenticationFilter 有两个用户必须指定的参数,一个是用来指定 Cas Server 登录地址的 casServerLoginUrl ,另一个是用来指定认证成功后需要跳转地址的 serverName service service serverName 只需要指定一个就可以了。当两者都指定了,参数 service 将具有更高的优先级,即将以 service 指定的参数值为准。 service serverName 的区别在于 service 指定的是一个确定的 URL ,认证成功后就会确切的跳转到 service 指定的 URL ;而 serverName 则是用来指定主机名,其格式为 {protocol}:{hostName}:{port} ,如: https://localhost:8443 ,当指定的是 serverName 时, AuthenticationFilter 将会把它附加上当前请求的 URI ,以及对应的查询参数来构造一个确定的 URL ,如指定 serverName 为“ http://localhost ”,而当前请求的 URI 为“ /app ”,查询参数为“ a=b&b=c ”,则对应认证成功后的跳转地址将为“ http://localhost/app?a=b&b=c ”。

 

       除了上述必须指定的参数外, AuthenticationFilter 还可以指定如下可选参数:

l   renew :当指定 renew true 时,在请 Cas Server 时将带上参数“ renew=true ”,默认为 false

l   gateway :指定 gateway true 时,在请求 Cas Server 时将带上参数“ gateway=true ”,默认为 false

l   artifactParameterName :指定 ticket 对应的请求参数名称,默认为 ticket

l   serviceParameterName :指定 service 对应的请求参数名称,默认为 service

 

       如下是一个配置 AuthenticationFilter 的示例, serverName 由于在接下来配置的 Filter 中还要用,所以利用 context-param 将其配置为一个公用的参数。“ elim ”对应我的电脑名。

 

   < context-param >

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

      < param-value > http://elim:8080 </ param-value >

   </ context-param >

  

   < filter >

      < filter-name > casAuthenticationFilter </ filter-name >

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

      < init-param >

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

         < param-value > https://elim:8443/cas/login </ param-value >

      </ init-param >

   </ filter >

   < filter-mapping >

      < filter-name > casAuthenticationFilter </ filter-name >

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

   </ filter-mapping >

 

1.2.2     TicketValidationFilter

       在请求通过 AuthenticationFilter 的认证之后,如果请求中携带了参数 ticket 则将会由 TicketValidationFilter 来对携带的 ticket 进行校验。 TicketValidationFilter 只是对验证 ticket 的这一类 Filter 的统称,其并不对应 Cas Client 中的一个具体类型。 Cas Client 中有多种验证 ticket Filter ,都继承自 AbstractTicketValidationFilter ,它们的验证逻辑都是一致的,都有 AbstractTicketValidationFilter 实现,所不同的是使用的 TicketValidator 不一样。笔者这里将以 Cas10TicketValidationFilter 为例,其它还有 Cas20ProxyReceivingTicketValidationFilter Saml11TicketValidationFilter

 

   < filter >

      < filter-name > casTicketValidationFilter </ filter-name >

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

      < init-param >

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

         < param-value > https://elim:8443/cas </ param-value >

      </ init-param >

   </ filter >

   < filter-mapping >

      < filter-name > casTicketValidationFilter </ filter-name >

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

   </ filter-mapping >

 

       必须指定的参数:

l   casServerUrlPrefix :用来指定 Cas Server 对应 URL 地址的前缀,如上面示例的“ https://elim:8443/cas ”。

l   serverName service :语义跟前面介绍的一致。

 

       可选参数:

l   redirectAfterValidation   :表示是否验证通过后重新跳转到该 URL ,但是不带参数 ticket ,默认为 true

l   useSession   :在验证 ticket 成功后会生成一个 Assertion 对象,如果 useSession true ,则会将该对象存放到 Session 中。如果为 false ,则要求每次请求都需要携带 ticket 进行验证,显然 useSession false redirectAfterValidation true 是冲突的。默认为 true

l   exceptionOnValidationFailure   :表示 ticket 验证失败后是否需要抛出异常,默认为 true

l   renew :当值为 true 时将发送“ renew=true ”到 Cas Server ,默认为 false

 

1.2.3     HttpServletRequestWrapperFilter

       HttpServletRequestWrapperFilter 用于将每一个请求对应的 HttpServletRequest 封装为其内部定义的 CasHttpServletRequestWrapper ,该封装类将利用之前保存在 Session request 中的 Assertion 对象重写 HttpServletRequest getUserPrincipal() getRemoteUser() isUserInRole() 方法。这样在我们的应用中就可以非常方便的从 HttpServletRequest 中获取到用户的相关信息。以下是一个配置 HttpServletRequestWrapperFilter 的示例:

   < filter >

      < filter-name > casHttpServletRequestWrapperFilter </ filter-name >

   < filter-class > org.jasig.cas.client.util.HttpServletRequestWrapperFilter </ filter-class >

   </ filter >

   < filter-mapping >

      < filter-name > casHttpServletRequestWrapperFilter </ filter-name >

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

   </ filter-mapping >

 

1.2.4     AssertionThreadLocalFilter

       AssertionThreadLocalFilter 是为了方便用户在应用的其它地方获取 Assertion 对象,其会将当前的 Assertion 对象存放到当前的线程变量中,那么以后用户在程序的任何地方都可以从线程变量中获取当前 Assertion ,无需再从 Session request 中进行解析。该线程变量是由 AssertionHolder 持有的,我们在获取当前的 Assertion 时也只需要通过 AssertionHolder getAssertion() 方法获取即可,如:

   Assertion assertion = AssertionHolder. getAssertion ();

 

       AssertionThreadLocalFilter 这种设计理念是非常好的,实际应用中使用的也比较多, Spring Security 中也有用到这种理念。为了便于大家了解,特贴出 AssertionHolder 的源码如下:

public  class AssertionHolder {

 

    /**

     * ThreadLocal to hold the Assertion for Threads to access.

     */

    private  static  final ThreadLocal threadLocal = new ThreadLocal();

 

 

    /**

     * Retrieve the assertion from the ThreadLocal.

     */

    public  static Assertion getAssertion() {

        return (Assertion) threadLocal .get();

    }

 

    /**

     * Add the Assertion to the ThreadLocal.

     */

    public  static  void setAssertion( final Assertion assertion) {

        threadLocal .set(assertion);

    }

 

    /**

     * Clear the ThreadLocal.

     */

    public  static  void clear() {

        threadLocal .set( null );

    }

}

 

       以下是配置 AssertionThreadLocalFilter 的示例:

   < filter >

      < filter-name > casAssertionThreadLocalFilter </ filter-name >

     < filter-class > org.jasig.cas.client.util.AssertionThreadLocalFilter </ filter-class >

   </ filter >

   < filter-mapping >

      < filter-name > casAssertionThreadLocalFilter </ filter-name >

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

   </ filter-mapping >

 

1.2.5     基于Spring的Filter配置

       使用 Cas 单点登录的应用需要我们在应用的 web.xml 文件中配置上述介绍的四个 Filter ,但如果用户的应用是使用 Spring 开发的,则我们可以只在 web.xml 文件中配置四个 Spring DelegatingFilterProxy 用来代理需要配置的四个 Filter ,对应的 Filter 名称对应我们需要代理的 Spring ApplicationContext bean 的名称,此时我们需要将对应的 Filter 配置为 Spring ApplicationContext 中的一个 bean 对象。所以此时对应的 web.xml 文件的定义应该是这样的:

   < filter >

      < filter-name > casAuthenticationFilter </ filter-name >

     < filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >

   </ filter >

   < filter-mapping >

      < filter-name > casAuthenticationFilter </ filter-name >

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

   </ filter-mapping >

 

   < filter >

      < filter-name > casTicketValidationFilter </ filter-name >

     < filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >

   </ filter >

   < filter-mapping >

      < filter-name > casTicketValidationFilter </ filter-name >

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

   </ filter-mapping >

 

   < filter >

      < filter-name > casHttpServletRequestWrapperFilter </ filter-name >

     < filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >

   </ filter >

   < filter-mapping >

      < filter-name > casHttpServletRequestWrapperFilter </ filter-name >

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

   </ filter-mapping >

 

   < filter >

      < filter-name > casAssertionThreadLocalFilter </ filter-name >

     < filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >

   </ filter >

   < filter-mapping >

      < filter-name > casAssertionThreadLocalFilter </ filter-name >

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

   </ filter-mapping >

 

       而对应的 Filter 应该都以对应的名称定义为 Spring ApplicationContext 中的一个 bean

   < bean name = "casAuthenticationFilter"

      class = "org.jasig.cas.client.authentication.AuthenticationFilter"

      p:casServerLoginUrl = "https://elim:8443/cas/login" p:renew = "false"

      p:gateway = "false" p:serverName = "http://elim:8080" />

 

   < bean name = "casTicketValidationFilter"

      class = "org.jasig.cas.client.validation.Cas10TicketValidationFilter"

      p:serverName = "http://elim:8080" p:redirectAfterValidation = "true" >

      < property name = "ticketValidator" >

         < bean class = "org.jasig.cas.client.validation.Cas10TicketValidator" >

            <!-- 对应于 casServerUrlPrefix -->

            < constructor-arg index = "0" value = "https://elim:8443/cas" />

         </ bean >

      </ property >

   </ bean >

 

   < bean id = "casHttpServletRequestWrapperFilter" class = "org.jasig.cas.client.util.HttpServletRequestWrapperFilter" />

  

   < bean id = "casAssertionThreadLocalFilter" class = "org.jasig.cas.client.util.AssertionThreadLocalFilter" />

 

1.3      添加证书到信任库

       ticket 验证成功后,还需要验证证书,这需要我们将之前建立的证书导出并添加到当前 JRE 的证书信任库中,否则将验证失败。 JRE 在寻找证书时将根据当前使用的 host 来寻找,且会用该 host 匹配之前创建证书时指定的用户名称,如果匹配则表示找到。这也就意味着我们在 创建证书时指定的用户名称需要是我们的 host 。我的机器名称为“ elim ”,我就把它作为我的 host ,那么对应的证书应该这样创建。

keytool -genkey -keyalg RSA -alias tomcat -dname "cn=elim" -storepass changeit

 

       该语句是对我们之前介绍的 keytool -genkey -alias tomcat -keyalg RSA 的精写,它已经通过相应的参数指定了对应的参数值,而不需要再与用户交互了。如果还用之前的语句生成证书的话,那么对应的值应该这样填:


Cas(07)——建立使用Cas进行单点登录的应用
 

 

       之后会在用户的对应目录下生成一个 .keystore 文件。之后需要将该文件导出为一个证书到 %JAVA_HOME%/jre/lib/security 目录下,对应指令为:

keytool -export -alias tomcat -file %JAVA_HOME%/jre/lib/security/tomcat.crt -storepass changeit

 

       之后需要将导出的 tomcat.crt 证书添加到运行时使用的 JRE 的受信任证书库中,此时如果出现异常可将原本 %JAVA_HOME%/jre/lib/security 目录下的 cacerts 删除后继续执行以下指令。

keytool -import -alias tomcat -file %JAVA_HOME%/jre/lib/security/tomcat.crt -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit

 

       经过以上几步后就可以启用我们自己的 Cas Client 应用了,然后初次访问该应用时就会跳转到 Cas Server 进行登录认证。认证成功后将跳转到我们自己的 Client 应用进行 ticket 的验证,验证通过后就可以自由的访问我们的 Client 应用了。

 

(注:本文是基于 Cas Server3.5.2 Cas Client3.1.11 所写)

(注:原创文章,转载请注明出处。原文地址: http://haohaoxuexi.iteye.com/blog/2142631

 

 

 

Cas(07)——建立使用Cas进行单点登录的应用


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论