zoukankan      html  css  js  c++  java
  • 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
    
        }
    }
     
  • 相关阅读:
    【Windows】Windows服务管家婆之Service Control Manager
    【Python】python文件名和文件路径操作
    【Python】python 调用c语言函数
    IIS 之 HTTP Error 404.2 – Not Found(ISAPI 和 CGI 限制)
    IIS 之 HTTP错误 404.17
    IIS 之 HTTP 错误 404.3
    IIS 之 HTTP 错误 403.14
    IIS 之 打开/关闭 Internet 信息服务
    Wcf 之 配置文件解析
    Web Service 之 开发、部署
  • 原文地址:https://www.cnblogs.com/wangyang108/p/5843877.html
Copyright © 2011-2022 走看看