cas sso單點登錄系列1_cas-client Filter源碼解碼(轉)


轉:http://blog.csdn.net/ae6623/article/details/8841801?utm_source=tuicool&utm_medium=referral

/*  Copyright (c) 2000-2004 Yale University. All rights reserved. 
 *  See full notice at end.
 */

package edu.yale.its.tp.cas.client.filter;

import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import edu.yale.its.tp.cas.client.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/*
 * 
 * @author Shawn Bayern
 * @author Drew Mazurek
 * @author andrew.petro@yale.edu
 */
public class CASFilter implements Filter {

    private static Log log = LogFactory.getLog(CASFilter.class);

    // Filter initialization parameters
    //必須參數
    /**
     * loginUrl:指定 CAS 提供登錄頁面的 URL
     */
    public final static String LOGIN_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.loginUrl";

    /**
     * validateUrl:指定 CAS 提供 service ticket 或 proxy ticket 驗證服務的 URL 
     */
    public final static String VALIDATE_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.validateUrl";

    /**
     * serviceUrl:本web項目的URL,該參數指定過后將覆蓋 serverName 參數,成為登錄成功過后重定向的目的地址 
     */
    public final static String SERVICE_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.serviceUrl";

    /**
     * serverName:全主機端口號,指定客戶端的域名和端口,是指客戶端應用所在機器而不是 CAS Server 所在機器,該參數或 serviceUrl 至少有一個必須指定
     */
    public final static String SERVERNAME_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.serverName";

    //可選參數
    /**
     * renew:如果指定為 true,那么受保護的資源每次被訪問時均要求用戶重新進行驗證,而不管之前是否已經通過 
     */
    public final static String RENEW_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.renew";

    /**
     * authorizedProxy:用於允許當前應用從代理處獲取 proxy tickets,該參數接受以空格分隔開的多個 proxy URLs,但實際使用只需要一個成功即可。當指定該參數過后,需要修改 validateUrl 到 proxyValidate,
     */
    public final static String AUTHORIZED_PROXY_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.authorizedProxy";

    /**
     * proxyCallbackUrl:用於當前應用需要作為其他服務的代理(proxy)時獲取 Proxy Granting Ticket 的地址 
     */
    public final static String PROXY_CALLBACK_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.proxyCallbackUrl";

    /**
     * wrapRequest:如果指定為 true,那么 CASFilter 將重新包裝 HttpRequest,並且使 getRemoteUser() 方法返回當前登錄用戶的用戶名
     */
    public final static String WRAP_REQUESTS_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.wrapRequest";

    /**
     * gateway:這個參數很奇葩,一開始沒讀懂是干嘛的。。官方解釋是一旦發生過CAS重定向,過濾器將不會自動重新設置登錄的用戶。然后你可以提供一個明確的CAS登錄鏈接(HTTPS:/ / CAS服務器/ CAS /登錄?服務= HTTP:/ /應用程序)或建立映射到不同的路徑的過濾器的兩個實例。一個實例將gateway實現。當你需要登錄的用戶,直接轉到其他過濾器。
     * 是的你沒有想錯,這一句話着實讓人不知道是要說明什么,於是萬能的百度上有且僅有一個前輩說出來了這個參數其實是和renew互斥的,renew就是說無論如何都得重新驗證此用戶,不管你session中有沒有上下文信息。而gateway則是只要檢測到session中有sso上下文,就不再重新認證
     */
    public final static String GATEWAY_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.gateway";

    public final static String CAS_FILTER_USER = "edu.yale.its.tp.cas.client.filter.user";

    public final static String CAS_FILTER_RECEIPT = "edu.yale.its.tp.cas.client.filter.receipt";

    private static final String CAS_FILTER_GATEWAYED = "edu.yale.its.tp.cas.client.filter.didGateway";

    // *********************************************************************
    // Configuration state


    private String casLogin;

    private String casValidate;

    private String casServiceUrl;

    private String casServerName;

    private String casProxyCallbackUrl;

    private boolean casRenew;

    private boolean wrapRequest;

    private boolean casGateway = false;

    /**
     * 對proxyticketreceptor URL授權代理在過濾器的路徑的服務列表
     */
    private List authorizedProxies = new ArrayList();

    // *********************************************************************
    // Initialization

    public void init(FilterConfig config) throws ServletException {
        //拿到參數
        casLogin = config.getInitParameter(LOGIN_INIT_PARAM);
        casValidate = config.getInitParameter(VALIDATE_INIT_PARAM);
        casServiceUrl = config.getInitParameter(SERVICE_INIT_PARAM);
        String casAuthorizedProxy = config.getInitParameter(AUTHORIZED_PROXY_INIT_PARAM);
        casRenew = Boolean.valueOf(config.getInitParameter(RENEW_INIT_PARAM)).booleanValue();
        casServerName = config.getInitParameter(SERVERNAME_INIT_PARAM);
        casProxyCallbackUrl = config.getInitParameter(PROXY_CALLBACK_INIT_PARAM);
        wrapRequest = Boolean.valueOf(config.getInitParameter(WRAP_REQUESTS_INIT_PARAM)).booleanValue();
        casGateway = Boolean.valueOf(config.getInitParameter(GATEWAY_INIT_PARAM)).booleanValue();

        if (casGateway && Boolean.valueOf(casRenew).booleanValue()) {
            //這倆參數不能一起設置為true
            throw new ServletException("gateway and renew cannot both be true in filter configuration");
        }
        if (casServerName != null && casServiceUrl != null) {
            //這倆參數也不能一起設置
            throw new ServletException("serverName and serviceUrl cannot both be set: choose one.");
        }
        if (casServerName == null && casServiceUrl == null) {
            //這倆參數也不能一起為null
            throw new ServletException("one of serverName or serviceUrl must be set.");
        }
        if (casServiceUrl != null) {
            //檢測uri前綴
            if (!(casServiceUrl.startsWith("https://") || (casServiceUrl.startsWith("http://")))) {
                throw new ServletException("service URL must start with http:// or https://; its current value is [" + casServiceUrl + "]");
            }
        }

        if (casValidate == null) {
            //cas驗證用戶的網址不能為空
            throw new ServletException("validateUrl parameter must be set.");
        }
        if (!casValidate.startsWith("https://")) {
            //如果cas認證網址不是以https開頭,就報錯。。如果你是用http請求,可以屏蔽掉這個判斷語句
            throw new ServletException("validateUrl must start with https://, its current value is [" + casValidate + "]");
        }
        //代理是否為空
        if (casAuthorizedProxy != null) {

            // parse and remember authorized proxies
            StringTokenizer casProxies = new StringTokenizer(casAuthorizedProxy);
            while (casProxies.hasMoreTokens()) {
                //授權的標記
                String anAuthorizedProxy = casProxies.nextToken();
                //https前綴檢測
                if (!anAuthorizedProxy.startsWith("https://")) {
                    throw new ServletException("CASFilter initialization parameter for authorized proxies " + "must be a whitespace delimited list of authorized proxies.  " + "Authorized proxies must be secure (https) addresses.  This one wasn't: [" + anAuthorizedProxy + "]");
                }
                //將所有授權的代理添加到list中(唉,着實不知道是干什么的,也許幾年后回來讀讀應該能知道答案,2013年4月22日14:56:37)
                this.authorizedProxies.add(anAuthorizedProxy);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug(("CASFilter initialized as: [" + toString() + "]"));
        }
    }

    // *********************************************************************
    // Filter processing
    // 過濾器處理
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws ServletException, IOException {
        
        //核心思想:首先檢查session中有無憑證receipt,如果有,那么就要去下個過濾器鏈進行處理,如果無,則獲取傳參ticket,如果有ticket,就經過getAuthenticatedUser()方法去拿到receipt憑證,如果無(這中間會有一些對renew或者gateway的處理),就立即進入cas服務端進行登錄
        if (log.isTraceEnabled()) {
            log.trace("entering doFilter()");
        }

        // make sure we've got an HTTP request
        if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
            log.error("doFilter() called on a request or response that was not an HttpServletRequest or response.");
            throw new ServletException("CASFilter protects only HTTP resources");
        }

        // Is this a request for the proxy callback listener? If so, pass
        // it through
        if (casProxyCallbackUrl != null && casProxyCallbackUrl.endsWith(((HttpServletRequest) request).getRequestURI()) && request.getParameter("pgtId") != null && request.getParameter("pgtIou") != null) {
            log.trace("passing through what we hope is CAS's request for proxy ticket receptor.");
            fc.doFilter(request, response);
            return;
        }

        // Wrap the request if desired
        if (wrapRequest) {
            log.trace("Wrapping request with CASFilterRequestWrapper.");
            request = new CASFilterRequestWrapper((HttpServletRequest) request);
        }
        // 1.從當前web應用中拿到session
        HttpSession session = ((HttpServletRequest) request).getSession();

        // if our attribute's already present and valid, pass through the filter chain

        // 1.1.如果存在一個票據(令牌,憑證),就要跳到下一個過濾器鏈(去驗證此票據的真實性,因為此票據的真實性是未知的)
        CASReceipt receipt = (CASReceipt) session.getAttribute(CAS_FILTER_RECEIPT);
        if (receipt != null && isReceiptAcceptable(receipt)) {
            log.trace("CAS_FILTER_RECEIPT attribute was present and acceptable - passing  request through filter..");
            fc.doFilter(request, response);
            return;
        }
     
        // otherwise, we need to authenticate via CAS
        // 1.2.如果receipt(令牌)不存在就先拿到ticket,我們要去cas驗證用戶進行登錄
        String ticket = request.getParameter("ticket");
        // no ticket? abort request processing and redirect
        //如果ticket為空
        if (ticket == null || ticket.equals("")) {
            log.trace("CAS ticket was not present on request.");

            // 4.1判斷是否經過網關參數(didGateway這個參數否已經經過網關的一個標記參數,表示不再進行認證)
            // did we go through the gateway already?
            boolean didGateway = Boolean.valueOf((String) session.getAttribute(CAS_FILTER_GATEWAYED)).booleanValue();
            // 4.1.1沒有casLogin的配置信息下的異常處理
            if (casLogin == null) {
                // TODO: casLogin should probably be ensured to not be null at filter initialization. -awp9
                log.fatal("casLogin was not set, so filter cannot redirect request for authentication.");
                throw new ServletException("When CASFilter protects pages that do not receive a 'ticket' " + "parameter, it needs a edu.yale.its.tp.cas.client.filter.loginUrl " + "filter parameter");
            }
            // 4.2如果網關標記為false,設置CAS_FILTER_GATEWAYED屬性為true,並跳轉到cas服務端進行驗證
            if (!didGateway) {
                log.trace("Did not previously gateway.  Setting session attribute to true.");
                session.setAttribute(CAS_FILTER_GATEWAYED, "true");
                redirectToCAS((HttpServletRequest) request, (HttpServletResponse) response);
                // abort chain
                return;
            } else {
                log.trace("Previously gatewayed.");
                // 4.3 如果有網關參數(之前已經通過了網關),就不再進行驗證,從而進入下一個過濾器處理即可。
                // if we should be logged in, make sure validation succeeded
                if (casGateway || session.getAttribute(CAS_FILTER_USER) != null) {
                    //已經通過了驗證和授權。。
                    log.trace("casGateway was true and CAS_FILTER_USER set: passing request along filter chain.");
                    // continue processing the request 交給下一個過濾器
                    fc.doFilter(request, response);
                    return;
                } else {
                    // 其他情況下,跳往cas服務端
                    // unknown state... redirect to CAS
                    //將經過網關的參數didGateway設置為true
                    session.setAttribute(CAS_FILTER_GATEWAYED, "true");
                    redirectToCAS((HttpServletRequest) request, (HttpServletResponse) response);
                    
                    // abort chain
                    return;
                }
            }
        }
    
        try {
            // ticket存在,就經過getAuthenticatedUser()方法去拿到receipt,初步判斷此方法是為根據request中的ticket參數組裝了一個數據發送給了cas服務端進行判斷此ticket是否是正確的合法的(它可能是使用代理類進行的實現)
            receipt = getAuthenticatedUser((HttpServletRequest) request);
        } catch (CASAuthenticationException e) {
            log.error(e);
            throw new ServletException(e);
        }

        if (!isReceiptAcceptable(receipt)) {
            //檢測授權不被認可,就是非法的。
            throw new ServletException("Authentication was technically successful but rejected as a matter of policy. [" + receipt + "]");
        }
        //既然拿到了憑證,就去拿到session中是否有相關信息,並寫入CASFilter.CAS_FILTER_RECEIPT
        // Store the authenticated user in the session
        if (session != null) { // probably unnecessary
            //將username(用戶名)信息放入session中
            session.setAttribute(CAS_FILTER_USER, receipt.getUserName());
            //放入票據
            session.setAttribute(CASFilter.CAS_FILTER_RECEIPT, receipt);
            // don't store extra unnecessary session state
            //不要儲存額外的不必要的會話狀態
            session.removeAttribute(CAS_FILTER_GATEWAYED);
        }
        if (log.isTraceEnabled()) {
            log.trace("validated ticket to get authenticated receipt [" + receipt + "], now passing request along filter chain.");
        }

        // continue processing the request
        //進入下一個過濾器進行處理
        fc.doFilter(request, response);
        log.trace("returning from doFilter()");
    }

    /**
     * Is this receipt acceptable as evidence of authentication by credentials that would have been acceptable to this path? Current implementation checks whether from renew and whether proxy was authorized.
     * 
     * @param receipt 票據
     * @return true if acceptable, false otherwise
     */
    private boolean isReceiptAcceptable(CASReceipt receipt) {
        if (receipt == null)
            throw new IllegalArgumentException("Cannot evaluate a null receipt.");
        if (this.casRenew && !receipt.isPrimaryAuthentication()) {
            return false;
        }
        if (receipt.isProxied()) {
            if (!this.authorizedProxies.contains(receipt.getProxyingService())) {
                return false;
            }
        }
        return true;
    }

    // *********************************************************************
    // Utility methods

    /**
     * Converts a ticket parameter to a CASReceipt, taking into account an optionally configured trusted proxy in the tier immediately in front of us.
     * 
     * @throws ServletException -
     *             when unable to get service for request
     * @throws CASAuthenticationException -
     *             on authentication failure
     */
    private CASReceipt getAuthenticatedUser(HttpServletRequest request) throws ServletException, CASAuthenticationException {
        log.trace("entering getAuthenticatedUser()");
        ProxyTicketValidator pv = null;

        pv = new ProxyTicketValidator();
        pv.setCasValidateUrl(casValidate);
        pv.setServiceTicket(request.getParameter("ticket"));
        pv.setService(getService(request));
        pv.setRenew(Boolean.valueOf(casRenew).booleanValue());
        if (casProxyCallbackUrl != null) {
            pv.setProxyCallbackUrl(casProxyCallbackUrl);
        }
        if (log.isDebugEnabled()) {
            log.debug("about to validate ProxyTicketValidator: [" + pv + "]");
        }

        return CASReceipt.getReceipt(pv);

    }

    /**
     * Returns either the configured service or figures it out for the current request. The returned service is URL-encoded.
     */
    private String getService(HttpServletRequest request) throws ServletException {

        log.trace("entering getService()");
        String serviceString;

        // ensure we have a server name or service name
        if (casServerName == null && casServiceUrl == null)
            throw new ServletException("need one of the following configuration " + "parameters: edu.yale.its.tp.cas.client.filter.serviceUrl or " + "edu.yale.its.tp.cas.client.filter.serverName");

        // use the given string if it's provided
        if (casServiceUrl != null)
            serviceString = URLEncoder.encode(casServiceUrl);
        else
            // otherwise, return our best guess at the service
            serviceString = Util.getService(request, casServerName);
        if (log.isTraceEnabled()) {
            log.trace("returning from getService() with service [" + serviceString + "]");
        }
        return serviceString;
    }

    /**
     * Redirects the user to CAS, determining the service from the request.
     */
    private void redirectToCAS(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        if (log.isTraceEnabled()) {
            log.trace("entering redirectToCAS()");
        }

        String casLoginString = casLogin + "?service=" + getService((HttpServletRequest) request) + ((casRenew) ? "&renew=true" : "") + (casGateway ? "&gateway=true" : "");

        if (log.isDebugEnabled()) {
            log.debug("Redirecting browser to [" + casLoginString + ")");
        }
        ((HttpServletResponse) response).sendRedirect(casLoginString);

        if (log.isTraceEnabled()) {
            log.trace("returning from redirectToCAS()");
        }
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[CASFilter:");
        sb.append(" casGateway=");
        sb.append(this.casGateway);
        sb.append(" wrapRequest=");
        sb.append(this.wrapRequest);

        sb.append(" casAuthorizedProxies=[");
        sb.append(this.authorizedProxies);
        sb.append("]");

        if (this.casLogin != null) {
            sb.append(" casLogin=[");
            sb.append(this.casLogin);
            sb.append("]");
        } else {
            sb.append(" casLogin=NULL!!!!!");
        }

        if (this.casProxyCallbackUrl != null) {
            sb.append(" casProxyCallbackUrl=[");
            sb.append(casProxyCallbackUrl);
            sb.append("]");
        }

        if (this.casRenew) {
            sb.append(" casRenew=true");
        }

        if (this.casServerName != null) {
            sb.append(" casServerName=[");
            sb.append(casServerName);
            sb.append("]");
        }

        if (this.casServiceUrl != null) {
            sb.append(" casServiceUrl=[");
            sb.append(casServiceUrl);
            sb.append("]");
        }

        if (this.casValidate != null) {
            sb.append(" casValidate=[");
            sb.append(casValidate);
            sb.append("]");
        } else {
            sb.append(" casValidate=NULL!!!");
        }

        return sb.toString();
    }

    /* (non-Javadoc)
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {
        // TODO Auto-generated method stub

    }
}
 

 


免責聲明!

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



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