Springboot security cas整合方案-原理篇


前言:網絡中關於Spring security整合cas的方案有很多例,對於Springboot security整合cas方案則比較少,且有些仿制下來運行也有些錯誤,所以博主在此篇詳細的分析cas原理以及Springboot如何正確的配置cas環境

CAS原理

首先整合cas方案的話,無疑理解cas的原理是迫在眉睫的,這在后面對理解代碼也有很好的幫助,此處可查看別人寫的文章>>>CAS實現SSO單點登錄原理,博主只在這里針對springboot cas整合羅列出了其中的邏輯
Spring security cas原理圖

以上的邏輯看起來比較抽象,下面我們結合源碼部分對其作補充

CAS代碼邏輯

我們需要熟悉下以下這幾個Filter類

- SingleSignOutFilter 單點注銷Filter類,接收cas服務端發出的注銷session請求
- LogoutFilter 登錄退出Filter類,轉發至cas服務端進行注銷
- CasAuthenticationFilter cas校驗Filter處理類,包括對含有token的請求或者指定的路徑請求處理
- ExceptionTranslationFilter 異常Filter處理類,主要是接受AccessDeniedException/AuthenticationException這兩個異常,其中涉及轉發請求至cas服務端登錄頁面
- FilterSecurityInterceptor 權限驗證處理類

SingleSignOutFilter

主要涉及session的創建以及銷毀,響應token請求、SLO的前后通道請求,源碼如下

	//最終處理請求響應類
	private static final SingleSignOutHandler HANDLER = new SingleSignOutHandler();
	//是否已初始化,默認為false
    private AtomicBoolean handlerInitialized = new AtomicBoolean(false);
	
	//復寫Filter類的init方法,主要是初始化參數
    public void init(final FilterConfig filterConfig) throws ServletException {
	    //初始化ConfigurationStrategy策略類,默認為LegacyConfigurationStrategyImpl實現類
        super.init(filterConfig);
        //ignoreInitConfiguration是否為true,false則采用ConfigurationStrategy的相應參數名
        if (!isIgnoreInitConfiguration()) {
            //設置憑證參數,默認為ticket            
            setArtifactParameterName(getString(ConfigurationKeys.ARTIFACT_PARAMETER_NAME));
            //設置登錄退出參數,默認為logoutRequest            
            setLogoutParameterName(getString(ConfigurationKeys.LOGOUT_PARAMETER_NAME));
            //設置前台通道參數,默認為SAMLRequest,基於SAML實現,此處可自行查閱                                 
            setFrontLogoutParameterName(getString(ConfigurationKeys.FRONT_LOGOUT_PARAMETER_NAME));
            //設置RelayState參數,默認為RelayState           
            setRelayStateParameterName(getString(ConfigurationKeys.RELAY_STATE_PARAMETER_NAME));
            //設置cas服務端前綴,比如https://example.cas.com/cas            
            setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX));

            HANDLER.setArtifactParameterOverPost(getBoolean(ConfigurationKeys.ARTIFACT_PARAMETER_OVER_POST));
            HANDLER.setEagerlyCreateSessions(getBoolean(ConfigurationKeys.EAGERLY_CREATE_SESSIONS));
        }
        //主要設置safeParameters參數,默認只有logoutParameterName,即logoutRequest
        HANDLER.init();
        //設置為已初始化
        handlerInitialized.set(true);
    }

進而繼續查看SingleSignOutFilter#doFilter方法,看其中的處理邏輯

public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
            final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;

	    //判斷有無初始化
        if (!this.handlerInitialized.getAndSet(true)) {
            HANDLER.init();
        }
		//通過SingleSignOutHandler類處理請求,只有返回true才放行
        if (HANDLER.process(request, response)) {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

核心處理類SingleSignOutHandler#process()的代碼如下

public boolean process(final HttpServletRequest request, final HttpServletResponse response) {
		//判斷是否是token請求,即request對象中是否含有ticket屬性
        if (isTokenRequest(request)) {
            logger.trace("Received a token request");
            //保存當前的會話
            recordSession(request);
            
            return true;

        }
        //POST請求&非文件上傳請求&request對象含有logoutRequest屬性 
        else if (isBackChannelLogoutRequest(request)) {
            logger.trace("Received a back channel logout request");
            //銷毀會話
            destroySession(request);
            return false;

        }
	    //GET請求&casServerUrlPrefix已設置&request對象含有SAMLRequest屬性 
		else if (isFrontChannelLogoutRequest(request)) {
            logger.trace("Received a front channel logout request");
            destroySession(request);
            // redirection url to the CAS server 拼裝至cas服務端的logout請求
            final String redirectionUrl = computeRedirectionToServer(request);
            if (redirectionUrl != null) {
                CommonUtils.sendRedirect(response, redirectionUrl);
            }
            return false;

        } else {
            //對非logout請求都進行放行
            return true;
        }
    }
  1. SingleSignOutFilter主要響應的是對cas服務端注銷后對客戶端應用的注銷請求,其需要LogoutFilter的配合。這里涉及到SLO/SAML的概念,有興趣的可自行查閱
  2. 放行策略:對已含有tokenticket參數的請求放行;非SLO logout請求放行

LogoutFilter

登錄退出過濾類,是比較簡單的Filter類,實現上也比較簡單,簡單看下

  • 構造函數
	public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler,
			LogoutHandler... handlers) {
		//兩個參數都不能為空
		Assert.notEmpty(handlers, "LogoutHandlers are required");
		this.handlers = Arrays.asList(handlers);
		Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
		this.logoutSuccessHandler = logoutSuccessHandler;
		//默認接受的登錄退出請求為 /logout
		setFilterProcessesUrl("/logout");
	}

        //設置退出操作成功后跳轉的url:logoutSuccessUrl,此處一般為跳轉至cas服務端退出路徑並攜帶service回調路徑
        public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
		    Assert.notEmpty(handlers, "LogoutHandlers are required");
		    this.handlers = Arrays.asList(handlers);
		    Assert.isTrue(
				!StringUtils.hasLength(logoutSuccessUrl)
						|| UrlUtils.isValidRedirectUrl(logoutSuccessUrl),
				logoutSuccessUrl + " isn't a valid redirect URL");
		    SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
		    if (StringUtils.hasText(logoutSuccessUrl)) {
			    urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
		    }
		    logoutSuccessHandler = urlLogoutSuccessHandler;
		    setFilterProcessesUrl("/logout");
	}
  • doFilter()邏輯
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		//即匹配當前的請求是否為指定的響應請求,默認判斷是否為/logout
		if (requiresLogout(request, response)) {
			//獲取上下文中的Authentication 憑證信息
			Authentication auth = SecurityContextHolder.getContext().getAuthentication();

			if (logger.isDebugEnabled()) {
				logger.debug("Logging out user '" + auth
						+ "' and transferring to logout destination");
			}
			//一般是銷毀session和清除Authentication 憑證信息,比如SecurityContextLogoutHandler
			for (LogoutHandler handler : handlers) {
				handler.logout(request, response, auth);
			}
			//跳轉至cas服務端注銷頁面
			logoutSuccessHandler.onLogoutSuccess(request, response, auth);

			return;
		}
		//非logout請求放行
		chain.doFilter(request, response);
	}

LogoutFilter的邏輯比較簡單,主要是對logout請求進行響應,具體作用是

  1. 銷毀session以及安全上下文的Authentication 憑證對象

  2. 跳轉至cas服務端注銷頁面,這里可以配置跳轉路徑為 casServerUrlPrefix+casServerLogoutUrl+"?service="+casAppServiceUrl

  3. cas服務端回調service來銷毀客戶端緩存的session,即SingleSignOutFilter

CasAuthenticationFilter

CasAuthenticationFilter涉及的篇幅較長,可點擊>>>Springboot security cas源碼陶冶-CasAuthenticationFilter

ExceptionTranslationFilter

異常處理類,主要涉及對AuthenticationExceptionAcessDeniedException的響應,在cas中主要應用為出現授權/校驗錯誤則轉發路徑到casServerLoginUrl供用戶統一登錄,具體的可查看>>>Springboot security cas源碼陶冶-ExceptionTranslationFilter

FilterSecurityInterceptor

授權攔截器,可點擊>>>Springboot security cas源碼陶冶-FilterSecurityInterceptor

spring security cas邏輯示意圖

通過此圖再結合以上的代碼分析,便可以深入理解spring security是如何整合cas了
spring-security-cas

下節預告

Springboot security cas整合方案-實踐篇


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM