zoukankan      html  css  js  c++  java
  • SSO单点登录系列1:cas客户端源码分析cas-client-java-2.1.1.jar

    落雨 cas 单点登录


    希望能给以后来研究cas的兄弟留下一点思路,也算是研究了两天的成果,外国人的代码写的很晦涩,翻译下来也没有时间继续跟进,所以有错误的还请大家跟帖和我讨论,qq 394263788


    edu.yale.its.tp.cas.client.filter源码分析:


     

    1. /*  Copyright (c) 2000-2004 Yale University. All rights reserved.  
    2.  *  See full notice at end. 
    3.  */  
    4.   
    5. package edu.yale.its.tp.cas.client.filter;  
    6.   
    7. import java.io.*;  
    8. import java.net.*;  
    9. import java.util.*;  
    10. import javax.servlet.*;  
    11. import javax.servlet.http.*;  
    12. import edu.yale.its.tp.cas.client.*;  
    13.   
    14. import org.apache.commons.logging.Log;  
    15. import org.apache.commons.logging.LogFactory;  
    16.   
    17. /* 
    18.  *  
    19.  * @author Shawn Bayern 
    20.  * @author Drew Mazurek 
    21.  * @author andrew.petro@yale.edu 
    22.  */  
    23. public class CASFilter implements Filter {  
    24.   
    25.     private static Log log = LogFactory.getLog(CASFilter.class);  
    26.   
    27.     // Filter initialization parameters  
    28.     //必须参数  
    29.     /** 
    30.      * loginUrl:指定 CAS 提供登录页面的 URL 
    31.      */  
    32.     public final static String LOGIN_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.loginUrl";  
    33.   
    34.     /** 
    35.      * validateUrl:指定 CAS 提供 service ticket 或 proxy ticket 验证服务的 URL  
    36.      */  
    37.     public final static String VALIDATE_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.validateUrl";  
    38.   
    39.     /** 
    40.      * serviceUrl:本web项目的URL,该参数指定过后将覆盖 serverName 参数,成为登录成功过后重定向的目的地址  
    41.      */  
    42.     public final static String SERVICE_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.serviceUrl";  
    43.   
    44.     /** 
    45.      * serverName:全主机端口号,指定客户端的域名和端口,是指客户端应用所在机器而不是 CAS Server 所在机器,该参数或 serviceUrl 至少有一个必须指定 
    46.      */  
    47.     public final static String SERVERNAME_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.serverName";  
    48.   
    49.     //可选参数  
    50.     /** 
    51.      * renew:如果指定为 true,那么受保护的资源每次被访问时均要求用户重新进行验证,而不管之前是否已经通过  
    52.      */  
    53.     public final static String RENEW_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.renew";  
    54.   
    55.     /** 
    56.      * authorizedProxy:用于允许当前应用从代理处获取 proxy tickets,该参数接受以空格分隔开的多个 proxy URLs,但实际使用只需要一个成功即可。当指定该参数过后,需要修改 validateUrl 到 proxyValidate, 
    57.      */  
    58.     public final static String AUTHORIZED_PROXY_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.authorizedProxy";  
    59.   
    60.     /** 
    61.      * proxyCallbackUrl:用于当前应用需要作为其他服务的代理(proxy)时获取 Proxy Granting Ticket 的地址  
    62.      */  
    63.     public final static String PROXY_CALLBACK_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.proxyCallbackUrl";  
    64.   
    65.     /** 
    66.      * wrapRequest:如果指定为 true,那么 CASFilter 将重新包装 HttpRequest,并且使 getRemoteUser() 方法返回当前登录用户的用户名 
    67.      */  
    68.     public final static String WRAP_REQUESTS_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.wrapRequest";  
    69.   
    70.     /** 
    71.      * gateway:这个参数很奇葩,一开始没读懂是干嘛的。。官方解释是一旦发生过CAS重定向,过滤器将不会自动重新设置登录的用户。然后你可以提供一个明确的CAS登录链接(HTTPS:/ / CAS服务器/ CAS /登录?服务= HTTP:/ /应用程序)或建立映射到不同的路径的过滤器的两个实例。一个实例将gateway实现。当你需要登录的用户,直接转到其他过滤器。 
    72.      * 是的你没有想错,这一句话着实让人不知道是要说明什么,于是万能的百度上有且仅有一个前辈说出来了这个参数其实是和renew互斥的,renew就是说无论如何都得重新验证此用户,不管你session中有没有上下文信息。而gateway则是只要检测到session中有sso上下文,就不再重新认证 
    73.      */  
    74.     public final static String GATEWAY_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.gateway";  
    75.   
    76.     public final static String CAS_FILTER_USER = "edu.yale.its.tp.cas.client.filter.user";  
    77.   
    78.     public final static String CAS_FILTER_RECEIPT = "edu.yale.its.tp.cas.client.filter.receipt";  
    79.   
    80.     private static final String CAS_FILTER_GATEWAYED = "edu.yale.its.tp.cas.client.filter.didGateway";  
    81.   
    82.     // *********************************************************************  
    83.     // Configuration state  
    84.   
    85.   
    86.     private String casLogin;  
    87.   
    88.     private String casValidate;  
    89.   
    90.     private String casServiceUrl;  
    91.   
    92.     private String casServerName;  
    93.   
    94.     private String casProxyCallbackUrl;  
    95.   
    96.     private boolean casRenew;  
    97.   
    98.     private boolean wrapRequest;  
    99.   
    100.     private boolean casGateway = false;  
    101.   
    102.     /** 
    103.      * 对proxyticketreceptor URL授权代理在过滤器的路径的服务列表 
    104.      */  
    105.     private List authorizedProxies = new ArrayList();  
    106.   
    107.     // *********************************************************************  
    108.     // Initialization  
    109.   
    110.     public void init(FilterConfig config) throws ServletException {  
    111.         //拿到参数  
    112.         casLogin = config.getInitParameter(LOGIN_INIT_PARAM);  
    113.         casValidate = config.getInitParameter(VALIDATE_INIT_PARAM);  
    114.         casServiceUrl = config.getInitParameter(SERVICE_INIT_PARAM);  
    115.         String casAuthorizedProxy = config.getInitParameter(AUTHORIZED_PROXY_INIT_PARAM);  
    116.         casRenew = Boolean.valueOf(config.getInitParameter(RENEW_INIT_PARAM)).booleanValue();  
    117.         casServerName = config.getInitParameter(SERVERNAME_INIT_PARAM);  
    118.         casProxyCallbackUrl = config.getInitParameter(PROXY_CALLBACK_INIT_PARAM);  
    119.         wrapRequest = Boolean.valueOf(config.getInitParameter(WRAP_REQUESTS_INIT_PARAM)).booleanValue();  
    120.         casGateway = Boolean.valueOf(config.getInitParameter(GATEWAY_INIT_PARAM)).booleanValue();  
    121.   
    122.         if (casGateway && Boolean.valueOf(casRenew).booleanValue()) {  
    123.             //这俩参数不能一起设置为true  
    124.             throw new ServletException("gateway and renew cannot both be true in filter configuration");  
    125.         }  
    126.         if (casServerName != null && casServiceUrl != null) {  
    127.             //这俩参数也不能一起设置  
    128.             throw new ServletException("serverName and serviceUrl cannot both be set: choose one.");  
    129.         }  
    130.         if (casServerName == null && casServiceUrl == null) {  
    131.             //这俩参数也不能一起为null  
    132.             throw new ServletException("one of serverName or serviceUrl must be set.");  
    133.         }  
    134.         if (casServiceUrl != null) {  
    135.             //检测uri前缀  
    136.             if (!(casServiceUrl.startsWith("https://") || (casServiceUrl.startsWith("http://")))) {  
    137.                 throw new ServletException("service URL must start with http:// or https://; its current value is [" + casServiceUrl + "]");  
    138.             }  
    139.         }  
    140.   
    141.         if (casValidate == null) {  
    142.             //cas验证用户的网址不能为空  
    143.             throw new ServletException("validateUrl parameter must be set.");  
    144.         }  
    145.         if (!casValidate.startsWith("https://")) {  
    146.             //如果cas认证网址不是以https开头,就报错。。如果你是用http请求,可以屏蔽掉这个判断语句  
    147.             throw new ServletException("validateUrl must start with https://, its current value is [" + casValidate + "]");  
    148.         }  
    149.         //代理是否为空  
    150.         if (casAuthorizedProxy != null) {  
    151.   
    152.             // parse and remember authorized proxies  
    153.             StringTokenizer casProxies = new StringTokenizer(casAuthorizedProxy);  
    154.             while (casProxies.hasMoreTokens()) {  
    155.                 //授权的标记  
    156.                 String anAuthorizedProxy = casProxies.nextToken();  
    157.                 //https前缀检测  
    158.                 if (!anAuthorizedProxy.startsWith("https://")) {  
    159.                     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 + "]");  
    160.                 }  
    161.                 //将所有授权的代理添加到list中(唉,着实不知道是干什么的,也许几年后回来读读应该能知道答案,2013年4月22日14:56:37)  
    162.                 this.authorizedProxies.add(anAuthorizedProxy);  
    163.             }  
    164.         }  
    165.   
    166.         if (log.isDebugEnabled()) {  
    167.             log.debug(("CASFilter initialized as: [" + toString() + "]"));  
    168.         }  
    169.     }  
    170.   
    171.     // *********************************************************************  
    172.     // Filter processing  
    173.     // 过滤器处理  
    174.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws ServletException, IOException {  
    175.           
    176.         //核心思想:首先检查session中有无凭证receipt,如果有,那么就要去下个过滤器链进行处理,如果无,则获取传参ticket,如果有ticket,就经过getAuthenticatedUser()方法去拿到receipt凭证,如果无(这中间会有一些对renew或者gateway的处理),就立即进入cas服务端进行登录  
    177.         if (log.isTraceEnabled()) {  
    178.             log.trace("entering doFilter()");  
    179.         }  
    180.   
    181.         // make sure we've got an HTTP request  
    182.         if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {  
    183.             log.error("doFilter() called on a request or response that was not an HttpServletRequest or response.");  
    184.             throw new ServletException("CASFilter protects only HTTP resources");  
    185.         }  
    186.   
    187.         // Is this a request for the proxy callback listener? If so, pass  
    188.         // it through  
    189.         if (casProxyCallbackUrl != null && casProxyCallbackUrl.endsWith(((HttpServletRequest) request).getRequestURI()) && request.getParameter("pgtId") != null && request.getParameter("pgtIou") != null) {  
    190.             log.trace("passing through what we hope is CAS's request for proxy ticket receptor.");  
    191.             fc.doFilter(request, response);  
    192.             return;  
    193.         }  
    194.   
    195.         // Wrap the request if desired  
    196.         if (wrapRequest) {  
    197.             log.trace("Wrapping request with CASFilterRequestWrapper.");  
    198.             request = new CASFilterRequestWrapper((HttpServletRequest) request);  
    199.         }  
    200.         // 1.从当前web应用中拿到session  
    201.         HttpSession session = ((HttpServletRequest) request).getSession();  
    202.   
    203.         // if our attribute's already present and valid, pass through the filter chain  
    204.   
    205.         // 1.1.如果存在一个票据(令牌,凭证),就要跳到下一个过滤器链(去验证此票据的真实性,因为此票据的真实性是未知的)  
    206.         CASReceipt receipt = (CASReceipt) session.getAttribute(CAS_FILTER_RECEIPT);  
    207.         if (receipt != null && isReceiptAcceptable(receipt)) {  
    208.             log.trace("CAS_FILTER_RECEIPT attribute was present and acceptable - passing  request through filter..");  
    209.             fc.doFilter(request, response);  
    210.             return;  
    211.         }  
    212.        
    213.         // otherwise, we need to authenticate via CAS  
    214.         // 1.2.如果receipt(令牌)不存在就先拿到ticket,我们要去cas验证用户进行登录  
    215.         String ticket = request.getParameter("ticket");  
    216.         // no ticket? abort request processing and redirect  
    217.         //如果ticket为空  
    218.         if (ticket == null || ticket.equals("")) {  
    219.             log.trace("CAS ticket was not present on request.");  
    220.   
    221.             // 4.1判断是否经过网关参数(didGateway这个参数否已经经过网关的一个标记参数,表示不再进行认证)  
    222.             // did we go through the gateway already?  
    223.             boolean didGateway = Boolean.valueOf((String) session.getAttribute(CAS_FILTER_GATEWAYED)).booleanValue();  
    224.             // 4.1.1没有casLogin的配置信息下的异常处理  
    225.             if (casLogin == null) {  
    226.                 // TODO: casLogin should probably be ensured to not be null at filter initialization. -awp9  
    227.                 log.fatal("casLogin was not set, so filter cannot redirect request for authentication.");  
    228.                 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");  
    229.             }  
    230.             // 4.2如果网关标记为false,设置CAS_FILTER_GATEWAYED属性为true,并跳转到cas服务端进行验证  
    231.             if (!didGateway) {  
    232.                 log.trace("Did not previously gateway.  Setting session attribute to true.");  
    233.                 session.setAttribute(CAS_FILTER_GATEWAYED, "true");  
    234.                 redirectToCAS((HttpServletRequest) request, (HttpServletResponse) response);  
    235.                 // abort chain  
    236.                 return;  
    237.             } else {  
    238.                 log.trace("Previously gatewayed.");  
    239.                 // 4.3 如果有网关参数(之前已经通过了网关),就不再进行验证,从而进入下一个过滤器处理即可。  
    240.                 // if we should be logged in, make sure validation succeeded  
    241.                 if (casGateway || session.getAttribute(CAS_FILTER_USER) != null) {  
    242.                     //已经通过了验证和授权。。  
    243.                     log.trace("casGateway was true and CAS_FILTER_USER set: passing request along filter chain.");  
    244.                     // continue processing the request 交给下一个过滤器  
    245.                     fc.doFilter(request, response);  
    246.                     return;  
    247.                 } else {  
    248.                     // 其他情况下,跳往cas服务端  
    249.                     // unknown state... redirect to CAS  
    250.                     //将经过网关的参数didGateway设置为true  
    251.                     session.setAttribute(CAS_FILTER_GATEWAYED, "true");  
    252.                     redirectToCAS((HttpServletRequest) request, (HttpServletResponse) response);  
    253.                       
    254.                     // abort chain  
    255.                     return;  
    256.                 }  
    257.             }  
    258.         }  
    259.       
    260.         try {  
    261.             // ticket存在,就经过getAuthenticatedUser()方法去拿到receipt,初步判断此方法是为根据request中的ticket参数组装了一个数据发送给了cas服务端进行判断此ticket是否是正确的合法的(它可能是使用代理类进行的实现)  
    262.             receipt = getAuthenticatedUser((HttpServletRequest) request);  
    263.         } catch (CASAuthenticationException e) {  
    264.             log.error(e);  
    265.             throw new ServletException(e);  
    266.         }  
    267.   
    268.         if (!isReceiptAcceptable(receipt)) {  
    269.             //检测授权不被认可,就是非法的。  
    270.             throw new ServletException("Authentication was technically successful but rejected as a matter of policy. [" + receipt + "]");  
    271.         }  
    272.         //既然拿到了凭证,就去拿到session中是否有相关信息,并写入CASFilter.CAS_FILTER_RECEIPT  
    273.         // Store the authenticated user in the session  
    274.         if (session != null) { // probably unnecessary  
    275.             //将username(用户名)信息放入session中  
    276.             session.setAttribute(CAS_FILTER_USER, receipt.getUserName());  
    277.             //放入票据  
    278.             session.setAttribute(CASFilter.CAS_FILTER_RECEIPT, receipt);  
    279.             // don't store extra unnecessary session state  
    280.             //不要储存额外的不必要的会话状态  
    281.             session.removeAttribute(CAS_FILTER_GATEWAYED);  
    282.         }  
    283.         if (log.isTraceEnabled()) {  
    284.             log.trace("validated ticket to get authenticated receipt [" + receipt + "], now passing request along filter chain.");  
    285.         }  
    286.   
    287.         // continue processing the request  
    288.         //进入下一个过滤器进行处理  
    289.         fc.doFilter(request, response);  
    290.         log.trace("returning from doFilter()");  
    291.     }  
    292.   
    293.     /** 
    294.      * 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. 
    295.      *  
    296.      * @param receipt 票据 
    297.      * @return true if acceptable, false otherwise 
    298.      */  
    299.     private boolean isReceiptAcceptable(CASReceipt receipt) {  
    300.         if (receipt == null)  
    301.             throw new IllegalArgumentException("Cannot evaluate a null receipt.");  
    302.         if (this.casRenew && !receipt.isPrimaryAuthentication()) {  
    303.             return false;  
    304.         }  
    305.         if (receipt.isProxied()) {  
    306.             if (!this.authorizedProxies.contains(receipt.getProxyingService())) {  
    307.                 return false;  
    308.             }  
    309.         }  
    310.         return true;  
    311.     }  
    312.   
    313.     // *********************************************************************  
    314.     // Utility methods  
    315.   
    316.     /** 
    317.      * Converts a ticket parameter to a CASReceipt, taking into account an optionally configured trusted proxy in the tier immediately in front of us. 
    318.      *  
    319.      * @throws ServletException - 
    320.      *             when unable to get service for request 
    321.      * @throws CASAuthenticationException - 
    322.      *             on authentication failure 
    323.      */  
    324.     private CASReceipt getAuthenticatedUser(HttpServletRequest request) throws ServletException, CASAuthenticationException {  
    325.         log.trace("entering getAuthenticatedUser()");  
    326.         ProxyTicketValidator pv = null;  
    327.   
    328.         pv = new ProxyTicketValidator();  
    329.         pv.setCasValidateUrl(casValidate);  
    330.         pv.setServiceTicket(request.getParameter("ticket"));  
    331.         pv.setService(getService(request));  
    332.         pv.setRenew(Boolean.valueOf(casRenew).booleanValue());  
    333.         if (casProxyCallbackUrl != null) {  
    334.             pv.setProxyCallbackUrl(casProxyCallbackUrl);  
    335.         }  
    336.         if (log.isDebugEnabled()) {  
    337.             log.debug("about to validate ProxyTicketValidator: [" + pv + "]");  
    338.         }  
    339.   
    340.         return CASReceipt.getReceipt(pv);  
    341.   
    342.     }  
    343.   
    344.     /** 
    345.      * Returns either the configured service or figures it out for the current request. The returned service is URL-encoded. 
    346.      */  
    347.     private String getService(HttpServletRequest request) throws ServletException {  
    348.   
    349.         log.trace("entering getService()");  
    350.         String serviceString;  
    351.   
    352.         // ensure we have a server name or service name  
    353.         if (casServerName == null && casServiceUrl == null)  
    354.             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");  
    355.   
    356.         // use the given string if it's provided  
    357.         if (casServiceUrl != null)  
    358.             serviceString = URLEncoder.encode(casServiceUrl);  
    359.         else  
    360.             // otherwise, return our best guess at the service  
    361.             serviceString = Util.getService(request, casServerName);  
    362.         if (log.isTraceEnabled()) {  
    363.             log.trace("returning from getService() with service [" + serviceString + "]");  
    364.         }  
    365.         return serviceString;  
    366.     }  
    367.   
    368.     /** 
    369.      * Redirects the user to CAS, determining the service from the request. 
    370.      */  
    371.     private void redirectToCAS(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {  
    372.         if (log.isTraceEnabled()) {  
    373.             log.trace("entering redirectToCAS()");  
    374.         }  
    375.   
    376.         String casLoginString = casLogin + "?service=" + getService((HttpServletRequest) request) + ((casRenew) ? "&renew=true" : "") + (casGateway ? "&gateway=true" : "");  
    377.   
    378.         if (log.isDebugEnabled()) {  
    379.             log.debug("Redirecting browser to [" + casLoginString + ")");  
    380.         }  
    381.         ((HttpServletResponse) response).sendRedirect(casLoginString);  
    382.   
    383.         if (log.isTraceEnabled()) {  
    384.             log.trace("returning from redirectToCAS()");  
    385.         }  
    386.     }  
    387.   
    388.     public String toString() {  
    389.         StringBuffer sb = new StringBuffer();  
    390.         sb.append("[CASFilter:");  
    391.         sb.append(" casGateway=");  
    392.         sb.append(this.casGateway);  
    393.         sb.append(" wrapRequest=");  
    394.         sb.append(this.wrapRequest);  
    395.   
    396.         sb.append(" casAuthorizedProxies=[");  
    397.         sb.append(this.authorizedProxies);  
    398.         sb.append("]");  
    399.   
    400.         if (this.casLogin != null) {  
    401.             sb.append(" casLogin=[");  
    402.             sb.append(this.casLogin);  
    403.             sb.append("]");  
    404.         } else {  
    405.             sb.append(" casLogin=NULL!!!!!");  
    406.         }  
    407.   
    408.         if (this.casProxyCallbackUrl != null) {  
    409.             sb.append(" casProxyCallbackUrl=[");  
    410.             sb.append(casProxyCallbackUrl);  
    411.             sb.append("]");  
    412.         }  
    413.   
    414.         if (this.casRenew) {  
    415.             sb.append(" casRenew=true");  
    416.         }  
    417.   
    418.         if (this.casServerName != null) {  
    419.             sb.append(" casServerName=[");  
    420.             sb.append(casServerName);  
    421.             sb.append("]");  
    422.         }  
    423.   
    424.         if (this.casServiceUrl != null) {  
    425.             sb.append(" casServiceUrl=[");  
    426.             sb.append(casServiceUrl);  
    427.             sb.append("]");  
    428.         }  
    429.   
    430.         if (this.casValidate != null) {  
    431.             sb.append(" casValidate=[");  
    432.             sb.append(casValidate);  
    433.             sb.append("]");  
    434.         } else {  
    435.             sb.append(" casValidate=NULL!!!");  
    436.         }  
    437.   
    438.         return sb.toString();  
    439.     }  
    440.   
    441.     /* (non-Javadoc) 
    442.      * @see javax.servlet.Filter#destroy() 
    443.      */  
    444.     public void destroy() {  
    445.         // TODO Auto-generated method stub  
    446.   
    447.     }  
    448. }  
    449.    

    注释都已经写在代码块里了

  • 相关阅读:
    pdb文件转
    C#是.NET吗?hr说:我们只招聘C#工程师,你是做.NET的和我们要求不符。。。
    C#中的==、Equal、ReferenceEqual > 转
    常用HTML代码转
    SQL Server数据库字段数据类型
    jQuery性能优化的28个建议 转
    收尾
    OSI七层网络协议之传输层
    将其他程序中的大纲文本插入到 PowerPoint 演示文稿中
    Applet 编程中多媒体文件放置的位置
  • 原文地址:https://www.cnblogs.com/austinspark-jessylu/p/6899818.html
Copyright © 2011-2022 走看看