前言:網絡中關於Spring security整合cas的方案有很多例,對於Springboot security整合cas方案則比較少,且有些仿制下來運行也有些錯誤,所以博主在此篇詳細的分析cas原理以及Springboot如何正確的配置cas環境
CAS原理
首先整合cas方案的話,無疑理解cas的原理是迫在眉睫的,這在后面對理解代碼也有很好的幫助,此處可查看別人寫的文章>>>CAS實現SSO單點登錄原理,博主只在這里針對springboot 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;
}
}
- SingleSignOutFilter主要響應的是對cas服務端注銷后對客戶端應用的注銷請求,其需要
LogoutFilter
的配合。這里涉及到SLO/SAML
的概念,有興趣的可自行查閱- 放行策略:對已含有
token
即ticket
參數的請求放行;非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請求進行響應,具體作用是
銷毀session以及安全上下文的Authentication 憑證對象
跳轉至cas服務端注銷頁面,這里可以配置跳轉路徑為 casServerUrlPrefix+casServerLogoutUrl+"?service="+casAppServiceUrl
cas服務端回調service來銷毀客戶端緩存的session,即
SingleSignOutFilter
CasAuthenticationFilter
CasAuthenticationFilter涉及的篇幅較長,可點擊>>>Springboot security cas源碼陶冶-CasAuthenticationFilter
ExceptionTranslationFilter
異常處理類,主要涉及對AuthenticationException
和AcessDeniedException
的響應,在cas中主要應用為出現授權/校驗錯誤則轉發路徑到casServerLoginUrl供用戶統一登錄,具體的可查看>>>Springboot security cas源碼陶冶-ExceptionTranslationFilter
FilterSecurityInterceptor
授權攔截器,可點擊>>>Springboot security cas源碼陶冶-FilterSecurityInterceptor
spring security cas邏輯示意圖
通過此圖再結合以上的代碼分析,便可以深入理解spring security是如何整合cas了
下節預告
Springboot security cas整合方案-實踐篇