CAS单点登录(九)——客户端接入

在前面的CAS系列文章中,我们讲解了CAS从搭建到具体的自定义配置,但针对的都是CAS服务端的知识,今天我们就来讲解一下CAS的客户端的知识点。

我们知道CAS分为客户端和服务端,在前面的一系列文章中,我们主要讲解了CAS中主要的属性和特点,对CAS的部署和配置有了一定的了解,但是我们该如何接入CAS系统呢?这就需要我们配置CAS的客户端,从而实现CAS系统的接入。其实在前面的文章中,我们就讲解了CAS基本用法的demo,在官方的代码中也为我们提供了样例,但是比较简单,没有详细分析,今天我们就来仔细分析一下。

由于各个系统使用的语言不同,具体提供的不同CAS的客户端接入方法也不同,但是都大同小异,这里我就不一一介绍了,官方为我们提供Java、C#、PHP的客户端代码,其他语言的客户端也可以在github上找到。这里我们就主要以Java语言接入来介绍。

这里主要介绍两种方式接入,一种是Java Web,另一种是使用Spring Boot来接入CAS客户端系统。

一、Java Web配置CAS

这里我们先介绍使用的Java Web方式,这里我们还是结合原来讲解的代码cas-sample-java-webapp来分析,在cas-sample-java-webapp代码的resources下的webapp下面的web.xml中进行配置。在配置文档中,我们也可以查看到具体的配置指南

web1

web.xml

首先我们要配置的是org.jasig.cas.client.authentication.AuthenticationFilter,文档也指出了这个类的作用是用户是否要检测,是否已经登录了,如果没有,跳转到CAS服务器端进行登录匹配认证。

AuthenticationFilter

同时文档也给出了相应的属性含义,这里大致介绍一下重要的属性信息。

property

casServerUrlPrefix:cas服务端认证URL地址,比如https://sso.anumbrella.net/cas

casServerLoginUrl:cas登录URL,比如https://sso.anumbrella.net/cas/login,如果casServerLoginUrl设置将会覆盖casServerUrlPrefix。也就是casServerUrlPrefix和casServerLoginUrl设置一个就行了。

serverName:接入服务(客户端)的URL地址,包含协议和端口,比如:https://client.anumbrella.net:9443

renew:指定是否应将renew = true发送到CAS服务器。 有效值为true / false(或根本没有值)。 请注意,不能在本地init-param设置renew。该值得作用是:CAS协议允许客户端选择是否跳出单点登录(强制重新登录),这就是renew。它允许一个客户端通知CAS服务器总是验证一个用户,不管一个单点登录的session是否存在。这是一个非常有用的属性,当一个特定的使用CAS认证机制的服务允许访问敏感资料时,它能强迫CAS重新认证一个用户,确保登录的是一个正确的用户。

ignorePattern:定义认证时候忽略的URL信息。

ignoreUrlPatternType:定义URL忽略模式,默认为REGEX模式,还包含CONTAINS、EXACT、FULL_REGEX。具体信息如下:

ignoreUrlPatternType

除了这个验证类外,还有一org.jasig.cas.client.authentication.Saml11AuthenticationFilter,这个类的作用同上面org.jasig.cas.client.authentication.AuthenticationFilter一样,是对用户进行检测,是否已经登录了,如果没有,跳转到CAS服务器端进行登录匹配认证。不同之处是使用的SAML 1.1来验证。

Saml11AuthenticationFilter
具体配置的属性同上面一致,这里就不再复述了。

接着是org.jasig.cas.client.validation.Cas10TicketValidationFilter
org.jasig.cas.client.validation.Saml11TicketValidationFilter类这里是指验证tickets分别使用CAS 1.0 Protocol和SAML 1.1 Protocol来验证,除此之外还有org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilterorg.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter
分别是使用CAS 2.0 Protocol和CAS 3.0 Protocol来进行tickets验证。

这里的这几个类主要是验证票据(tickets)的过滤器,分别是使用不同协议来校验。

org.jasig.cas.client.validation.Cas10TicketValidationFilter

Cas10TicketValidationFilter

org.jasig.cas.client.validation.Saml11TicketValidationFilter

Saml11TicketValidationFilter

org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter

Cas20ProxyReceivingTicketValidationFilter

org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter

Cas30ProxyReceivingTicketValidationFilter

具体的配置也是大同小异。

casServerUrlPrefix:cas服务端认证URL地址,比如https://sso.anumbrella.net/cas

serverName: 接入服务(客户端)的URL地址,包含协议和端口,比如:https://client.anumbrella.net:9443

renew:指定是否应将renew = true发送到CAS服务器。 有效值为true / false(或根本没有值)。 请注意,不能在本地init-param设置renew。该值得作用是:CAS协议允许客户端选择是否跳出单点登录(强制重新登录),这就是renew。它允许一个客户端通知CAS服务器总是验证一个用户,不管一个单点登录的session是否存在。这是一个非常有用的属性,当一个特定的使用CAS认证机制的服务允许访问敏感资料时,它能强迫CAS重新认证一个用户,确保登录的是一个正确的用户。

redirectAfterValidation:是否在票证验证后重定向到相同的URL,但参数中没有票证,默认为ture。

useSession:是否在会话中存储session。如果不使用会话,则每个请求都需要票据。默认为true。

exceptionOnValidationFailure:是否在验证票据失败后,抛出一个异常。默认为true。

org.jasig.cas.client.util.HttpServletRequestWrapperFilter 包装httpservletrequest,以便getRemoteUser和getPrincipal返回与CAS相关的属性。

HttpServletRequestWrapperFilter
org.jasig.cas.client.util.AssertionThreadLocalFilter该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息或前端传过来的信息。但它无法访问HttpServletRequest,因此无法调用getRemoteUser()。

AssertionThreadLocalFilter

org.jasig.cas.client.util.ErrorRedirectFilter根据异常重定向到提供的URL的过滤器。 异常和URL通过init过滤器名称/参数值配置。

ErrorRedirectFilter

上面就是CAS单点登录的相关配置,当然我们知道除了单点登录,还有单点退出,就是一个客户端主动发起退出后,其他的客户端也会相应收到服务端发送的退出请求,让其他的在线客户端也退出。如果不太熟悉概念,可以查看CAS单点登录(一)——初识SSO

在客户端接入中,同样提供了单点退出的相关配置。

single sign outcasServerUrlPrefix:cas服务端认证URL地址,比如https://sso.anumbrella.net/cas

logoutCallbackPath:预期从CAS服务器接收注销回调请求的路径。如果您的应用程序在处理表单发布时需要访问原始输入流,那么这是必需的。如果没有配置,默认行为将检查每个表单发布的注销参数。

同样的单点退出也支持两种协议,配置大致都是相同。一种是CAS Protocol,另一种是SAML Protocol。

CAS Protocol

cas

SAML Protocol

SAML

上面便是全部的CAS的相关配置相关信息,包括客户端和服务端,更多具体相关的属性可以参考demo。

二、Spring Boot配置CAS

其实Spring Boot整合CAS客户端和前面Java Web的配置大致相同,前者是在XML中进行配置,而Spring Boot这里则是在配置文件中配置,然后通过注入Java的方式来实现配置的。这里有一个Spring Boot用户登录的基本实例,我们将结合它来进行单点登录的配置。

login

success

我们使用这个简单的基于Spring Boot的用户登录系统,将给它来接入CAS系统,这里的登录验证是使用的spring-boot-starter-data-jpa,来验证表user中的用户信息。现在我们介入CAS客户端代码,首先导入依赖。

  <!--CAS Client-->
  <dependency>
      <groupId>org.jasig.cas.client</groupId>
      <artifactId>cas-client-core</artifactId>
      <version>3.5.1</version>
  </dependency>

首先我们在application.properties中添加配置,这里也就是我们上面讲解过的配置信息。

# 监听退出的接口,即所有接口都会进行监听
spring.cas.sign-out-filters=/*
# 需要拦截的认证的接口
spring.cas.auth-filters=/*
spring.cas.validate-filters=/*
spring.cas.request-wrapper-filters=/*
spring.cas.assertion-filters=/*
# 表示忽略拦截的接口,也就是不用进行拦截
spring.cas.ignore-filters=/test
spring.cas.cas-server-login-url=https://sso.anumbrella.net:8443/cas/login
spring.cas.cas-server-url-prefix=https://sso.anumbrella.net:8443/cas/
spring.cas.redirect-after-validation=true
spring.cas.use-session=true
spring.cas.server-name=https://client.anumbrella.net:9443

然后再新建bean类SpringCasAutoconfig,读取配置文件中的信息。

package net.anumbrella.sso.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.Arrays;
import java.util.List;

/**
 * @author Anumbrella
 */
@ConfigurationProperties(prefix ="spring.cas")
public class SpringCasAutoconfig {

    static final String separator = ",";

    private String validateFilters;
    private String signOutFilters;
    private String authFilters;
    private String assertionFilters;
    private String requestWrapperFilters;
    private String ignoreFilters; //需要放行的url,多个可以使用|分隔,遵循正则

    private String casServerUrlPrefix;
    private String casServerLoginUrl;
    private String serverName;
    private boolean useSession = true;
    private boolean redirectAfterValidation = true;

    public String getIgnoreFilters() {
        return ignoreFilters;
    }
    public void setIgnoreFilters(String ignoreFilters) {
        this.ignoreFilters = ignoreFilters;
    }
    public List<String> getValidateFilters() {
        return Arrays.asList(validateFilters.split(separator));
    }
    public void setValidateFilters(String validateFilters) {
        this.validateFilters = validateFilters;
    }
    public List<String> getSignOutFilters() {
        return Arrays.asList(signOutFilters.split(separator));
    }
    public void setSignOutFilters(String signOutFilters) {
        this.signOutFilters = signOutFilters;
    }
    public List<String> getAuthFilters() {
        return Arrays.asList(authFilters.split(separator));
    }
    public void setAuthFilters(String authFilters) {
        this.authFilters = authFilters;
    }
    public List<String> getAssertionFilters() {
        return Arrays.asList(assertionFilters.split(separator));
    }
    public void setAssertionFilters(String assertionFilters) {
        this.assertionFilters = assertionFilters;
    }
    public List<String> getRequestWrapperFilters() {
        return Arrays.asList(requestWrapperFilters.split(separator));
    }
    public void setRequestWrapperFilters(String requestWrapperFilters) {
        this.requestWrapperFilters = requestWrapperFilters;
    }
    public String getCasServerUrlPrefix() {
        return casServerUrlPrefix;
    }
    public void setCasServerUrlPrefix(String casServerUrlPrefix) {
        this.casServerUrlPrefix = casServerUrlPrefix;
    }
    public String getCasServerLoginUrl() {
        return casServerLoginUrl;
    }
    public void setCasServerLoginUrl(String casServerLoginUrl) {
        this.casServerLoginUrl = casServerLoginUrl;
    }
    public String getServerName() {
        return serverName;
    }
    public void setServerName(String serverName) {
        this.serverName = serverName;
    }
    public boolean isRedirectAfterValidation() {
        return redirectAfterValidation;
    }
    public void setRedirectAfterValidation(boolean redirectAfterValidation) {
        this.redirectAfterValidation = redirectAfterValidation;
    }
    public boolean isUseSession() {
        return useSession;
    }
    public void setUseSession(boolean useSession) {
        this.useSession = useSession;
    }
}

最后配置以下注入CAS的配置,这些配置同上面介绍的一致,只是这里是通过Java Bean方式注入,来实现客户端接入。

package net.anumbrella.sso.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.AssertionThreadLocalFilter;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.stereotype.Component;

/**
 * @author Anumbrella
 */
@Configuration
@Component
public class CasCustomConfig {

    @Autowired
    SpringCasAutoconfig autoconfig;

    private static boolean casEnabled = true;

    public CasCustomConfig() {
    }

    @Bean
    public SpringCasAutoconfig getSpringCasAutoconfig() {
        return new SpringCasAutoconfig();
    }

    @Bean
    public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() {
        ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<SingleSignOutHttpSessionListener>();
        listener.setEnabled(casEnabled);
        listener.setListener(new SingleSignOutHttpSessionListener());
        listener.setOrder(1);
        return listener;
    }

    /**
     * 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean singleSignOutFilter() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new SingleSignOutFilter());
        filterRegistration.setEnabled(casEnabled);
        if (autoconfig.getSignOutFilters().size() > 0) {
            filterRegistration.setUrlPatterns(autoconfig.getSignOutFilters());
        } else {
            filterRegistration.addUrlPatterns("/*");
        }
        filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix());
        filterRegistration.setOrder(3);
        return filterRegistration;
    }

    /**
     * 该过滤器负责用户的认证工作
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean authenticationFilter() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new AuthenticationFilter());
        filterRegistration.setEnabled(casEnabled);
        if (autoconfig.getAuthFilters().size() > 0) {
            filterRegistration.setUrlPatterns(autoconfig.getAuthFilters());
        } else {
            filterRegistration.addUrlPatterns("/*");
        }
        if (autoconfig.getIgnoreFilters() != null) {
            filterRegistration.addInitParameter("ignorePattern", autoconfig.getIgnoreFilters());
        }
        filterRegistration.addInitParameter("casServerLoginUrl", autoconfig.getCasServerLoginUrl());
        filterRegistration.addInitParameter("serverName", autoconfig.getServerName());
        filterRegistration.addInitParameter("useSession", autoconfig.isUseSession() ? "true" : "false");
        filterRegistration.addInitParameter("redirectAfterValidation", autoconfig.isRedirectAfterValidation() ? "true" : "false");
        filterRegistration.setOrder(4);
        return filterRegistration;
    }

    /**
     * 该过滤器负责对Ticket的校验工作,使用CAS 3.0协议
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean cas30ProxyReceivingTicketValidationFilter() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        filterRegistration.setEnabled(casEnabled);
        if (autoconfig.getValidateFilters().size() > 0) {
            filterRegistration.setUrlPatterns(autoconfig.getValidateFilters());
        } else {
            filterRegistration.addUrlPatterns("/*");
        }
        filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix());
        filterRegistration.addInitParameter("serverName", autoconfig.getServerName());
        filterRegistration.setOrder(5);
        return filterRegistration;
    }

    @Bean
    public FilterRegistrationBean httpServletRequestWrapperFilter() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new HttpServletRequestWrapperFilter());
        filterRegistration.setEnabled(true);
        if (autoconfig.getRequestWrapperFilters().size() > 0) {
            filterRegistration.setUrlPatterns(autoconfig.getRequestWrapperFilters());
        } else {
            filterRegistration.addUrlPatterns("/*");
        }
        filterRegistration.setOrder(6);
        return filterRegistration;
    }

    /**
     * 该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
     * 比如AssertionHolder.getAssertion().getPrincipal().getName()。
     * 这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean assertionThreadLocalFilter() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new AssertionThreadLocalFilter());
        filterRegistration.setEnabled(true);
        if (autoconfig.getAssertionFilters().size() > 0) {
            filterRegistration.setUrlPatterns(autoconfig.getAssertionFilters());
        } else {
            filterRegistration.addUrlPatterns("/*");
        }
        filterRegistration.setOrder(7);
        return filterRegistration;
    }
}

那么当我登陆成功后,如何判断呢?我们可以通过下面的方法获取CAS给我们传递回来的对象,放置在session中。

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

如下,我们获取登陆的用户名,并打印出来。

//获取cas给我们传递回来的对象,这个东西放到了session中
 //session的 key是 _const_cas_assertion_
          
Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
//获取登录用户名
String loginName = assertion.getPrincipal().getName();
 System.out.printf("登录用户名:%s\r\n", loginName);

info

到此我们的Spring Boot接入CAS就完成了,输入实例地址,我们可以发现出现了我们熟悉的CAS登录界面。

sso

除了使用官方的jar包外,我们还可以使用第三方开发的cas-client包,它将Spring Boot集合CAS做了封装,更简单。比如cas-client-autoconfig-support,通过简单配置和加上@EnableCasClient注解就实现了集成CAS客户端。

代码实例:Chapter8

参考

  • 14
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值