Spring Security3源碼分析-CAS支持

Spring Security3對CAS的支持主要在這個spring-security-cas-client-3.0.2.RELEASE.jar包中

Spring Security和CAS集成的配置資料不少。這裏講解的比較詳細

http://lengyun3566.iteye.com/blog/1358323java

配置方面,主要爲下面的部分:web

<security:http auto-config="true" entry-point-ref="casAuthEntryPoint" access-denied-page="/error/403.jsp">
		<security:custom-filter ref="casAuthenticationFilter" position="CAS_FILTER"/>
		<security:form-login login-page="/login.jsp"/>
		<security:logout logout-success-url="/login.jsp"/>
		<security:intercept-url pattern="/admin.jsp*" access="ROLE_ADMIN"/>
		<security:intercept-url pattern="/index.jsp*" access="ROLE_USER,ROLE_ADMIN"/>
		<security:intercept-url pattern="/home.jsp*" access="ROLE_USER,ROLE_ADMIN"/>
		<security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/>	
	</security:http>
	
	<security:authentication-manager alias="authenticationmanager">
		<security:authentication-provider ref="casAuthenticationProvider"/>
	</security:authentication-manager>
	
	<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
  		<property name="ticketValidator" ref="casTicketValidator"/>
  		<property name="serviceProperties" ref="casService"/>
  		<property name="key" value="docms"/>
  		<property name="authenticationUserDetailsService" ref="authenticationUserDetailsService"/>
	</bean>
	
	<bean id="casAuthEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
  		<property name="loginUrl" value="https://server:8443/cas/login"/>
  		<property name="serviceProperties" ref="casService"/>
	</bean>	
	
	<bean id="casService" class="org.springframework.security.cas.ServiceProperties">
		<property name="service" value="http://localhost:8888/docms/j_spring_cas_security_check"/>
	</bean>	
	
	<bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
  		<property name="authenticationManager" ref="authenticationmanager"/>
	</bean>
	
	<bean id="casTicketValidator" class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
  		<constructor-arg value="https://server:8443/cas/"/>
	</bean>

	<bean id="authenticationUserDetailsService" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
  		<property name="userDetailsService" ref="userDetailsManager"/>
	</bean>

 

這裏須要強調一下http標籤的entry-point-ref屬性,由於以前沒有着重的介紹,英文的意思是入口點引用。爲何須要這個入口點呢。這個入口點其實僅僅是被ExceptionTranslationFilter引用的。前面已經介紹過ExceptionTranslationFilter過濾器的做用是異常翻譯,在出現認證異常、訪問異常時,經過入口點決定redirect、forward的操做。好比如今是form-login的認證方式,若是沒有經過UsernamePasswordAuthenticationFilter的認證就直接訪問某個被保護的url,那麼通過ExceptionTranslationFilter過濾器處理後,先捕獲到訪問拒絕異常,並把跳轉動做交給入口點來處理。form-login的對應入口點類爲LoginUrlAuthenticationEntryPoint,這個入口點類的commence方法會redirect或forward到指定的url(form-login標籤的login-page屬性)


清楚了entry-point-ref屬性的意義。那麼與CAS集成時,若是訪問一個受保護的url,就經過CAS認證對應的入口點org.springframework.security.cas.web.CasAuthenticationEntryPoint類redirect到loginUrl屬性所配置的url中,即通常爲CAS的認證頁面(好比:https://server:8443/cas/login)。spring

下面爲CasAuthenticationEntryPoint類的commence方法。其主要任務就是構造跳轉的url,再執行redirect動做。根據上面的配置,實際上跳轉的url爲:https://server:8443/cas/login?service=http%3A%2F%2Flocalhost%3A8888%2Fdocms%2Fj_spring_cas_security_check緩存

public final void commence(final HttpServletRequest servletRequest, final HttpServletResponse response,
            final AuthenticationException authenticationException) throws IOException, ServletException {

        final String urlEncodedService = createServiceUrl(servletRequest, response);
        final String redirectUrl = createRedirectUrl(urlEncodedService);

        preCommence(servletRequest, response);
        response.sendRedirect(redirectUrl);
    }

 

 

接下來繼續分析custom-filter ref="casAuthenticationFilter" position="CAS_FILTER"網絡

這是一個自定義標籤,而且在過濾器鏈中的位置是CAS_FILTER。這個過濾器在什麼時候會起做用呢?帶着這個疑問繼續閱讀源碼session

CasAuthenticationFilter對應的類路徑是app

org.springframework.security.cas.web.CasAuthenticationFilterless

這個類與UsernamePasswordAuthenticationFilter同樣,都繼承於AbstractAuthenticationProcessingFilter。實際上全部認證過濾器都繼承這個抽象類,其過濾器自己只要實現attemptAuthentication方法便可。jsp

CasAuthenticationFilter的構造方法直接向父類的構造方法傳入/j_spring_cas_security_check用於判斷當前請求的url是否須要進一步的認證處理ide

public CasAuthenticationFilter() {
        super("/j_spring_cas_security_check");
    }

CasAuthenticationFilter類的attemptAuthentication方法源碼以下

public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response)
            throws AuthenticationException {
        //設置用戶名爲有狀態標識符
        final String username = CAS_STATEFUL_IDENTIFIER;
        //獲取CAS認證成功後返回的ticket
        String password = request.getParameter(this.artifactParameter);

        if (password == null) {
            password = "";
        }
        //構造UsernamePasswordAuthenticationToken對象
        final UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        //由認證管理器完成認證工做
        return this.getAuthenticationManager().authenticate(authRequest);
    }

 在以前的源碼分析中,已經詳細分析了認證管理器AuthenticationManager認證的整個過程,這裏就再也不贅述了。

 

因爲AuthenticationManager是依賴於具體的AuthenticationProvider的,因此接下來看

<security:authentication-manager alias="authenticationmanager">
<security:authentication-provider ref="casAuthenticationProvider"/>
</security:authentication-manager>

注意這裏的ref屬性定義。若是沒有使用CAS認證,此處通常定義user-service-ref屬性。這兩個屬性的區別在於

ref:直接將ref依賴的bean注入到AuthenticationProvider的providers集合中

user-service-ref:定義DaoAuthenticationProvider的bean注入到AuthenticationProvider的providers集合中,而且DaoAuthenticationProvider的變量userDetailsService由user-service-ref依賴的bean注入。

 

因而可知,採用CAS認證時,AuthenticationProvider只有AnonymousAuthenticationProvider和CasAuthenticationProvider

 

繼續分析CasAuthenticationProvider是如何完成認證工做的

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //省略若干判斷
        CasAuthenticationToken result = null;
        //注意這裏的無狀態條件。主要用於無httpsession的環境中。如soap調用
        if (stateless) {
            // Try to obtain from cache
            //經過緩存來存儲認證明體。主要避免每次請求最新ticket的網絡開銷
            result = statelessTicketCache.getByTicketId(authentication.getCredentials().toString());
        }

        if (result == null) {
            result = this.authenticateNow(authentication);
            result.setDetails(authentication.getDetails());
        }

        if (stateless) {
            // Add to cache
            statelessTicketCache.putTicketInCache(result);
        }

        return result;
    }

    //完成認證工做
    private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
        try {
            //經過cas client的ticketValidator完成ticket校驗,並返回身份斷言
            final Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), serviceProperties.getService());
           //根據斷言信息構造UserDetails 
            final UserDetails userDetails = loadUserByAssertion(assertion);
           //檢查帳號狀態
           userDetailsChecker.check(userDetails);
           //構造CasAuthenticationToken
            return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), userDetails.getAuthorities(), userDetails, assertion);
        } catch (final TicketValidationException e) {
            throw new BadCredentialsException(e.getMessage(), e);
        }
    }

    //經過注入的authenticationUserDetailsService根據token中的認證主體即用戶名獲取UserDetails 
    protected UserDetails loadUserByAssertion(final Assertion assertion) {
        final CasAssertionAuthenticationToken token = new CasAssertionAuthenticationToken(assertion, "");
        return this.authenticationUserDetailsService.loadUserDetails(token);
    }

須要注意的是爲何要定義authenticationUserDetailsService這個bean。因爲CAS須要authentication-manager標籤下定義<security:authentication-provider ref="casAuthenticationProvider"/>,而不是以前所介紹的
user-service-ref屬性,因此這裏僅僅定義了一個provider,而沒有注入UserDetailsService,因此這裏須要單獨定義authenticationUserDetailsService這個bean,並注入到CasAuthenticationProvider中。

這裏須要對CasAuthenticationToken、CasAssertionAuthenticationToken單獨解釋一下

CasAuthenticationToken:一個成功經過的CAS認證,與UsernamePasswordAuthenticationToken同樣,都是繼承於AbstractAuthenticationToken,而且最終會保存到SecurityContext上下文、session中

CasAssertionAuthenticationToken:一個臨時的認證對象用於輔助獲取UserDetails

 

配置文件中幾個bean定義這裏就不一一分析了,都是爲了輔助完成CAS認證、跳轉的工做。 

 

如今,能夠對整個CAS認證的過程總結一下了:

1.客戶端發起一個請求,試圖訪問系統系統中受保護的url

2.各filter鏈進行攔截並作相應處理,因爲沒有經過認證,ExceptionTranslationFilter過濾器會捕獲到訪問拒絕異常,並把該異常交給入口點處理

3.CAS 認證對應的入口點直接跳轉到CAS Server端的登陸界面,並攜帶參數service(通常爲url:……/j_spring_cas_security_check)

4.CAS Server對登陸信息進行處理,若是登陸成功,就跳轉到應用系統中service指定的url,並攜帶ticket

5.應用系統中的各filter鏈再次對該url攔截,此時CasAuthenticationFilter攔截到j_spring_cas_security_check,就會對ticket進行驗證,驗證成功返回一個身份斷言,再經過身份斷言從當前應用系統中獲取對應的UserDetails、GrantedAuthority。此時,若是步驟1中受保護的url權限列表有一個權限存在於GrantedAuthority列表中,說明有權限訪問,直接響應客戶端所試圖訪問的url

相關文章
相關標籤/搜索