从上图可以看到,CAS有很多Filter,主要包括认证Filter和验证Ticket的Filter,还有一些上图没有的,比如:
AssertionThreadLocalFilter
和 DelegatingFilter
,下面一一分析下.
-
AbstractConfigurationFilter:
这个类是很多关键Filter的父类,提供了基础的配置外置化功能.其实我们也可以在自己的项目里直接继承它.
- 有一个属性:ConfigurationStrategy configurationStrategy,
配置Filter时,提供一个配置属性的策略.方便外部控制Filter的属性值
主要代码:
public void init(FilterConfig filterConfig) throws ServletException {
final String configurationStrategyName = filterConfig.getServletContext().getInitParameter(CONFIGURATION_STRATEGY_KEY);
//ConfigurationStrategyName 是一个枚举类,里面设定了几个client实现了的onfigurationStrategy,默认是LegacyConfigurationStrategyImpl
//ConfigurationStrategyName.resolveToConfigurationStrategy 处理了枚举类定义的几个实现,
//外部 configurationStrategyName="PROPERTY_FILE"就可以了
//同时也支持类完整名称初始化
this.configurationStrategy = ReflectUtils.newInstance(ConfigurationStrategyName.resolveToConfigurationStrategy(configurationStrategyName));
this.configurationStrategy.init(filterConfig, getClass());
}
- 提供了一些方便获取配置的方法,如:getBoolean(final ConfigurationKey<Boolean> configurationKey)
-
ConfigurationStrategy
:这个接口定义了init方法和方便获取配置值的方法,有5个实现类
在BaseConfigurationStrategy
中处理了方便获取配置的方法.getBoolean,getLong,getInt,getClass
等
实现获取配置的方法用了我们常用的回调,java8可改成lambda形式,在此略过...
-
LegacyConfigurationStrategyImpl
在用默认值之前会先用WebXmlConfigurationStrategyImpl
再用JndiConfigurationStrategyImpl
方式获取,
如果没有才用 ConfigurationKey 的默认值 -
WebXmlConfigurationStrategyImpl
先调用filterConfig.getInitParameter
方法获取再调用filterConfig.getServletContext().getInitParameter
获取 -
JndiConfigurationStrategyImpl
从JNDI中获取配置,ENVIRONMENT_PREFIX = "java:comp/env/cas/";
-
PropertiesConfigurationStrategyImpl
先通过filterConfig.getInitParameter
获取configFileLocation
参数的文件位置,
没有再用filterConfig.getServletContext().getInitParameter
获取文件,
再没有就从默认的位置DEFAULT_CONFIGURATION_FILE_LOCATION = /etc/java-cas-client.properties
获取配置,还没有就报错啦... -
ConfigurationStrategy.pngSystemPropertiesConfigurationStrategyImpl
从系统环境参数中获取System.getProperty
这几个类的类图如下:
-
HttpServletRequestWrapperFilter
- 封装
HttpServletRequest
添加getUserPrincipal(),getRemoteUser(),isUserInRole(String)
方法 - 里面有个关键类
CasHttpServletRequestWrapper
继承自HttpServletRequestWrapper
并在里面添加了上面3个方法,就是这个包装下request,这样就多了几个方法,
getUserPrincipal()
从request或session中获取信息,CONST_CAS_ASSERTION = "_const_cas_assertion_"
isUserInRole(final String role)
判断是否为指定的role,关键代码this.principal.getAttributes().get(roleAttribute)
,从principal
获取属性
roleAttribute
这个值可以是在配置中指定getString(ConfigurationKeys.ROLE_ATTRIBUTE);
也可以配置是否忽略大小写getBoolean(ConfigurationKeys.IGNORE_CASE)
,这样比较role的时候就会忽略大小写
-
SingleSignOutFilter
管理登出
这里面的逻辑都委托给了类 SingleSignOutHandler
处理,包括 init和process
这个类比较有意思的是对init的处理,代码中的注释说,因为受spring security的影响,在调用doFilter方法的时候有可能init
方法还没调用,这时SingleSignOutHandler
还没初始化好,
所以在doFilter方法里面又初始化了一次.代码如下:
//定义了个线程安全的属性,后面用 getAndSet方法保证原子操作
private AtomicBoolean handlerInitialized = new AtomicBoolean(false);
...
//在doFilter方法调用
if (!this.handlerInitialized.getAndSet(true)) {
HANDLER.init();
}
接着我们分析下 SingleSignOutHandler
类
这个类是真正干活的,里面有很多属性,比较有意思的是: SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage()
和 LogoutStrategy logoutStrategy = isServlet30() ? new Servlet30LogoutStrategy() : new Servlet25LogoutStrategy()
初始化方法也是对这些变量赋值和校验的过程
真正的逻辑处理在process
,代码贴出来:
public boolean process(final HttpServletRequest request, final HttpServletResponse response) {
//artifactParameterName 默认等于 ticket ,logoutParameterName 默认等于 logoutRequest
//判断是否为带了ticket参数的请求
if (isTokenRequest(request)) {
logger.trace("Received a token request");
//调用 SessionMappingStorage 创建 session,默认是 HashMapBackedSessionMappingStorage
//这个存储方式就是简单的把session和ticket 存储起来
//ID_TO_SESSION_KEY_MAPPING 是以session id为key,ticket为值
//MANAGED_SESSIONS 是以 ticket为key ,HttpSession 为值
//当用户很多的时候就要嗝屁了
//里面用到了request.getSession(this.eagerlyCreateSessions),关于获取session的这个eagerlyCreateSessions参数,我们如果不要session时要将其设置为false,这个值默认为true
//request.getSession(true/false/null)的区别
//HttpServletRequest.getSession(ture)等同于 HttpServletRequest.getSession()
//HttpServletRequest.getSession(false)等同于 如果当前Session没有就为null;
//当向Session中存取登录信息时,一般建议:HttpSession session =request.getSession();
//当从Session中获取登录信息时,一般建议:HttpSession session =request.getSession(false);
recordSession(request);
return true;
}
//是否为登出请求,处理了GET和POST,附件的类型
if (isLogoutRequest(request)) {
logger.trace("Received a logout request");
//从请求中获取logoutRequest 参数信息,解压得到sessionId,然后调用 session.invalidate()
//同时也清除 sessionMappingStorage 中的信息
destroySession(request);
return false;
}
logger.trace("Ignoring URI for logout: {}", request.getRequestURI());
return true;
}
-
AbstractCasFilter
这个类抽象出了CAS很多filter都要用的方法,比如重要的方法如下:
protected final String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
return CommonUtils.constructServiceUrl(request, response, this.service, this.serverName,
this.protocol.getServiceParameterName(),
this.protocol.getArtifactParameterName(), this.encodeServiceUrl);
}
分析下 CommonUtils.constructServiceUrl
/**
* Constructs a service url from the HttpServletRequest or from the given
* serviceUrl. Prefers the serviceUrl provided if both a serviceUrl and a
* serviceName.
*
* @param request the HttpServletRequest
* @param response the HttpServletResponse
* @param service the configured service url (this will be used if not null)
* @param serverNames the server name to use to construct the service url if the service param is empty. Note, prior to CAS Client 3.3, this was a single value.
* As of 3.3, it can be a space-separated value. We keep it as a single value, but will convert it to an array internally to get the matching value. This keeps backward compatability with anything using this public
* method.
* @param serviceParameterName the service parameter name to remove (i.e. service)
* @param artifactParameterName the artifact parameter name to remove (i.e. ticket)
* @param encode whether to encode the url or not (i.e. Jsession).
* @return the service url to use.
*/
public static String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response,
final String service, final String serverNames, final String serviceParameterName,
final String artifactParameterName, final boolean encode) {
if (CommonUtils.isNotBlank(service)) {
return encode ? response.encodeURL(service) : service;
}
//会从 Host 和 X-Forwarded-Host 请求中获取server name
final String serverName = findMatchingServerName(request, serverNames);
final URIBuilder originalRequestUrl = new URIBuilder(request.getRequestURL().toString(), encode);
originalRequestUrl.setParameters(request.getQueryString());
//CAS 工具箱中的 builder
final URIBuilder builder;
boolean containsScheme = true;
if (!serverName.startsWith("https://") && !serverName.startsWith("http://")) {
builder = new URIBuilder(encode);
builder.setScheme(request.isSecure() ? "https" : "http");
builder.setHost(serverName);
containsScheme = false;
} else {
builder = new URIBuilder(serverName, encode);
}
//判断是否包含端口或者是标准端口,此处被坑过
if (!serverNameContainsPort(containsScheme, serverName) && !requestIsOnStandardPort(request)) {
builder.setPort(request.getServerPort());
}
builder.setEncodedPath(request.getRequestURI());
final List<String> serviceParameterNames = Arrays.asList(serviceParameterName.split(","));
if (!serviceParameterNames.isEmpty() && !originalRequestUrl.getQueryParams().isEmpty()) {
for (final URIBuilder.BasicNameValuePair pair : originalRequestUrl.getQueryParams()) {
String name = pair.getName();
if (!name.equals(artifactParameterName) && !serviceParameterNames.contains(name)) {
if (name.contains("&") || name.contains("=") ){
URIBuilder encodedParamBuilder = new URIBuilder();
encodedParamBuilder.setParameters(name);
for (final URIBuilder.BasicNameValuePair pair2 :encodedParamBuilder.getQueryParams()){
String name2 = pair2.getName();
if (!name2.equals(artifactParameterName) && !serviceParameterNames.contains(name2)) {
builder.addParameter(name2, pair2.getValue());
}
}
} else {
builder.addParameter(name, pair.getValue());
}
}
}
}
final String result = builder.toString();
final String returnValue = encode ? response.encodeURL(result) : result;
LOGGER.debug("serviceUrl generated: {}", returnValue);
return returnValue;
}
-
AuthenticationFilter
认证过滤器
这个类主要有两个方法实现逻辑,initInternal
和 doFilter
先分析怎么初始化的
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
//同样的套路,处理初始化开关
if (!isIgnoreInitConfiguration()) {
//先调用父类的初始化
super.initInternal(filterConfig);
setCasServerLoginUrl(getString(ConfigurationKeys.CAS_SERVER_LOGIN_URL));
setRenew(getBoolean(ConfigurationKeys.RENEW));
setGateway(getBoolean(ConfigurationKeys.GATEWAY));
//下面这段逻辑都是处理忽略认证的逻辑的
//先从static里面初始化的几种系统预设 PATTERN_MATCHER_TYPES 里面取,值分别是:CONTAINS,REGEX(默认值),EXACT
//没有匹配到再从Filter配置里面获取,参数值为 ignoreUrlPatternType
//如果还没有,那就是为null了,后期在 isRequestUrlExcluded 方法里判断的时候直接返回false,也就是所有的都要校验
//isRequestUrlExcluded 方法校验的时候会加上 request.getQueryString() 参数值一起匹配
final String ignorePattern = getString(ConfigurationKeys.IGNORE_PATTERN);
final String ignoreUrlPatternType = getString(ConfigurationKeys.IGNORE_URL_PATTERN_TYPE);
if (ignorePattern != null) {
final Class<? extends UrlPatternMatcherStrategy> ignoreUrlMatcherClass = PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);
if (ignoreUrlMatcherClass != null) {
this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName());
} else {
try {
logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);
this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlPatternType);
} catch (final IllegalArgumentException e) {
logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, e);
}
}
if (this.ignoreUrlPatternMatcherStrategyClass != null) {
this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);
}
}
//GatewayResolver是什么用的?
//有两个方法,hasGatewayedAlready 从session中获取_const_cas_gateway_标志,判断是否已经存在; storeGatewayInformation是存入session信息
//默认值为:DefaultGatewayResolverImpl,
final Class<? extends GatewayResolver> gatewayStorageClass = getClass(ConfigurationKeys.GATEWAY_STORAGE_CLASS);
if (gatewayStorageClass != null) {
setGatewayStorage(ReflectUtils.newInstance(gatewayStorageClass));
}
// 设置认证后的跳转策略,默认为:DefaultAuthenticationRedirectStrategy,就一句话 response.sendRedirect(potentialRedirectUrl);
final Class<? extends AuthenticationRedirectStrategy> authenticationRedirectStrategyClass = getClass(ConfigurationKeys.AUTHENTICATION_REDIRECT_STRATEGY_CLASS);
if (authenticationRedirectStrategyClass != null) {
this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass);
}
}
}
再来分析过滤方法 doFilter
public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
//调用上面的策略过滤是否需要认证
if (isRequestUrlExcluded(request)) {
logger.debug("Request is ignored.");
filterChain.doFilter(request, response);
return;
}
//获取session,没有就返回null,不创建
final HttpSession session = request.getSession(false);
//CONST_CAS_ASSERTION = _const_cas_assertion_
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
//已经认证过的不用再认证了
if (assertion != null) {
filterChain.doFilter(request, response);
return;
}
//组装serviceURL
final String serviceUrl = constructServiceUrl(request, response);
final String ticket = retrieveTicketFromRequest(request);
final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
//关于Gateway的用法资料
//https://wiki.jasig.org/display/casc/cas+java+client+gateway+example
//http://exceptioneye.iteye.com/blog/1889278
if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
filterChain.doFilter(request, response);
return;
}
final String modifiedServiceUrl;
logger.debug("no ticket and no assertion found");
if (this.gateway) {
logger.debug("setting gateway attribute in session");
//标记已经Gateway了,后面只有ticket参数就可以直接通过了,不用调登录了
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
logger.debug("Constructed service url: {}", modifiedServiceUrl);
final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
logger.debug("redirecting to \"{}\"", urlToRedirectTo);
this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
}
-
AbstractTicketValidationFilter
处理认证ticket的一切
getSSLConfig
获取SSL的配置信息
getHostnameVerifier
获取 HostnameVerifier 的配置信息
这两个方法都是处理HTTPS的一些配置
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
setExceptionOnValidationFailure(getBoolean(ConfigurationKeys.EXCEPTION_ON_VALIDATION_FAILURE));
setRedirectAfterValidation(getBoolean(ConfigurationKeys.REDIRECT_AFTER_VALIDATION));
setUseSession(getBoolean(ConfigurationKeys.USE_SESSION));
//为了避免无限重定向,当不用session时会强制设置 RedirectAfterValidation = false
if (!this.useSession && this.redirectAfterValidation) {
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);
}
上面的初始化方法很简单.略...
下面分析doFilter
方法
public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
if (!preFilter(servletRequest, servletResponse, filterChain)) {
return;
}
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final String ticket = retrieveTicketFromRequest(request);
//只处理有ticket参数的请求
if (CommonUtils.isNotBlank(ticket)) {
logger.debug("Attempting to validate ticket: {}", ticket);
try {
//调用认证器认证请求
final Assertion assertion = this.ticketValidator.validate(ticket,
constructServiceUrl(request, response));
logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
request.setAttribute(CONST_CAS_ASSERTION, assertion);
if (this.useSession) {
request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
}
onSuccessfulValidation(request, response, assertion);
if (this.redirectAfterValidation) {
logger.debug("Redirecting after successful ticket validation.");
response.sendRedirect(constructServiceUrl(request, response));
return;
}
} catch (final TicketValidationException e) {
logger.debug(e.getMessage(), e);
onFailedValidation(request, response);
if (this.exceptionOnValidationFailure) {
throw new ServletException(e);
}
response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
return;
}
}
filterChain.doFilter(request, response);
}
从上面代码可以看到,关键就是ticketValidator 认证器了,下面详细分析
首先贴类图:
TicketValidator.png
-
AbstractUrlBasedTicketValidator
根据名字和类图的位置很显然这个是个抽象基类,处理一些公共的事情,在这里用到了模板方法,定义了整个验证处理的流程,真正的逻辑在具体的类中
public final Assertion validate(final String ticket, final String service) throws TicketValidationException {
//组装成验证的请求,这个方法里面也定义了一写抽象方法
final String validationUrl = constructValidationUrl(ticket, service);
logger.debug("Constructing validation url: {}", validationUrl);
try {
logger.debug("Retrieving response from server.");
//连接CAS Server验证ticket,这里定义了一个抽象方法
final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);
if (serverResponse == null) {
throw new TicketValidationException("The CAS server returned no response.");
}
logger.debug("Server response: {}", serverResponse);
//将CAS server的响应信息封装成 Assertion,这里也是定义了一个抽象方法
return parseResponseFromServer(serverResponse);
} catch (final MalformedURLException e) {
throw new TicketValidationException(e);
}
}
-
AbstractCasProtocolUrlBasedTicketValidator
实现了一个方法 retrieveResponseFromServer
就一行代码
return CommonUtils.getResponseFromServer(validationUrl, getURLConnectionFactory(), getEncoding());
我们分析下 CommonUtils.getResponseFromServer
public static String getResponseFromServer(final URL constructedUrl, final HttpURLConnectionFactory factory,
final String encoding) {
HttpURLConnection conn = null;
InputStreamReader in = null;
try {
//核心代码就这一句,factory有两个实现,这里一般是用 HttpsURLConnectionFactory
//里面主要是对SSLSocketFactory和HostnameVerifier 两个属性进行特殊赋值,有时我们需要用到
//HostnameVerifier在CAS client里有3中实现,AnyHostnameVerifier,RegexHostnameVerifier和WhitelistHostnameVerifier
conn = factory.buildHttpURLConnection(constructedUrl.openConnection());
if (CommonUtils.isEmpty(encoding)) {
in = new InputStreamReader(conn.getInputStream());
} else {
in = new InputStreamReader(conn.getInputStream(), encoding);
}
final StringBuilder builder = new StringBuilder(255);
int byteRead;
while ((byteRead = in.read()) != -1) {
builder.append((char) byteRead);
}
return builder.toString();
} catch (final RuntimeException e) {
throw e;
} catch (final SSLException e) {
LOGGER.error("SSL error getting response from host: {} : Error Message: {}", constructedUrl.getHost(), e.getMessage(), e);
throw new RuntimeException(e);
} catch (final IOException e) {
LOGGER.error("Error getting response from host: [{}] with path: [{}] and protocol: [{}] Error Message: {}",
constructedUrl.getHost(), constructedUrl.getPath(), constructedUrl.getProtocol(), e.getMessage(), e);
throw new RuntimeException(e);
} finally {
closeQuietly(in);
if (conn != null) {
conn.disconnect();
}
}
}
-
Cas10TicketValidator
用CAS1.0协议的验证器.代码很简单,现在都基本不用了,端点是:validate
-
Cas20ServiceTicketValidator
支持CAS2.0,端点是 serviceValidate
有3个属性:
proxyCallbackUrl,proxyGrantingTicketStorage 和 proxyRetriever
里面主要也是覆写了一个方法 parseResponseFromServer
protected Assertion parseResponseFromServer(final String response) throws TicketValidationException {
//从返回的XML中获取失败信息 ,代码就一行 XmlUtils.getTextForElement(response, "authenticationFailure");
final String error = parseAuthenticationFailureFromResponse(response);
//失败了直接抛异常
if (CommonUtils.isNotBlank(error)) {
throw new TicketValidationException(error);
}
//获取用户认证信息 XmlUtils.getTextForElement(response, "user");
final String principal = parsePrincipalFromResponse(response);
//一样一行代码,XmlUtils.getTextForElement(response, "proxyGrantingTicket");
final String proxyGrantingTicketIou = parseProxyGrantingTicketFromResponse(response);
final String proxyGrantingTicket;
if (CommonUtils.isBlank(proxyGrantingTicketIou) || this.proxyGrantingTicketStorage == null) {
proxyGrantingTicket = null;
} else {
// ProxyGrantingTicketStorage 有两个实现 ProxyGrantingTicketStore 和 ProxyGrantingTicketStorageImpl
// CasConfiguration 中用的是 ProxyGrantingTicketStore,用Google的Guava 缓存实现
//ProxyGrantingTicketStorageImpl 用的是 ConcurrentHashMap 实现,对于超时的处理是通过 CleanUpTimerTask 定时任务去处理的
proxyGrantingTicket = this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou);
}
if (CommonUtils.isEmpty(principal)) {
throw new TicketValidationException("No principal was found in the response from the CAS server.");
}
final Assertion assertion;
//调用 SAX将返回的数据转换成Map,里面定义了 CustomAttributeHandler做转换处理
final Map<String, Object> attributes = extractCustomAttributes(response);
if (CommonUtils.isNotBlank(proxyGrantingTicket)) {
final AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, attributes,
proxyGrantingTicket, this.proxyRetriever);
assertion = new AssertionImpl(attributePrincipal);
} else {
assertion = new AssertionImpl(new AttributePrincipalImpl(principal, attributes));
}
//模板方法
customParseResponse(response, assertion);
return assertion;
}
-
Cas30ServiceTicketValidator
继承自Cas20ServiceTicketValidator
只改变了端口为p3/serviceValidate
-
Cas30JsonServiceTicketValidator
处理CAS Server返回值是JSON格式的情况
@Override
protected Assertion parseResponseFromServer(final String response) throws TicketValidationException {
try {
//JsonValidationResponseParser 负责将 JSON传转换成 TicketValidationJsonResponse对象,用 Jackson的ObjectMapper
//并校验是否成功
final TicketValidationJsonResponse json = new JsonValidationResponseParser().parse(response);
//TicketValidationJsonResponse 负责转换成需要的 Assertion
return json.getAssertion(getProxyGrantingTicketStorage(), getProxyRetriever());
} catch (final JsonProcessingException e) {
//处理JSON失败就默认用XML格式处理
logger.warn("Unable parse the JSON response. Falling back to XML", e);
return super.parseResponseFromServer(response);
} catch (final IOException e) {
throw new TicketValidationException(e.getMessage(), e);
}
}
-
Cas20ProxyTicketValidator
继承自 Cas20ServiceTicketValidator
端点为: proxyValidate
覆写了 customParseResponse
方法,该方法是 Cas20ServiceTicketValidator
的 parseResponseFromServer
最后面调用的
主要是对Proxy做了一些验证
-
Cas30ProxyTicketValidator
端点为:p3/proxyValidate
,其他的方法都没覆写
-
Cas30JsonProxyTicketValidator
处理JSON格式
@Override
protected Assertion parseResponseFromServer(final String response) throws TicketValidationException {
try {
//和前面 Cas30JsonServiceTicketValidator 一样的套路
final TicketValidationJsonResponse json = new JsonValidationResponseParser().parse(response);
return json.getAssertion(getProxyGrantingTicketStorage(), getProxyRetriever());
} catch (final Exception e) {
logger.warn("Unable parse the JSON response");
return super.parseResponseFromServer(response);
}
}
//这个是 Cas20ProxyTicketValidator 中定义的方法, Cas20ProxyTicketValidator是处理XML的,这个是处理JSON的
@Override
protected List<String> parseProxiesFromResponse(final String response) {
try {
//和前面 Cas30JsonServiceTicketValidator 一样的套路
final TicketValidationJsonResponse json = new JsonValidationResponseParser().parse(response);
return json.getServiceResponse().getAuthenticationSuccess().getProxies();
} catch (final Exception e) {
//失败了也会默认调用XML的解析一次
logger.warn("Unable to locate proxies from the JSON response", e);
return super.parseProxiesFromResponse(response);
}
}
-
Cas20ProxyReceivingTicketValidationFilter
处理ticket过滤器,默认用 Cas20ServiceTicketValidator
和 Cas20ProxyTicketValidator
处理
里面有个 proxyGrantingTicketStorage
属性,默认是用 ProxyGrantingTicketStorageImpl
实现的,这个是靠一个定时任务去清除过期ticket的
在初始化的时候定义了一个定时任务去定时清理
public void init() {
super.init();
CommonUtils.assertNotNull(this.proxyGrantingTicketStorage, "proxyGrantingTicketStorage 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);
}
initInternal
方法也是对 该 proxyGrantingTicketStorage
的一些参数处理,比如:加密的方法,密钥;还有设定定时器循环时间.
这个类主要还覆写了getTicketValidator
方法,该方法是在父类 AbstractTicketValidationFilter
定义,这个决定了认证的方法
这里对Cas20ServiceTicketValidator
做了一些特殊处理,代码如下:
/**
* Constructs a Cas20ServiceTicketValidator or a Cas20ProxyTicketValidator based on supplied parameters.
*
* @param filterConfig the Filter Configuration object.
* @return a fully constructed TicketValidator.
*/
protected final TicketValidator getTicketValidator(final FilterConfig filterConfig) {
final boolean allowAnyProxy = getBoolean(ConfigurationKeys.ACCEPT_ANY_PROXY);
final String allowedProxyChains = getString(ConfigurationKeys.ALLOWED_PROXY_CHAINS);
final String casServerUrlPrefix = getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX);
final Class<? extends Cas20ServiceTicketValidator> ticketValidatorClass = getClass(ConfigurationKeys.TICKET_VALIDATOR_CLASS);
final Cas20ServiceTicketValidator validator;
if (allowAnyProxy || CommonUtils.isNotBlank(allowedProxyChains)) {
final Cas20ProxyTicketValidator v = createNewTicketValidator(ticketValidatorClass, casServerUrlPrefix,
this.defaultProxyTicketValidatorClass);
//处理代理的参数
v.setAcceptAnyProxy(allowAnyProxy);
v.setAllowedProxyChains(CommonUtils.createProxyList(allowedProxyChains));
validator = v;
} else {
validator = createNewTicketValidator(ticketValidatorClass, casServerUrlPrefix,
this.defaultServiceTicketValidatorClass);
}
validator.setProxyCallbackUrl(getString(ConfigurationKeys.PROXY_CALLBACK_URL));
//赋值init方法里面初始化的存储方式
validator.setProxyGrantingTicketStorage(this.proxyGrantingTicketStorage);
final HttpURLConnectionFactory factory = new HttpsURLConnectionFactory(getHostnameVerifier(),
getSSLConfig());
validator.setURLConnectionFactory(factory);
validator.setProxyRetriever(new Cas20ProxyRetriever(casServerUrlPrefix, getString(ConfigurationKeys.ENCODING), factory));
validator.setRenew(getBoolean(ConfigurationKeys.RENEW));
validator.setEncoding(getString(ConfigurationKeys.ENCODING));
//添加用户自定义的参数
final Map<String, String> additionalParameters = new HashMap<String, String>();
final List<String> params = Arrays.asList(RESERVED_INIT_PARAMS);
for (final Enumeration<?> e = filterConfig.getInitParameterNames(); e.hasMoreElements(); ) {
final String s = (String) e.nextElement();
if (!params.contains(s)) {
additionalParameters.put(s, filterConfig.getInitParameter(s));
}
}
validator.setCustomParameters(additionalParameters);
return validator;
}
-
Cas20ProxyRetriever:
这个类主要是为CasProxyProfile
提供getProxyTicketFor
功能
声明了getProxyTicketIdFor
proxyGrantingTicketId 就是 proxyGrantingTicketStorage 存起来的 ,根据 proxyGrantingTicketIou来获取,
而 proxyGrantingTicketIou来获取 是从 validate的请求返回的XML(JSON)文件信息获取的,代码是:
XmlUtils.getTextForElement(response, "proxyGrantingTicket");
-
AssertionThreadLocalFilter
将 Assertion
信息放到 AssertionHolder
里面, AssertionHolder
是一个 ThreadLocal
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpSession session = request.getSession(false);
final Assertion assertion = (Assertion) (session == null ? request
.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session
.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION));
try {
AssertionHolder.setAssertion(assertion);
filterChain.doFilter(servletRequest, servletResponse);
} finally {
AssertionHolder.clear();
}
}
-
DelegatingFilter
代理Filter,根据某个参数和设定的 Map<String, Filter> delegators
匹配,匹配上就用某一个过滤器.
tips:
//是否就是某一个类
public boolean exactMatch(final Throwable e) {
return this.className.equals(e.getClass());
}
//判断是否继承自某一个类
public boolean inheritanceMatch(final Throwable e) {
return className.isAssignableFrom(e.getClass());
}
-
用到的认证实体关系
Assertion:包含认证的时间和 AttributePrincipal
AttributePrincipal是一个接口,继承自Principal 定义了一个Map属性和getProxyTicketFor方法,实现类是AttributePrincipalImpl
SimplePrincipal 实现了 Principal
AssertionPrincipal 继承自 SimplePrincipal 并且包含了 Assertion
网友评论