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

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章