cas4.2.7实现单点登录

准备前参考: 

 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 ([email protected]) ***/
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 ([email protected]) ***/
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

时间: 2024-08-10 19:09:35

cas4.2.7实现单点登录的相关文章

2、cas4.0 单点登录 之 cas-client

cas4.0 单点登录 之 cas-client cas4.0 单点登录 之 https证书已经做好了证书的准备工作,现在结合cas-server来配置单点登录: 一.安装cas服务端(cas-server) cas服务端是一个war包,这里只做体验单点登录,cas-server下载点这里cas-server-webapp-4.0.0.war,将war包放tomcat下运行即可,运行cas-server的tomcat的要开启SSL支持,上面文章也有说明,server.xml需要如下配置: <Co

【试水CAS-4.0.3】第07节_CAS客户端配置单点登录

本文源码下载:http://download.csdn.net/detail/jadyer/8934207 /** * @see CAS客户端配置 * @see ------------------------------------------------------------------------------------------------------------------------ * @see 这里用的是cas-client-core-3.4.0.jar(这是2015-07-

CAS实现SSO单点登录

环境cas-server-4.1.8,cas-client-3.4.0,Java-8,Maven-3,Tomcat-7.0.72 CAS Server 安装点此进入 CAS 下载列表,选择下载 cas-4.1.8.zip. 解压缩 cas-4.1.8.zip 并进入 cas-server-webapp 目录,在当前目录打开 cmd 并执行安装命令. mvn -e -ff clean install -Dmaven.test.skip=true经亲身测试(自己拉的电信12M网络),该安装过程非常漫

Springboot集成Shiro和Cas实现单点登录(服务端篇CAS5)

什么是单点登录? 先说一个需求场景,比如:一个企业的内部有N多个子系统,每个子系统都有一套自己的用户名和密码,那么企业的员工要登录N个子系统,这样一个员工 就要记住N个用户名和密码,就算各个子系统的用户名和密码都是统一的,登录每个子系统都要输入用户名和密码进行登录也是一个繁琐的操作过程,那么单点登录功能由此便应运而生了.单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应

CAS单点登录系列之极速入门于实战教程(4.2.7)

@ 目录 一. SSO简介 1.1 单点登录定义 1.2 单点登录角色 1.3 单点登录分类 二. CAS简介 2.1 CAS简单定义 2.2 CAS体系结构 2.3 CAS原理 三.CAS服务端搭建 3.1 CAS支持Http登录配置 3.2 CAS服务端部署运行 四.CAS客户端接入 五.客户端极速接入 一. SSO简介 1.1 单点登录定义 单点登录(Single sign on),英文名称缩写SSO,SSO的意思就是在多系统的环境中,登录单方系统,就可以在不用再次登录的情况下访问相关受信

八幅漫画理解使用 JSON Web Token 设计单点登录系统

原文出处: John Wu 上次在<JSON Web Token – 在Web应用间安全地传递信息>中我提到了JSON Web Token可以用来设计单点登录系统.我尝试用八幅漫画先让大家理解如何设计正常的用户认证系统,然后再延伸到单点登录系统. 如果还没有阅读<JSON Web Token – 在Web应用间安全地传递信息>,我强烈建议你花十分钟阅读它,理解JWT的生成过程和原理. 用户认证八步走 所谓用户认证(Authentication),就是让用户登录,并且在接下来的一段时

单点登录原理理解

最近接触了一点单点登录的知识,有一点理解,记录一下.有些问题并没有找到完美的解决方法,还需要找点已有框架来看看. 欢迎留言探讨. 1       概念 1.1     概念及理解 有一个网上广为流传的定义“SSO是一种统一认证和授权机制,指访问同一服务器不同应用中的受保护资源的同一用户,只需要登录一次,即通过一个应用中的安全验证后,再访问其他应用中的受保护资源时,不再需要重新登录验证”. 个人理解,其中的“同一服务器”的限制应当去掉,关键在于“一次登录”,另外,很少有人提及“退出”的处理逻辑,因

单点登录(一)使用Cookie+File实现单点登录

本文使用Cookies+Filter实现www.taobao.tgb.com 和 www.tianmao.tgb.com的单点登录. 源码分享:链接: http://pan.baidu.com/s/1eQheDpS 密码: gn9d 一 实现原理 使用用户名和密码登录taobao后,会将用户名存储在Session和Cookie中各一份.当用户登录tianmao时,可直接从Cookie中获取用户名和密码,不需要二次登陆. 二 知识点解析 1.本例使用tomcat做服务器,绑定1个域名,且此域名对应

构建和实现单点登录解决方案

将一个开放源码的基于 Java 的身份验证组件集成进 Web 门户中 在现有的应用程序中实现单点登录解决方案(single sign-on,SSO,即登录一次,就可以向所有网络资源验证用户的身份)是非常困难的,但是在构建复杂的门户时,每个开发人员都要面对这个问题.因为门户需要与后端资源集成,而每个后端资源都有自己的身份验证需求,所以门户常常必须向用户提供单点登录特性.在本文中,Chris Dunne 一步步地描述了他为一个 Web 门户构建单点登录解决方案的经历.他将讲解如何设置一个开放源码解决