SpringBoot整合CAS服务

本文详细介绍了如何在SpringBoot项目中整合CAS服务器,包括配置、添加依赖、Bean的实现以及测试过程,展示了CasClient与CASServer之间的通信机制和功能,如单点登录、Ticket验证和Session管理。
摘要由CSDN通过智能技术生成

      背景

        上一篇博客里我们成功搭建了一个CAS Server,只有Server,没有Client,未免不太圆满,今天我们继续来熟悉CAS,通过搭建一个SpringBoot项目整合CAS来见证CAS Server 和Cas Client的通信过程。

     SpringBoot整合Cas实践

        1、搭建一个空的SpringBoot项目,这个可以参考我之前的博客Idea2023创建自定义项目骨架,用模板搭建,嘎嘎快,这里就不赘述了。

        2、在application.yml中添加cas的相关配置

cas:
  server-url-prefix: https://server.cas.com:8443/cas
  #cas服务端的登录地址
  server-login-url: https://server.cas.com:8443/cas/login
  #当前服务器的地址(客户端)
  client-host-url: http://localhost:19200
  #Ticket校验器使用Cas30ProxyReceivingTicketValidationFilter
  validation-type: cas3

        3、添加maven依赖


        <dependency>
            <groupId>net.unicon.cas</groupId>
            <artifactId>cas-client-autoconfig-support</artifactId>
            <version>2.3.0-GA</version>
        </dependency>

        4、添加cas配置类

package com.leixi.casclient.config;


import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;
/**
 *
 * @author leixiyueqi
 * @since 2024/1/5 19:39
 */
@Configuration
public class CasConfig {


    //cas认证服务中心地址
    @Value("${cas.server-url-prefix}")
    private String CAS_SERVER_URL_PREFIX;

    //cas认证服务中心登陆地址
    @Value("${cas.server-login-url}")
    private String CAS_SERVER_URL_LOGIN ;

    @Value("${cas.client-host-url}")
    //客户端的地址
    private String SERVER_NAME;


    @Bean
    public FilterRegistrationBean filterSingleRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new SingleSignOutFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String,String> initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", CAS_SERVER_URL_PREFIX);
        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }


    @Bean
    public FilterRegistrationBean filterValidationRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String,String>  initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", CAS_SERVER_URL_PREFIX);
        initParameters.put("serverName", SERVER_NAME);
        initParameters.put("useSession", "true");   //是否将令牌存在会话中
        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }



    @Bean
    public FilterRegistrationBean filterAuthenticationRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new AuthenticationFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");

        Map<String,String>  initParameters = new HashMap<String, String>();
        initParameters.put("casServerLoginUrl", CAS_SERVER_URL_LOGIN);
        initParameters.put("serverName", SERVER_NAME);
        //设置忽略  退出登录不用登录
        initParameters.put("ignorePattern", "/system/*");

        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }




    @Bean
    public FilterRegistrationBean filterWrapperRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new HttpServletRequestWrapperFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }


    @Bean
    public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration(){
        ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>();
        registrationBean.setListener(new SingleSignOutHttpSessionListener());
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

        5、启动类中添加@EnableCasClient

        6、编写一个测试的Controller,从session中获取用户信息

/**
 *
 * @author leixiyueqi
 * @since 2024/1/5 19:39
 */
@RestController
public class CasClientController {

    @RequestMapping("/casTest")
    public String casTest(HttpSession session){
        Assertion assertion = (Assertion)session.getAttribute(CONST_CAS_ASSERTION);
        AttributePrincipal principal = assertion.getPrincipal();
        String loginName = principal.getName();
        return "当前登录账户"+loginName;
    }
}

        7、测试,启动CAS Server 和 SpringBoot之后,访问http://localhost:19200/leixi/casTest, 浏览器会先跳转到登陆页面,如下:

        输入用户名,密码,登陆之后,可以正常显示用户名,说明客户端可以正常定向回来,并且获取到用户信息。

     CasConfig里的Bean

        在调试CAS的客户端时,我特别注意了一下CasConfig里的Bean,发现这些Bean个个都神通广大,对于我熟悉CAS架构有很大的帮助,这里一一进行说明。

        SingleSignOutFilter

        该过滤器主要用于拦截 CAS 服务器发送的注销请求,实现单点注销功能。其实现原理如下:
        1、当用户在 CAS 服务器上注销登录时,CAS 服务器会向所有已登录的客户端发送注销请求。
        2、SingleSignOutFilter 过滤器会拦截这个注销请求,并将其解析为一个票据(Ticket)。
        3、SingleSignOutFilter 过滤器会遍历所有已登录的用户会话(Session),查找包含与该票据相关联的 CAS 令牌(Token)的会话。
        4、对于包含该令牌的会话,SingleSignOutFilter 过滤器会调用 Session.invalidate() 方法,使该会话失效并注销用户登录。
        5、当用户再次访问应用程序时,由于会话已经失效,用户需要重新登录并获取新的会话。

        SingleSignOutHttpSessionListener 

        该过滤器用于监听 Session 的销毁事件,当用户在一个应用程序中注销登录或 Session 超时时,应用程序会销毁对应的 Session,但此时 CAS 服务器并不会知道该 Session 已经被销毁,因此该票据仍然可以在其他应用程序中被使用,这就导致了单点注销的问题。

        为了解决这个问题,SingleSignOutHttpSessionListener 会监听 Session 的销毁事件,在 Session 销毁时,获取该 Session 中保存的票据信息,通过 CAS 客户端向 CAS 服务器发送注销请求。在接收到注销请求后,CAS 服务器会将该票据对应的 Session 注销,并通知其他应用程序将该票据对应的 Session 也进行注销。这样,即使用户在一个应用程序中注销登录或 Session 超时,该票据也会被注销,从而保证了单点注销的正确性。

        Cas30ProxyReceivingTicketValidationFilter 
        该过滤器用于验证 CAS 服务器发放的票据(Ticket),其实现原理如下:
        1、客户端携带票据访问应用程序时,Cas30ProxyReceivingTicketValidationFilter 过滤器会拦截该请求,并将票据信息发送给 CAS 服务器进行验证。
        2、CAS 服务器验证该票据的合法性,并返回用户信息给客户端。
        3、客户端将用户信息保存在会话中,并对该请求放行。

        AuthenticationFilter

        该过滤器用于实现CAS的跳转登陆验证,URL重定向。其实现原理如下:
        1、当用户访问需要进行 CAS 认证的 URL 时,AuthenticationFilter 会拦截该请求,在 doFilter 方法中调用 HttpServletRequest 的 getSession 方法获取当前会话的 HttpSession 对象。
        2、调用 AuthenticationFilter 的 isAuthenticated 方法,判断当前用户是否已经通过 CAS 认证。如果已经通过 CAS 认证,则直接调用 filterChain.doFilter 方法将请求传递给下一个过滤器;
        3、如果用户未通过 CAS 认证,则跳转到 CAS 服务器的登录页面进行认证。AuthenticationFilter 会构建 CAS 客户端的认证 URL,并将用户重定向到该 URL。认证 URL 中包括以下参数:
            casServerLoginUrl:CAS 服务器的登录 URL。
            service:本地应用程序的 URL。
            renew:是否强制进行 CAS 认证。
            gateway:是否允许通过 CAS 认证。
        4、用户在 CAS 服务器上输入用户名和密码进行认证后,CAS 服务器会返回一个票据(Ticket)。AuthenticationFilter 会将该票据保存在 HttpSession 中,并将请求重定向到本地应用程序的 URL。然后再次从步骤1开始执行。

        HttpServletRequestWrapperFilter

        该过滤器主要用于将 ServletRequest 包装为 HttpServletRequest,提供了一些新的方法,使得获取请求参数、请求头等更加方便,比如:
        1、在 ServletRequest 的基础上,HttpServletRequest 提供了更加丰富的请求信息获取方法,如获取请求参数、请求头、请求 URI 等等。
        2、在实际应用场景中,我们可能需要对请求进行更加细致的处理,比如通过请求参数来进行业务处理、通过请求头来进行请求授权、通过请求 URI 来进行请求路由等等。而 ServletRequest 并没有提供这些方便的方法,这时候我们就需要将其包装为 HttpServletRequest,从而获得更多的处理能力。
        3、此外,HttpServletRequest 还提供了一些方便的方法,如获取 Session、设置 Cookie 等等,这些方法也能够极大地帮助我们进行请求处理。
        因此,将 ServletRequest 包装为 HttpServletRequest,可以提供更加丰富的请求处理能力,同时也能够提高代码的可读性和可维护性。

        测试验证结果

        为了验证上述的Filter,我将cas-client复制了一份,注释了其中一个CasConfig的@Configuration并添加以下代码:

    @RequestMapping("/logout")
    public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession();
        session.invalidate();
        response.sendRedirect("https://server.cas.com:8443/cas/logout");
    }

        分别访问以下url:

        http://localhost:19200/leixi/casTest
        http://localhost:19201/leixi/casTest
        http://localhost:19200/leixi/logout
        http://localhost:19201/leixi/logout

        测试结论如下:

        1、通过 http://localhost:19200/leixi/logout 注销,可以做到一次注销,多服务器生效的效果,但是直接访问https://server.cas.com:8443/cas/logout,却会出现有部分服务仍然可以访问的怪象。猜测是CAS SERVER没有正常的将各客户端的服务注销。

        2、各客户端获取到ST,正常访问后,关闭CAS Server,这时候仍有部分客户端可以访问资源。这说明客户端会优先从本地缓存中获取ST。

      踩过的坑 

        1、出下如下bug: 未认证授权的服务。

        这是因为访问http型的资源时,CAS Server 的/resources/services/xx.json文件里没有加http。我加了之后还报了这样的错,原因有二:一是overlays文件夹下的WEB-INF/classes/services里也有相关的json文件,可以把这个文件夹下对应的文件删了,免得启动时有多个配置文件。二是我在resources文件里添加services时,把名字错打成service了,导致配置不生效。

        2、报bug javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching server.cas.com found。 这个是因为我注册SSL key 时,名字姓氏和组织单位没设成域名,又是粗心大意引起的。

        以上就是我搭建SpringBoot项目整合CAS服务的全部经过了,希望能对新人们有一定的帮助

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值