准備前參考:
自定義登錄頁和登錄認證
准備工作
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,另一個是用來指定認證成功后需要跳轉地址的serverName或service。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
Database Authentication
CAS4.2.x
報異常:Application Not Authorized to Use CAS
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