cas4.2.7实现单点登录

Java框架

浏览数:196

2019-5-13

AD:资源代下载服务

准备前参考: 

 cas server下载地址

 cas client 下载地址

   安全cookie setSecure详解

   Spring通过构造方法注入的四种方式

 cas 学习博文

自定义登录页和登录认证

  cas server端的login-webflow详细流程

  CAS服务端自定义数据库认证用户

准备工作

  1. cas server下载之后解压,在项目根目录下执行 mvn clean 和 mvn install命令(如果使用给定的命令报错)。

  2. 根目录下会生成target目录,target中会有一个cas.war的文件,将这个文件通过 mvn install 发布到maven的本地仓库中。

  

mvn install:install-file -DgroupId=com.hjz -DartifactId=cas -Dversion=1.0.0 -Dfile=cas.war -Dpackaging=war -DgeneratePom=true

  3.项目中的依赖

<dependency>
  <groupId>com.hjz</groupId>
  <artifactId>cas</artifactId>
  <version>1.0.0</version>
  <type>war</type>
</dependency>

客户端cas ticket验证走查

  1.如果请求中携带了参数ticket则将会由TicketValidationFilter来对携带的ticket进行校验。TicketValidationFilter只是对验证ticket的这一类Filter的统称,其并不对应Cas Client中的一个具体类型。Cas Client中有多种验证ticket的Filter,都继承自AbstractTicketValidationFilter,它们的验证逻辑都是一致的,都有AbstractTicketValidationFilter实现,所不同的是使用的TicketValidator不一样。笔者这里将以Cas20TicketValidationFilter为例。

<context-param>
      <param-name>serverName</param-name>
      <param-value>http://127.0.0.1:8089</param-value>
</context-param>

<filter>
   <filter-name>casTicketValidationFilter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class>
   <init-param>
      <param-name>casServerUrlPrefix</param-name>
      <param-value>http://127.0.0.1:8080/cas</param-value>
   </init-param>
</filter>
<filter-mapping>
   <filter-name>casTicketValidationFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

  一个是用来指定Cas Server登录地址的casServerLoginUrl,另一个是用来指定认证成功后需要跳转地址的serverNameservice。service和serverName只需要指定一个就可以了。当两者都指定了,参数service将具有更高的优先级,即将以service指定的参数值为准。service和serverName的区别在于service指定的是一个确定的URL,认证成功后就会确切的跳转到service指定的URL;而serverName则是用来指定主机名,其格式为{protocol}:{hostName}:{port},如:https://localhost:8443,当指定的是serverName时,AuthenticationFilter将会把它附加上当前请求的URI,以及对应的查询参数来构造一个确定的URL,如指定serverName为“http://localhost”,而当前请求的URI为“/app”,查询参数为“a=b&b=c”,则对应认证成功后的跳转地址将为“http://localhost/app?a=b&b=c”。

       必须指定的参数:

   casServerUrlPrefix:用来指定Cas Server对应URL地址的前缀,如上面示例的“https://elim:8443/cas”。

   serverName或service:语义跟前面介绍的一致。

       可选参数:

   redirectAfterValidation :表示是否验证通过后重新跳转到该URL,但是不带参数ticket,默认为true。

   useSession :在验证ticket成功后会生成一个Assertion对象,如果useSession为true,则会将该对象存放到Session中。如果为false,则要求每次请求都需要携带ticket进行验证,显然useSession为false跟redirectAfterValidation为true是冲突的。默认为true。

  exceptionOnValidationFailure :表示ticket验证失败后是否需要抛出异常,默认为true。

   renew:当值为true时将发送“renew=true”到Cas Server,默认为false。

  

  2.通过模仿 Cas10TicketValidationFilter 的逻辑,自定义 ticket validation filter。

package com.xxx.web.cas;

import com.xxx.sdk.RedisUtils;
import com.xxx.web.cas.logout.entity.TenantUser;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.net.ssl.HostnameVerifier;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.proxy.AbstractEncryptedProxyGrantingTicketStorageImpl;
import org.jasig.cas.client.proxy.Cas20ProxyRetriever;
import org.jasig.cas.client.proxy.CleanUpTimerTask;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl;
import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
import org.jasig.cas.client.ssl.HttpsURLConnectionFactory;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.slf4j.Logger;
import uap.web.esapi.EncryptException;
import uap.web.utils.PropertyUtil;
import uap.web.utils.TokenGenerator;

public class TenantProxyReceivingTicketValidationFilter extends AbstractCasFilter {
   private static final String[] RESERVED_INIT_PARAMS = { "proxyGrantingTicketStorageClass", "proxyReceptorUrl",
         "acceptAnyProxy", "allowedProxyChains", "casServerUrlPrefix", "proxyCallbackUrl", "renew",
         "exceptionOnValidationFailure", "redirectAfterValidation", "useSession", "serverName", "service",
         "artifactParameterName", "serviceParameterName", "encodeServiceUrl", "millisBetweenCleanUps",
         "hostnameVerifier", "encoding", "config", "ticketValidatorClass" };
   private static final int DEFAULT_MILLIS_BETWEEN_CLEANUPS = 60000;
   private String proxyReceptorUrl;
   private Timer timer;
   private TimerTask timerTask;
   private int millisBetweenCleanUps;
   private static final String TENANT_ASSERTION = "tenant_assertion";
   private TicketValidator ticketValidator;
   private boolean redirectAfterValidation;
   private boolean exceptionOnValidationFailure;
   private boolean useSession;
   private ProxyGrantingTicketStorage proxyGrantingTicketStorage;

   public TenantProxyReceivingTicketValidationFilter() {
      this.redirectAfterValidation = true;

      this.exceptionOnValidationFailure = false;

      this.useSession = true;

      this.proxyGrantingTicketStorage = new ProxyGrantingTicketStorageImpl();
   }

   protected void initInternal(FilterConfig filterConfig) throws ServletException {
      setProxyReceptorUrl(getPropertyFromInitParams(filterConfig, "proxyReceptorUrl", null));

      String proxyGrantingTicketStorageClass = getPropertyFromInitParams(filterConfig,
            "proxyGrantingTicketStorageClass", null);

      if (proxyGrantingTicketStorageClass != null) {
         this.proxyGrantingTicketStorage = ((ProxyGrantingTicketStorage) ReflectUtils
               .newInstance(proxyGrantingTicketStorageClass, new Object[0]));

         if (this.proxyGrantingTicketStorage instanceof AbstractEncryptedProxyGrantingTicketStorageImpl) {
            AbstractEncryptedProxyGrantingTicketStorageImpl p = (AbstractEncryptedProxyGrantingTicketStorageImpl) this.proxyGrantingTicketStorage;
            String cipherAlgorithm = getPropertyFromInitParams(filterConfig, "cipherAlgorithm", "DESede");

            String secretKey = getPropertyFromInitParams(filterConfig, "secretKey", null);

            p.setCipherAlgorithm(cipherAlgorithm);
            try {
               if (secretKey != null)
                  p.setSecretKey(secretKey);
            } catch (Exception e) {
               throw new RuntimeException(e);
            }
         }
      }

      this.logger.trace("Setting proxyReceptorUrl parameter: {}", this.proxyReceptorUrl);
      this.millisBetweenCleanUps = Integer
            .parseInt(getPropertyFromInitParams(filterConfig, "millisBetweenCleanUps", Integer.toString(60000)));

      setExceptionOnValidationFailure(
            parseBoolean(getPropertyFromInitParams(filterConfig, "exceptionOnValidationFailure", "false")));

      this.logger.trace("Setting exceptionOnValidationFailure parameter: {}",
            Boolean.valueOf(this.exceptionOnValidationFailure));
      setRedirectAfterValidation(
            parseBoolean(getPropertyFromInitParams(filterConfig, "redirectAfterValidation", "true")));

      this.logger.trace("Setting redirectAfterValidation parameter: {}",
            Boolean.valueOf(this.redirectAfterValidation));
      setUseSession(parseBoolean(getPropertyFromInitParams(filterConfig, "useSession", "true")));
      this.logger.trace("Setting useSession parameter: {}", Boolean.valueOf(this.useSession));

      if ((!(this.useSession)) && (this.redirectAfterValidation)) {
         this.logger.warn(
               "redirectAfterValidation parameter may not be true when useSession parameter is false. Resetting it to false in order to prevent infinite redirects.");

         setRedirectAfterValidation(false);
      }

      setTicketValidator(getTicketValidator(filterConfig));

      super.initInternal(filterConfig);

      String servername = PropertyUtil.getPropertyByKey("servername");
      if (StringUtils.isNotBlank(servername))
         setServerName(servername);
      else
         setServerName(getPropertyFromInitParams(filterConfig, "serverName", null));
   }

   public void init() {
      super.init();
      CommonUtils.assertNotNull(this.proxyGrantingTicketStorage, "proxyGrantingTicketStorage cannot be null.");
      CommonUtils.assertNotNull(this.ticketValidator, "ticketValidator cannot be null.");
      if (this.timer == null) {
         this.timer = new Timer(true);
      }

      if (this.timerTask == null) {
         this.timerTask = new CleanUpTimerTask(this.proxyGrantingTicketStorage);
      }
      this.timer.schedule(this.timerTask, this.millisBetweenCleanUps, this.millisBetweenCleanUps);
   }

   private <T> T createNewTicketValidator(String ticketValidatorClass, String casServerUrlPrefix, Class<T> clazz) {
      if (CommonUtils.isBlank(ticketValidatorClass)) {
         return ReflectUtils.newInstance(clazz, new Object[] { casServerUrlPrefix });
      }

      return ReflectUtils.newInstance(ticketValidatorClass, new Object[] { casServerUrlPrefix });
   }

   protected final TicketValidator getTicketValidator(FilterConfig filterConfig) {
      String allowAnyProxy = getPropertyFromInitParams(filterConfig, "acceptAnyProxy", null);
      String allowedProxyChains = getPropertyFromInitParams(filterConfig, "allowedProxyChains", null);

      String casServerUrlPrefix = getCasUrl(filterConfig);

      String ticketValidatorClass = getPropertyFromInitParams(filterConfig, "ticketValidatorClass", null);
      Cas20ServiceTicketValidator validator;
      Cas20ServiceTicketValidator validator;
      if ((CommonUtils.isNotBlank(allowAnyProxy)) || (CommonUtils.isNotBlank(allowedProxyChains))) {
         Cas20ProxyTicketValidator v = (Cas20ProxyTicketValidator) createNewTicketValidator(ticketValidatorClass,
               casServerUrlPrefix, Cas20ProxyTicketValidator.class);

         v.setAcceptAnyProxy(parseBoolean(allowAnyProxy));
         v.setAllowedProxyChains(CommonUtils.createProxyList(allowedProxyChains));
         validator = v;
      } else {
         validator = (Cas20ServiceTicketValidator) createNewTicketValidator(ticketValidatorClass, casServerUrlPrefix,
               Cas20ServiceTicketValidator.class);
      }

      validator.setProxyCallbackUrl(getPropertyFromInitParams(filterConfig, "proxyCallbackUrl", null));
      validator.setProxyGrantingTicketStorage(this.proxyGrantingTicketStorage);

      HttpURLConnectionFactory factory = new HttpsURLConnectionFactory(getHostnameVerifier(filterConfig),
            getSSLConfig(filterConfig));

      validator.setURLConnectionFactory(factory);

      validator.setProxyRetriever(new Cas20ProxyRetriever(casServerUrlPrefix,
            getPropertyFromInitParams(filterConfig, "encoding", null), factory));

      validator.setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false")));
      validator.setEncoding(getPropertyFromInitParams(filterConfig, "encoding", null));

      Map additionalParameters = new HashMap();
      List params = Arrays.asList(RESERVED_INIT_PARAMS);

      for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) {
         String s = (String) e.nextElement();

         if (!(params.contains(s))) {
            additionalParameters.put(s, filterConfig.getInitParameter(s));
         }
      }

      validator.setCustomParameters(additionalParameters);
      return validator;
   }

   public void destroy() {
      super.destroy();
      this.timer.cancel();
   }

   protected final boolean preFilter(ServletRequest servletRequest, ServletResponse servletResponse,
         FilterChain filterChain) throws IOException, ServletException {
      HttpServletRequest request = (HttpServletRequest) servletRequest;
      HttpServletResponse response = (HttpServletResponse) servletResponse;
      String requestUri = request.getRequestURI();

      if ((CommonUtils.isEmpty(this.proxyReceptorUrl)) || (!(requestUri.endsWith(this.proxyReceptorUrl)))) {
         return true;
      }
      try {
         CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
      } catch (RuntimeException e) {
         this.logger.error(e.getMessage(), e);
         throw e;
      }

      return false;
   }

   public final void setProxyReceptorUrl(String proxyReceptorUrl) {
      this.proxyReceptorUrl = proxyReceptorUrl;
   }

   public void setProxyGrantingTicketStorage(ProxyGrantingTicketStorage storage) {
      this.proxyGrantingTicketStorage = storage;
   }

   public void setTimer(Timer timer) {
      this.timer = timer;
   }

   public void setTimerTask(TimerTask timerTask) {
      this.timerTask = timerTask;
   }

   public void setMillisBetweenCleanUps(int millisBetweenCleanUps) {
      this.millisBetweenCleanUps = millisBetweenCleanUps;
   }

   protected Properties getSSLConfig(FilterConfig filterConfig) {
      Properties properties = new Properties();
      String fileName = getPropertyFromInitParams(filterConfig, "sslConfigFile", null);

      if (fileName != null) {
         FileInputStream fis = null;
         try {
            fis = new FileInputStream(fileName);
            properties.load(fis);
            this.logger.trace("Loaded {} entries from {}", Integer.valueOf(properties.size()), fileName);
         } catch (IOException ioe) {
            this.logger.error(ioe.getMessage(), ioe);
         } finally {
            CommonUtils.closeQuietly(fis);
         }
      }
      return properties;
   }

   protected HostnameVerifier getHostnameVerifier(FilterConfig filterConfig) {
      String className = getPropertyFromInitParams(filterConfig, "hostnameVerifier", null);
      this.logger.trace("Using hostnameVerifier parameter: {}", className);
      String config = getPropertyFromInitParams(filterConfig, "hostnameVerifierConfig", null);
      this.logger.trace("Using hostnameVerifierConfig parameter: {}", config);
      if (className != null) {
         if (config != null) {
            return ((HostnameVerifier) ReflectUtils.newInstance(className, new Object[] { config }));
         }
         return ((HostnameVerifier) ReflectUtils.newInstance(className, new Object[0]));
      }

      return null;
   }

   protected void onSuccessfulValidation(HttpServletRequest request, HttpServletResponse response,
         Assertion assertion) {
   }

   protected void onFailedValidation(HttpServletRequest request, HttpServletResponse response) {
   }

   public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
         throws IOException, ServletException {
      if (!(preFilter(servletRequest, servletResponse, filterChain))) {
         return;
      }

      HttpServletRequest request = (HttpServletRequest) servletRequest;
      HttpServletResponse response = (HttpServletResponse) servletResponse;
      String ticket = retrieveTicketFromRequest(request);

      if (CommonUtils.isNotBlank(ticket)) {
         this.logger.debug("Attempting to validate ticket: {}", ticket);
         try {
            Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));

            this.logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());

            request.setAttribute("_const_cas_assertion_", assertion);

            if (this.useSession) {
               request.getSession().setAttribute("_const_cas_assertion_", assertion);
            }

            createToken(ticket, assertion, response);
            saveAssertion(ticket, assertion);
            onSuccessfulValidation(request, response, assertion);

            if (this.redirectAfterValidation) {
               this.logger.debug("Redirecting after successful ticket validation.");
               response.sendRedirect(constructServiceUrl(request, response));
               return;
            }
         } catch (TicketValidationException e) {
            this.logger.debug(e.getMessage(), e);

            onFailedValidation(request, response);

            if (this.exceptionOnValidationFailure) {
               throw new ServletException(e);
            }

            response.sendError(403, e.getMessage());

            return;
         }
      }

      filterChain.doFilter(request, response);
   }

   public final void setTicketValidator(TicketValidator ticketValidator) {
      this.ticketValidator = ticketValidator;
   }

   public final void setRedirectAfterValidation(boolean redirectAfterValidation) {
      this.redirectAfterValidation = redirectAfterValidation;
   }

   public final void setExceptionOnValidationFailure(boolean exceptionOnValidationFailure) {
      this.exceptionOnValidationFailure = exceptionOnValidationFailure;
   }

   public final void setUseSession(boolean useSession) {
      this.useSession = useSession;
   }

   private void createToken(String ticket, Assertion assertion, HttpServletResponse response) {
      AttributePrincipal principal = assertion.getPrincipal();
      String userID = "";
      if (principal != null) {
         Map params = principal.getAttributes();
         Object userId = params.get("userId");
         if (userId != null) {
            userID = userId.toString();
         }
      }

      if (StringUtils.isBlank(userID))
         return;
      String saveUserid = ticket + "__" + userID;
      TenantUser existUser = RedisUtils.getUserCache("user.info.login.tenant:" + saveUserid);
      HashMap cookiesMap;
      Iterator iterator;
      if (existUser == null) {
         TenantUser user = new TenantUser();
         user.setUserId(saveUserid);
         long ts = System.currentTimeMillis();
         user.setLoginTs(ts);
         String cookieValue = "";
         try {
            cookieValue = TokenGenerator.genToken(saveUserid, ts, RedisUtils.findSeed());
         } catch (EncryptException e) {
            this.logger.error("Fail to generate cookie!", e);
         }

         HashMap cookiesMap = new HashMap();
         cookiesMap.put("tenant_username", saveUserid);
         cookiesMap.put("tenant_token", cookieValue);

         for (Iterator iterator = cookiesMap.keySet().iterator(); iterator.hasNext();) {
            String key = (String) iterator.next();
            Cookie cookie = new Cookie(key, URLEncoder.encode((String) cookiesMap.get(key)));
            cookie.setPath("/");

            cookie.setMaxAge(-1);
            cookie.setHttpOnly(true);
            response.addCookie(cookie);
         }
         try {
            RedisUtils.cacheUser(saveUserid, user);
         } catch (Exception e) {
            this.logger.error("登陆信息写入到redis缓存中失败", e);
         }
      } else {
         long ts = existUser.getLoginTs();
         String cookieValue = "";
         try {
            cookieValue = TokenGenerator.genToken(saveUserid, ts, RedisUtils.findSeed());
         } catch (EncryptException e) {
            this.logger.error("Fail to generate cookie!", e);
         }

         cookiesMap = new HashMap();
         cookiesMap.put("tenant_username", saveUserid);
         cookiesMap.put("tenant_token", cookieValue);

         for (iterator = cookiesMap.keySet().iterator(); iterator.hasNext();) {
            String key = (String) iterator.next();
            Cookie cookie = new Cookie(key, URLEncoder.encode((String) cookiesMap.get(key)));
            cookie.setPath("/");

            cookie.setMaxAge(-1);
            cookie.setHttpOnly(true);
            response.addCookie(cookie);
         }
      }
   }

   private void saveAssertion(String ticket, Assertion assertion) {
      if (assertion == null)
         return;
      AttributePrincipal principal = assertion.getPrincipal();
      String userID = "";
      if (principal != null) {
         Map params = principal.getAttributes();
         Object userId = params.get("userId");
         if (userId != null) {
            userID = userId.toString();
         }
      }
      if ((userID != null) && (!(userID.equalsIgnoreCase("")))) {
         String saveUserid = ticket + "__" + userID;
         RedisUtils.putSessionCacheAttribute("tenant_assertion", saveUserid, assertion);
      }
   }

   private String getCasUrl(FilterConfig filterConfig) {
      String casUrl = PropertyUtil.getPropertyByKey("cas.url");
      if (StringUtils.isBlank(casUrl)) {
         casUrl = getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null);
      }
      return casUrl;
   }
}

  这样,servername 和 casServerUrlPrefix 获取的逻辑可以自定义了,例如可以放到 .properties 中。

  3.通过casServerUrlPrefix 和 Cas10TicketValidator 或(Cas20ProxyTicketValidator)的 getUrlSuffix方法(返回值分别是“validate”和“serviceValidate”)可以确定出 具体的校验ticket的接口。通过查看 cas server的web.xml,可以在servlet-mapping中看到getUrlSuffix返回值对应的servlet是org.springframework.web.servlet.DispatcherServlet,参数contextConfigLocation 对应的值是’/WEB-INF/cas-servlet.xml, /WEB-INF/cas-servlet-*.xml’,里面定义了springmvc初始的handlerMapping和handlerAdapter,如下。

  <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

  <bean
      id="handlerMappingC"
      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
      p:alwaysUseFullPath="true">
    <property name="mappings">
      <util:properties>
        <prop key="/serviceValidate">serviceValidateController</prop>
        <prop key="/proxyValidate">proxyValidateController</prop>

        <prop key="/p3/serviceValidate">v3ServiceValidateController</prop>
        <prop key="/p3/proxyValidate">v3ProxyValidateController</prop>
        <prop key="/validate">legacyValidateController</prop>
        <prop key="/proxy">proxyController</prop>
        <prop key="/authorizationFailure.html">passThroughController</prop>
      </util:properties>
    </property>
  </bean>

  找到 ‘/serviceValidate’ 对应的bean -> serviceValidateController

   <bean id="abstractValidateController" class="org.jasig.cas.web.ServiceValidateController" abstract="true"
        p:centralAuthenticationService-ref="centralAuthenticationService"
        p:proxyHandler-ref="proxy20Handler"
        p:argumentExtractor-ref="casArgumentExtractor"
        p:servicesManager-ref="servicesManager" />

  <bean id="serviceValidateController" parent="abstractValidateController"
        p:validationSpecificationClass="org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification"/>

  于是可以确认 最终调用 org.jasig.cas.web.ServiceValidateController 进行ticket验证。到底是调用哪个方法呢? 这里先看一下 springmvc中 DispatchServlet的doDispatch方法,如下。

  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  
        HttpServletRequest processedRequest = request;  
        HandlerExecutionChain mappedHandler = null;  
        boolean multipartRequestParsed = false;  
  
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);  
  
        try {  
            ModelAndView mv = null;  
            Exception dispatchException = null;  
  
            try {  
                processedRequest = checkMultipart(request);  
                multipartRequestParsed = (processedRequest != request);  
                      //这个是重点,第一步由HandlerMapping找到对应的handler  
                // Determine handler for the current request.  
                mappedHandler = getHandler(processedRequest);  
                if (mappedHandler == null || mappedHandler.getHandler() == null) {  
                    noHandlerFound(processedRequest, response);  
                    return;  
                }  
  
                // Determine handler adapter for the current request.  
                       //这是第二步,找到合适的HandlerAdapter,然后由它来调度执行handler的方法  
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
  
                // Process last-modified header, if supported by the handler.  
                String method = request.getMethod();  
                boolean isGet = "GET".equals(method);  
                if (isGet || "HEAD".equals(method)) {  
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());  
                    if (logger.isDebugEnabled()) {  
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);  
                    }  
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {  
                        return;  
                    }  
                }  
  
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {  
                    return;  
                }  
  
                try {  
                    // Actually invoke the handler.  
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
                }  
                finally {  
                    if (asyncManager.isConcurrentHandlingStarted()) {  
                        return;  
                    }  
                }  
  
                applyDefaultViewName(request, mv);  
                mappedHandler.applyPostHandle(processedRequest, response, mv);  
            }  
            catch (Exception ex) {  
                dispatchException = ex;  
            }  
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);  
        }  
        catch (Exception ex) {  
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);  
        }  
        catch (Error err) {  
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);  
        }  
        finally {  
            if (asyncManager.isConcurrentHandlingStarted()) {  
                // Instead of postHandle and afterCompletion  
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);  
                return;  
            }  
            // Clean up any resources used by a multipart request.  
            if (multipartRequestParsed) {  
                cleanupMultipart(processedRequest);  
            }  
        }  
    }  

  接下来看一下  org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter 的handle方法,如下。

package org.springframework.web.servlet.mvc;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;

public class SimpleControllerHandlerAdapter implements HandlerAdapter {
    public boolean supports(Object handler) {
        return handler instanceof Controller;
    }

    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return ((Controller) handler).handleRequest(request, response);
    }

    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }
}

  可见,handlerAdapter的handle方法调用了某个具体handler的handleRequest方法,查看如上handler配置中 org.jasig.cas.web.ServiceValidateController 的handleRequest方法,如下。

package org.springframework.web.servlet.mvc;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.WebUtils;

public abstract class AbstractController extends WebContentGenerator implements Controller {
    private boolean synchronizeOnSession;

    public AbstractController() {
        this.synchronizeOnSession = false;
    }

    public final void setSynchronizeOnSession(boolean synchronizeOnSession) {
        this.synchronizeOnSession = synchronizeOnSession;
    }

    public final boolean isSynchronizeOnSession() {
        return this.synchronizeOnSession;
    }

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        checkAndPrepare(request, response, this instanceof LastModified);

        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    return handleRequestInternal(request, response);
                }
            }
        }

        return handleRequestInternal(request, response);
    }

    protected abstract ModelAndView handleRequestInternal(HttpServletRequest paramHttpServletRequest,
            HttpServletResponse paramHttpServletResponse) throws Exception;
}

  父类handleRequest方法调用了handleRequestInternal方法,子类ServiceValidateController 自身重写了 handleRequestInternal方法。

/*
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jasig.cas.web;

import org.jasig.cas.CasProtocolConstants;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.AuthenticationException;
import org.jasig.cas.authentication.Credential;
import org.jasig.cas.authentication.HttpBasedServiceCredential;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.authentication.principal.WebApplicationService;
import org.jasig.cas.services.RegisteredService;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.services.UnauthorizedProxyingException;
import org.jasig.cas.services.UnauthorizedServiceException;
import org.jasig.cas.ticket.TicketException;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.ticket.TicketValidationException;
import org.jasig.cas.ticket.proxy.ProxyHandler;
import org.jasig.cas.validation.Assertion;
import org.jasig.cas.validation.Cas20ProtocolValidationSpecification;
import org.jasig.cas.validation.ValidationSpecification;
import org.jasig.cas.web.support.ArgumentExtractor;
import org.jasig.cas.web.view.CasViewConstants;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.net.URL;
import java.util.Collections;
import java.util.Map;

/**
 * Process the /validate , /serviceValidate , and /proxyValidate URL requests.
 * <p>
 * Obtain the Service Ticket and Service information and present them to the CAS
 * validation services. Receive back an Assertion containing the user Principal
 * and (possibly) a chain of Proxy Principals. Store the Assertion in the Model
 * and chain to a View to generate the appropriate response (CAS 1, CAS 2 XML,
 * SAML, ...).
 *
 * @author Scott Battaglia
 * @author Misagh Moayyed
 * @since 3.0.0
 */
public class ServiceValidateController extends DelegateController {
    /** View if Service Ticket Validation Fails. */
    public static final String DEFAULT_SERVICE_FAILURE_VIEW_NAME = "cas2ServiceFailureView";

    /** View if Service Ticket Validation Succeeds. */
    public static final String DEFAULT_SERVICE_SUCCESS_VIEW_NAME = "cas2ServiceSuccessView";

    /** Implementation of Service Manager. */
    @NotNull
    private ServicesManager servicesManager;
    
    /** The CORE which we will delegate all requests to. */
    @NotNull
    private CentralAuthenticationService centralAuthenticationService;

    /** The validation protocol we want to use. */
    @NotNull
    private Class<?> validationSpecificationClass = Cas20ProtocolValidationSpecification.class;

    /** The proxy handler we want to use with the controller. */
    @NotNull
    private ProxyHandler proxyHandler;

    /** The view to redirect to on a successful validation. */
    @NotNull
    private String successView = DEFAULT_SERVICE_SUCCESS_VIEW_NAME;

    /** The view to redirect to on a validation failure. */
    @NotNull
    private String failureView = DEFAULT_SERVICE_FAILURE_VIEW_NAME;

    /** Extracts parameters from Request object. */
    @NotNull
    private ArgumentExtractor argumentExtractor;

    /**
     * Overrideable method to determine which credentials to use to grant a
     * proxy granting ticket. Default is to use the pgtUrl.
     *
     * @param service the webapp service requesting proxy
     * @param request the HttpServletRequest object.
     * @return the credentials or null if there was an error or no credentials
     * provided.
     */
    protected Credential getServiceCredentialsFromRequest(final WebApplicationService service, final HttpServletRequest request) {
        final String pgtUrl = request.getParameter(CasProtocolConstants.PARAMETER_PROXY_CALLBACK_URL);
        if (StringUtils.hasText(pgtUrl)) {
            try {
                final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
                verifyRegisteredServiceProperties(registeredService, service);
                return new HttpBasedServiceCredential(new URL(pgtUrl), registeredService);
            } catch (final Exception e) {
                logger.error("Error constructing pgtUrl", e);
            }
        }

        return null;
    }

    /**
     * Inits the binder with the required fields. <code>renew</code> is required.
     *
     * @param request the request
     * @param binder the binder
     */
    protected void initBinder(final HttpServletRequest request, final ServletRequestDataBinder binder) {
        binder.setRequiredFields("renew");
    }

    @Override
    protected final ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response)
            throws Exception {
        final WebApplicationService service = this.argumentExtractor.extractService(request);
        final String serviceTicketId = service != null ? service.getArtifactId() : null;

        if (service == null || serviceTicketId == null) {
            logger.debug("Could not identify service and/or service ticket for service: [{}]", service);
            return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_REQUEST,
                    CasProtocolConstants.ERROR_CODE_INVALID_REQUEST, null);
        }

        try {
            final Credential serviceCredential = getServiceCredentialsFromRequest(service, request);
            TicketGrantingTicket proxyGrantingTicketId = null;
            
            if (serviceCredential != null) {
                try {
                    proxyGrantingTicketId = this.centralAuthenticationService.delegateTicketGrantingTicket(serviceTicketId,
                                serviceCredential);
                    logger.debug("Generated PGT [{}] off of service ticket [{}] and credential [{}]",
                            proxyGrantingTicketId.getId(), serviceTicketId, serviceCredential);
                } catch (final AuthenticationException e) {
                    logger.info("Failed to authenticate service credential {}", serviceCredential);
                } catch (final TicketException e) {
                    logger.error("Failed to create proxy granting ticket for {}", serviceCredential, e);
                }
                
                if (proxyGrantingTicketId == null) {
                    return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
                            CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
                            new Object[] {serviceCredential.getId()});
                }
            }

            final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service);

            final ValidationSpecification validationSpecification = this.getCommandClass();
            final ServletRequestDataBinder binder = new ServletRequestDataBinder(validationSpecification, "validationSpecification");
            initBinder(request, binder);
            binder.bind(request);

            if (!validationSpecification.isSatisfiedBy(assertion)) {
                logger.debug("Service ticket [{}] does not satisfy validation specification.", serviceTicketId);
                return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_TICKET,
                        CasProtocolConstants.ERROR_CODE_INVALID_TICKET, null);
            }

            String proxyIou = null;
            if (serviceCredential != null && this.proxyHandler.canHandle(serviceCredential)) {
                proxyIou = this.proxyHandler.handle(serviceCredential, proxyGrantingTicketId);
                if (StringUtils.isEmpty(proxyIou)) {
                    return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
                            CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
                            new Object[] {serviceCredential.getId()});
                }
            }

            onSuccessfulValidation(serviceTicketId, assertion);
            logger.debug("Successfully validated service ticket {} for service [{}]", serviceTicketId, service.getId());
            return generateSuccessView(assertion, proxyIou, service, proxyGrantingTicketId);
        } catch (final TicketValidationException e) {
            final String code = e.getCode();
            return generateErrorView(code, code,
                    new Object[] {serviceTicketId, e.getOriginalService().getId(), service.getId()});
        } catch (final TicketException te) {
            return generateErrorView(te.getCode(), te.getCode(),
                new Object[] {serviceTicketId});
        } catch (final UnauthorizedProxyingException e) {
            return generateErrorView(e.getMessage(), e.getMessage(), new Object[] {service.getId()});
        } catch (final UnauthorizedServiceException e) {
            return generateErrorView(e.getMessage(), e.getMessage(), null);
        }
    }

    /**
     * Triggered on successful validation events. Extensions are to
     * use this as hook to plug in behvior.
     *
     * @param serviceTicketId the service ticket id
     * @param assertion the assertion
     */
    protected void onSuccessfulValidation(final String serviceTicketId, final Assertion assertion) {
        // template method with nothing to do.
    }

    /**
     * Generate error view, set to {@link #setFailureView(String)}.
     *
     * @param code the code
     * @param description the description
     * @param args the args
     * @return the model and view
     */
    private ModelAndView generateErrorView(final String code, final String description, final Object[] args) {
        final ModelAndView modelAndView = new ModelAndView(this.failureView);
        final String convertedDescription = getMessageSourceAccessor().getMessage(description, args, description);
        modelAndView.addObject("code", code);
        modelAndView.addObject("description", convertedDescription);

        return modelAndView;
    }

    /**
     * Generate the success view. The result will contain the assertion and the proxy iou.
     *
     * @param assertion the assertion
     * @param proxyIou the proxy iou
     * @param service the validated service
     * @param proxyGrantingTicket the proxy granting ticket
     * @return the model and view, pointed to the view name set by
     */
    private ModelAndView generateSuccessView(final Assertion assertion, final String proxyIou,
                                             final WebApplicationService service,
                                             final TicketGrantingTicket proxyGrantingTicket) {

        final ModelAndView success = new ModelAndView(this.successView);
        success.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_ASSERTION, assertion);
        success.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_SERVICE, service);
        success.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET_IOU, proxyIou);
        if (proxyGrantingTicket != null) {
            success.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, proxyGrantingTicket.getId());
        }
        final Map<String, ?> augmentedModelObjects = augmentSuccessViewModelObjects(assertion);
        if (augmentedModelObjects != null) {
            success.addAllObjects(augmentedModelObjects);
        }
        return success;
    }

    /**
     * Augment success view model objects. Provides
     * a way for extension of this controller to dynamically
     * populate the model object with attributes
     * that describe a custom nature of the validation protocol.
     *
     * @param assertion the assertion
     * @return map of objects each keyed to a name
     */
    protected Map<String, ?> augmentSuccessViewModelObjects(final Assertion assertion) {
        return Collections.emptyMap();  
    }
    
    /**
     * Gets the command class based on {@link #setValidationSpecificationClass(Class)}.
     *
     * @return the command class
     */
    private ValidationSpecification getCommandClass() {
        try {
            return (ValidationSpecification) this.validationSpecificationClass.newInstance();
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canHandle(final HttpServletRequest request, final HttpServletResponse response) {
        return true;
    }

    /**
     * @param centralAuthenticationService The centralAuthenticationService to
     * set.
     */
    public final void setCentralAuthenticationService(final CentralAuthenticationService centralAuthenticationService) {
        this.centralAuthenticationService = centralAuthenticationService;
    }

    
    public final void setArgumentExtractor(final ArgumentExtractor argumentExtractor) {
        this.argumentExtractor = argumentExtractor;
    }

    /**
     * @param validationSpecificationClass The authenticationSpecificationClass
     * to set.
     */
    public final void setValidationSpecificationClass(final Class<?> validationSpecificationClass) {
        this.validationSpecificationClass = validationSpecificationClass;
    }

    /**
     * @param failureView The failureView to set.
     */
    public final void setFailureView(final String failureView) {
        this.failureView = failureView;
    }

    /**
     * @param successView The successView to set.
     */
    public final void setSuccessView(final String successView) {
        this.successView = successView;
    }

    /**
     * @param proxyHandler The proxyHandler to set.
     */
    public final void setProxyHandler(final ProxyHandler proxyHandler) {
        this.proxyHandler = proxyHandler;
    }

    /**
     * Sets the services manager.
     *
     * @param servicesManager the new services manager
     */
    public final void setServicesManager(final ServicesManager servicesManager) {
        this.servicesManager = servicesManager;
    }

    /**
     * Ensure that the service is found and enabled in the service registry.
     * @param registeredService the located entry in the registry
     * @param service authenticating service
     * @throws UnauthorizedServiceException
     */
    private void verifyRegisteredServiceProperties(final RegisteredService registeredService, final Service service) {
        if (registeredService == null) {
            final String msg = String.format("ServiceManagement: Unauthorized Service Access. "
                    + "Service [%s] is not found in service registry.", service.getId());
            logger.warn(msg);
            throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);
        }
        if (!registeredService.getAccessStrategy().isServiceAccessAllowed()) {
            final String msg = String.format("ServiceManagement: Unauthorized Service Access. "
                    + "Service [%s] is not enabled in service registry.", service.getId());
            
            logger.warn(msg);
            throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);
        }
    }
}

  handleRequestInternal方法中调用了一个argumentExtractor,在上面的配置中可以看到,可确定这是个 spring bean。这个bean在 WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml中,内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<!--

    Licensed to Apereo under one or more contributor license
    agreements. See the NOTICE file distributed with this work
    for additional information regarding copyright ownership.
    Apereo licenses this file to you under the Apache License,
    Version 2.0 (the "License"); you may not use this file
    except in compliance with the License.  You may obtain a
    copy of the License at the following location:

      http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.

-->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    <description>
        Argument Extractors are what are used to translate HTTP requests into requests of the appropriate protocol (i.e.
        CAS, SAML, SAML2,
        OpenId, etc.). By default, only CAS is enabled.
    </description>

    <!--
    <bean id="samlArgumentExtractor" class="org.jasig.cas.support.saml.web.support.SamlArgumentExtractor" />
    -->

    <bean
            id="casArgumentExtractor"
            class="org.jasig.cas.web.support.CasArgumentExtractor"/>

    <util:list id="argumentExtractors">
        <!-- <ref bean="samlArgumentExtractor" /> -->
        <ref bean="casArgumentExtractor"/>
    </util:list>
</beans>

  查看org.jasig.cas.web.support.CasArgumentExtractor内容如下。

/*** Eclipse Class Decompiler plugin, copyright (c) 2016 Chen Chao (cnfree2000@hotmail.com) ***/
package org.jasig.cas.web.support;

import javax.servlet.http.HttpServletRequest;
import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl;
import org.jasig.cas.authentication.principal.WebApplicationService;

public final class CasArgumentExtractor extends AbstractArgumentExtractor {
    public WebApplicationService extractServiceInternal(HttpServletRequest request) {
        return SimpleWebApplicationServiceImpl.createServiceFrom(request);
    }
}

  最后查看org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl内容如下。

/*** Eclipse Class Decompiler plugin, copyright (c) 2016 Chen Chao (cnfree2000@hotmail.com) ***/
package org.jasig.cas.authentication.principal;

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;

public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicationService {
    private static final long serialVersionUID = 8334068957483758042L;
    private static final String CONST_PARAM_SERVICE = "service";
    private static final String CONST_PARAM_TARGET_SERVICE = "targetService";
    private static final String CONST_PARAM_TICKET = "ticket";
    private static final String CONST_PARAM_METHOD = "method";
    private final Response.ResponseType responseType;

    public SimpleWebApplicationServiceImpl(String id) {
        this(id, id, null, null);
    }

    private SimpleWebApplicationServiceImpl(String id, String originalUrl, String artifactId,
            Response.ResponseType responseType) {
        super(id, originalUrl, artifactId);
        this.responseType = responseType;
    }

    public static SimpleWebApplicationServiceImpl createServiceFrom(HttpServletRequest request) {
        String targetService = request.getParameter("targetService");
        String service = request.getParameter("service");
        String serviceAttribute = (String) request.getAttribute("service");
        String method = request.getParameter("method");
        String serviceToUse;
        String serviceToUse;
        if (StringUtils.hasText(targetService)) {
            serviceToUse = targetService;
        } else {
            String serviceToUse;
            if (StringUtils.hasText(service))
                serviceToUse = service;
            else {
                serviceToUse = serviceAttribute;
            }
        }
        if (!(StringUtils.hasText(serviceToUse))) {
            return null;
        }

        String id = cleanupUrl(serviceToUse);
        String artifactId = request.getParameter("ticket");

        return new SimpleWebApplicationServiceImpl(id, serviceToUse, artifactId,
                ("POST".equals(method)) ? Response.ResponseType.POST : Response.ResponseType.REDIRECT);
    }

    public Response getResponse(String ticketId) {
        Map parameters = new HashMap();

        if (StringUtils.hasText(ticketId)) {
            parameters.put("ticket", ticketId);
        }

        if (Response.ResponseType.POST == this.responseType) {
            return DefaultResponse.getPostResponse(getOriginalUrl(), parameters);
        }
        return DefaultResponse.getRedirectResponse(getOriginalUrl(), parameters);
    }
}

  在SimpleWebApplicationServiceImpl createServiceFrom方法中可以看到,ticket从request中取到。也就是对应 org.jasig.cas.web.ServiceValidateController 的handleRequestInternal 如下获取方式。

final WebApplicationService service = this.argumentExtractor.extractService(request);
final String serviceTicketId = service != null ? service.getArtifactId() : null;

通过配置deployerConfigContext.xml实现自定义认证(本例中提供了四中方式)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:sec="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- 数据源配置, 使用druid连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${druid.initialSize}"/>
        <property name="minIdle" value="${druid.minIdle}"/>
        <property name="maxActive" value="${druid.maxActive}"/>

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${druid.maxWait}"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />

        <property name="validationQuery" value="${druid.validationQuery}" />
        <property name="testWhileIdle" value="${druid.testWhileIdle}" />
        <property name="testOnBorrow" value="${druid.testOnBorrow}" />
        <property name="testOnReturn" value="${druid.testOnReturn}" />

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小  如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。-->
        <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
    </bean>

    <bean id="usernamePasswordAuthenticationHandler" class="com.hjzgg.auth.UsernamePasswordAuthenticationHandler"/>

    <util:map id="authenticationHandlersResolvers">
        <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
        <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />
    </util:map>

    <util:list id="authenticationMetadataPopulators">
        <ref bean="successfulHandlerMetaDataPopulator" />
        <ref bean="rememberMeAuthenticationMetaDataPopulator" />
    </util:list>

    <bean id="attributeRepository" class="org.jasig.services.persondir.support.NamedStubPersonAttributeDao"
          p:backingMap-ref="attrRepoBackingMap" />

    <!--first 默认方式-->
    <!--<alias name="acceptUsersAuthenticationHandler" alias="primaryAuthenticationHandler" />-->

    <!--second 使用 MD5对密码进行加密-->
    <!--<bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" autowire="byName">
        <constructor-arg  value="MD5"/>
    </bean >
    <bean id="queryDatabaseAuthenticationHandler" name="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
        <property name="passwordEncoder" ref="MD5PasswordEncoder"/>
    </bean>-->

    <!--third 不使用加密算法-->
    <!--<alias name="queryDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />-->

    <!--forth 通过继承AbstractUsernamePasswordAuthenticationHandler,并实现authenticateUsernamePasswordInternal-->
    <alias name="usernamePasswordAuthenticationHandler" alias="primaryAuthenticationHandler" />
    <alias name="personDirectoryPrincipalResolver" alias="primaryPrincipalResolver" />

    <alias name="dataSource" alias="queryDatabaseDataSource" />

    <util:map id="attrRepoBackingMap">
        <entry key="uid" value="uid" />
        <entry key="eduPersonAffiliation" value="eduPersonAffiliation" />
        <entry key="groupMembership" value="groupMembership" />
        <entry>
            <key><value>memberOf</value></key>
            <list>
                <value>faculty</value>
                <value>staff</value>
                <value>org</value>
            </list>
        </entry>
    </util:map>

    <alias name="serviceThemeResolver" alias="themeResolver" />

    <alias name="jsonServiceRegistryDao" alias="serviceRegistryDao" />

    <alias name="defaultTicketRegistry" alias="ticketRegistry" />
    
    <alias name="ticketGrantingTicketExpirationPolicy" alias="grantingTicketExpirationPolicy" />
    <alias name="multiTimeUseOrTimeoutExpirationPolicy" alias="serviceTicketExpirationPolicy" />

    <alias name="anyAuthenticationPolicy" alias="authenticationPolicy" />
    <alias name="acceptAnyAuthenticationPolicyFactory" alias="authenticationPolicyFactory" />

    <bean id="auditTrailManager"
          class="org.jasig.inspektr.audit.support.Slf4jLoggingAuditTrailManager"
          p:entrySeparator="${cas.audit.singleline.separator:|}"
          p:useSingleLine="${cas.audit.singleline:false}"/>

    <alias name="neverThrottle" alias="authenticationThrottle" />

    <util:list id="monitorsList">
        <ref bean="memoryMonitor" />
        <ref bean="sessionMonitor" />
    </util:list>

    <alias name="defaultPrincipalFactory" alias="principalFactory" />
    <alias name="defaultAuthenticationTransactionManager" alias="authenticationTransactionManager" />
    <alias name="defaultPrincipalElectionStrategy" alias="principalElectionStrategy" />
    <alias name="tgcCipherExecutor" alias="defaultCookieCipherExecutor" />
</beans>

相关参考

CAS Properties

      
https://apereo.github.io/cas/5.0.x/installation/Configuration-Properties.html

Database Authentication

      
https://apereo.github.io/cas/4.2.x/installation/Database-Authentication.html#database-components

CAS4.2.x

      
https://apereo.github.io/cas/4.2.x/ 

报异常:Application Not Authorized to Use CAS

       cas4.2.7 取消https  

Cas https 证书生成

keytool -genkey -keyalg RSA -alias cas.server.com -dname "cn=cas.server.com" -keystore /usr/data/tomcat.keystore
 
sudo keytool -export -alias cas.server.com -keystore /usr/data/tomcat.keystore  -file /usr/data/tomcat.cer
 
cd  /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/security
 
sudo keytool -import -keystore cacerts -file /usr/data/tomcat.cer -alias cas.server.com -storepass changeit
 
 
sudo keytool -delete -trustcacerts -alias cas.server.com -keystore "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/security/cacerts" -storepass changeit
 
keytool -list -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/security/cacerts -storepass changeit 

完整项目地址:https://github.com/hjzgg/cas4.2.7-authentication

cas4.2.7 template项目地址:https://github.com/hjzgg/cas-overlay-template-4.2.7

作者:胡峻峥