CAS Client使用以及执行原理

CAS Client使用以及执行原理

流程介绍

CAS Client是利用Java Web中的Filter进行实现认证功能,客户端对CAS Server的认证流程分为以下步骤:

  • 访问CAS Client服务

  • 由于当前session中未检测到认证信息,会重定向到CAS Server地址进行认证

  • 在CAS Server上进行认证

  • 认证通过之后,CAS Server会重定向携带Ticket回到CAS Client地址

  • 将携带的Ticket在后台进行CAS Server校验

  • CAS Server校验Ticket通过之后会返回当前认证的账号信息

  • 在CAS Client拿到CAS Server返回的认证信息之后,缓存到当前session中

在以上流程中,CAS Client使用了两个Filter实现该主要功能。

使用

Maven依赖引入

<dependency>
  <groupId>org.jasig.cas.client</groupId>
  <artifactId>cas-client-core</artifactId>
  <version>3.6.4</version>
</dependency>

这里使用的Spring Boot 2.x版本(即JDK8版本),所以使用的依赖版本为3.6.4版本,如果是Spring Boot 3.x的话(JDK17),应当使用如下依赖:

<dependency>
    <groupId>org.apereo.cas.client</groupId>
    <artifactId>cas-client-core</artifactId>
    <version>4.0.4</version>
</dependency>

3.6.x以下的版本就不推荐引用了,之前的版本Spring版本为3.x,有兴趣的可以上Github看下官方版本:GitHub - apereo/java-cas-client: Apereo Java CAS Client

配置

将CAS Client依赖包中的两个Filter过滤器注入到Spring容器即可。

Spring Boot的配置文件,CAS相关

cas:
  server:
    name: http://localhost:9999
    login:
      url: http://localhost:8443/cas/login
    url:
      prefix: http://localhost:8443/cas/
AuthenticationFilter

该过滤器主要是用来检测当前客户端是否通过了CAS Server认证,如果没有通过的话则会调整到CAS Server地址,否则视为通过。

    // 设置cas server的登陆地址
    @Value("${cas.server.login.url}")
    private String casServerLoginUrl;
    // 设置cas server服务前缀,用于拼接cas server服务接口
    @Value("${cas.server.url.prefix}")
    private String casServerUrlPrefix;
    // 设置当前服务名称
    @Value("${cas.server.name}")
    private String serverName;    
​
    @Bean
    public FilterRegistrationBean casClientFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        // 拦截所有接口请求,可根据各自的业务进行配置
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setFilter(new AuthenticationFilter());
        filterRegistrationBean.addInitParameter("casServerLoginUrl", casServerLoginUrl);
        filterRegistrationBean.addInitParameter("casServerUrlPrefix", casServerUrlPrefix);
        filterRegistrationBean.addInitParameter("serverName", serverName);
        return filterRegistrationBean;
    }
​
TicketValidationFilter

该过滤器是用于获取Ticket以及校验Ticket是否有效,如果检验成功的话,则将校验接口返回的认证用户信息塞到当前session中。

该过滤器有多个版本,这边使用的是如下版本:

    @Bean
    public FilterRegistrationBean ticketValidateFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        // 拦截所有接口请求,可根据各自的业务进行配置
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
        filterRegistrationBean.addInitParameter("casServerLoginUrl", casServerLoginUrl);
        filterRegistrationBean.addInitParameter("casServerUrlPrefix", casServerUrlPrefix);
        filterRegistrationBean.addInitParameter("serverName", serverName);
        return filterRegistrationBean;
    }

其中配置与上个Filter参数一致即可;总共有四个是实现类,使用CAS20和CAS30的版本较多:

到这CAS Client的配置已经完成了。上述为主要配置,CAS Client中还有一些其他比较好用的Filter便于开发使用,比如AssertionThreadLocalFilter过滤器,将当前认证的用户信息保存到ThreadLocal当中,在开发过程中可以使用AssertionHolder.getAssertion()直接获取到当前用户信息。

执行原理

CAS Client的所有的配置项可以在ConfigurationKeys类中看到配置Key以及默认值;

Filter所有的参数初始化都是在initInternal方法进行的,在看doFilter方法时,需结合initInternal初始化方法一起,这样会了解的比较透彻。

通过解析CAS Client的两个过滤器来看看执行原理,先看看AuthenticationFilter中doFilter方法,重要步骤在如下一步一步说明:

@Override
    public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
​
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 1.判断当前接口请求是否需要被过滤
        if (isRequestUrlExcluded(request)) {
            logger.debug("Request is ignored.");
            filterChain.doFilter(request, response);
            return;
        }
​
        final HttpSession session = request.getSession(false);
        final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
        // 2.判断当前session中是否存在认证用户信息
        if (assertion != null) {
            filterChain.doFilter(request, response);
            return;
        }
        // 3.构建当前请求的服务地址,用于跳转cas server页面拼接的service参数
        final String serviceUrl = constructServiceUrl(request, response);
        // 4.获取当前请求是否存在ticket
        final String ticket = retrieveTicketFromRequest(request);
        // 5.判断当前请求是否为配置的网关地址
        final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
        // 6.如果存在ticket获取是在配置的网关地址中,则直接放开
        if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
            filterChain.doFilter(request, response);
            return;
        }
​
        final String modifiedServiceUrl;
​
        logger.debug("no ticket and no assertion found");
        if (this.gateway) {
            logger.debug("setting gateway attribute in session");
            modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
        } else {
            modifiedServiceUrl = serviceUrl;
        }
​
        logger.debug("Constructed service url: {}", modifiedServiceUrl);
        // 7.需拼接重定向地址页面
        final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
            getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway, this.method);
​
        logger.debug("redirecting to \"{}\"", urlToRedirectTo);
        this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
    }

以上可以综合为,先校验配置的不需要校验的接口,判断当前session是否存在认证的信息,不存在则跳转到指定的CAS Server认证页面。

而在Cas20ProxyReceivingTicketValidationFilter过滤器中,则就是解析Ticket以及将返回的认证用户信息塞到当前session中;

@Override
    public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
                               final FilterChain filterChain) throws IOException, ServletException {
        // 1.检测是否执行代理请求,一般为false,除非一些特殊的业务场景才需要用到
        if (!preFilter(servletRequest, servletResponse, filterChain)) {
            return;
        }
​
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 2.获取Ticket,存在则进入解析,否则直接进入下一个过滤器
        final String ticket = retrieveTicketFromRequest(request);
        
        if (CommonUtils.isNotBlank(ticket)) {
            logger.debug("Attempting to validate ticket: {}", ticket);
​
            try {
                // 3.去请求cas server的接口进行校验Ticket是否正确,正确并返回认证用户信息
                // CAS30是请求/p3/serviceValidate,而CAS20是请求/serviceValidate,两者接口均可以
                final Assertion assertion = this.ticketValidator.validate(ticket,
                        constructServiceUrl(request, response));
​
                logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
               // 4.校验Ticket通过之后,将认证用户信息塞入请求中
                request.setAttribute(CONST_CAS_ASSERTION, assertion);
               // 默认是会塞入到session中,除非特殊业务场景
                if (this.useSession) {
                    request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
                }
                onSuccessfulValidation(request, response, assertion);
                // 5.默认是重定向到请求地址
                if (this.redirectAfterValidation) {
                    logger.debug("Redirecting after successful ticket validation.");
                    response.sendRedirect(constructServiceUrl(request, response));
                    return;
                }
            } catch (final TicketValidationException e) {
                logger.debug(e.getMessage(), e);
               // Ticket校验失败,封装错误信息返回
                onFailedValidation(request, response);
​
                if (this.exceptionOnValidationFailure) {
                    throw new ServletException(e);
                }
​
                response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
​
                return;
            }
        }
​
        filterChain.doFilter(request, response);
​
    }
​

该过滤器用于校验Ticket并将返回的认证用户信息塞到session中,下次接口请求就无需校验了。

总结

以上为CAS Client的主要流程;在开发过程中大多数都是使用的Spring Boot作为业务开发框架,如果仅仅自己手动配置的CAS Client的话确实有点不和事宜,在CAS官方也提供了自动配置的依赖,目前找到了两种自动配置的依赖:

Unicon网址提供的CAS Client自动配置,该依赖很久没有更新了。

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

CAS官方提供的,分为Spring Boot 2.x以及3.x版本。

<dependency>
    <groupId>org.jasig.cas.client</groupId>
    <artifactId>cas-client-support-springboot</artifactId>
    <version>3.6.4</version>
</dependency>
<dependency>
    <groupId>org.apereo.cas.client</groupId>
    <artifactId>cas-client-support-springboot</artifactId>
    <version>4.0.4</version>
</dependency>

两者代码几乎是一致的,参数配置的话均可以根据CasClientConfigurationProperties进行配置即可。

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值