这篇紧接上一篇代理文章解读。
一、请求示例
配置好以后接下来将展示一个app1作为代理端访问app2的应用示例。该示例的重点在于app1的请求发起,对于需要请求的app2端的内容我们假设就是一个API接口,其简单的输出一些文本。对于代理端而言,其请求的发起通常需要经过如下步骤:
1、获取到当前的AttributePrincipal
对象,如果当前可以获取到request对象并且使用了HttpServletRequestWrapperFilter
,我们则可以直接从request中获取。
AttributePrincipal principal = (AttributePrincipal) req.getUserPrincipal();
当然,如果使用了AssertionThreadLocalFilter
,我们也可以从AssertionHolder
中获取Assertion
,进而获取到对应的AttributePrincipal
对象。
AttributePrincipal principal = AssertionHolder.getAssertion().getPrincipal();
2、通过AttributePrincipal
获取针对于被代理端对应的proxy ticket(PT),该操作将促使AttributePrincipal
向Cas Server发起请求,从而获取到对应的PT。针对同一URL每次从Cas Server请求获取到的PT都是不一样的。以下是为 http://client2.com:8889/user/users 请求PT的示例;
String proxyTicket = principal.getProxyTicketFor("http://client2.com:8889/user/users");
3、在请求被代理端时将获取到的proxy ticket以参数ticket一起传递过去,如:
URL url = new URL("http://client2.com:8889/user/users?ticket=" + proxyTicket);
但其实我们一般会对ticket
进行URLEncoder.encode
编码
4、client2在经过过滤器验证PT,通过之后直接返回结果数据。
二、cas客户端内部接口验证顺序
参数说明
-
service
:表示浏览器中受限的 URL 地址 -
tgtUrl
:表示代理回调地址,就是配置中的proxyCallbackUrl
参数内容,这个内容要能被cas所访问,与proxyReceptorUrl
参数路径匹配,回调不需要代码实现,这点不同于‘支付回调’ -
targetService
:表示代理方请求被代理方的受限 URL 目标地址 -
format
:表示数据返回的格式,有JSON
、XML
两种,默认XML
-
ticket
:表示 ST/PT 的值
1、POST请求调用 https://cas.server.com:8443/cas/v1/tickets form格式参数:username
、password
,获取 TGT
2、POST请求调用 https://cas.server.com:8443/cas/v1/tickets/ST值 form格式参数:service
,获取 ST
3、GET请求调用 https://cas.server.com:8443/cas/p3/proxyValidate form格式参数:service
、pgtUrl
、ticket
通过 pgtUrl
回调,获取 PGTIOU
、PGT
,并存储在 ProxyGrantingTicketStorage
中
https://cas.server.com:8443/cas/p3/proxyValidate?format=json&service=http%3A%2F%2Fclient1.com%3A8888%2Fproxy%2Fbooks&pgtUrl=http%3A%2F%2Fclient1.com%3A8888%2Fproxy%2Fcallback&ticket=ST值 解码后 https://cas.server.com:8443/cas/p3/proxyValidate?format=json&service=http://client1.com:8888/proxy/books&pgtUrl=http://client1.com:8888/proxy/callback&ticket=ST值
备注:单独用http请求,只能拿到PGTIOU的JSON数据。只有在回调中,才能取到 PGTIOU 和 PGT 信息,格式如下
{ "serviceResponse": { "authenticationSuccess": { "user": "casuser", "proxyGrantingTicket": "PGTIOU-2-xxxxxxxxxxxxk8I-liurenkuideMacBook-Pro", "attributes": { "credentialType": "UsernamePasswordCredential", "isFromNewLogin": [ false ], "authenticationDate": [ 1521682555.446 ], "authenticationMethod": "AcceptUsersAuthenticationHandler", "successfulAuthenticationHandlers": [ "AcceptUsersAuthenticationHandler" ], "longTermAuthenticationRequestTokenUsed": [ false ] } } } }
Debug回调地址查看PGTIOU和PGT
获取之后,在进行存储,具体的保存逻辑,可以参考后面代码解读
4、GET请求调用 https://cas.server.com:8443/cas/proxy ,form格式参数:targetService
、PGT
获取 PT
https://cas.server.com:8443/cas/proxy?targetService=http%3A%2F%2Fclient2.com%3A8889%2Fbook%2Fbooks&pgt=PGT值 解码后 https://cas.server.com:8443/cas/proxy?targetService=http://client2.com:8889/book/books&pgt=PGT值
正确
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'> <cas:proxySuccess> <cas:proxyTicket>PT-8-zl9p-rUAZ0-43hOhSUp-e2pwIko-liurenkuideMacBook-Pro</cas:proxyTicket> </cas:proxySuccess> </cas:serviceResponse>
5、GET请求被代理资源URL:http://client2.com:8889/book/books?ticket=PT值
6、GET请求调用 proxyValidate form格式参数:pgtUrl
、ticket
、service
https://cas.server.com:8443/cas/p3/proxyValidate?ticket=PT值&service=http%3A%2F%2Fclient2.com%3A8889%2Fbook%2Fbooks 解码后 https://cas.server.com:8443/cas/p3/proxyValidate?ticket=PT值&service=http://client2.com:8889/book/books
三、代理认证保存PGT逻辑,源码解读
Cas30ProxyReceivingTicketValidationFilter
继承自Cas20ProxyReceivingTicketValidationFilter
Cas20ProxyReceivingTicketValidationFilter
又继承自AbstractTicketValidationFilter
Cas20ProxyReceivingTicketValidationFilter
有一个前置过滤器方法preFilter
,这个方法最终会被AbstractTicketValidationFilter
父类的doFilter
方法的第一行调用判断preFilter
方法的作用就是,在执行 ticket
验证之前,先处理ProxyReceptor
代理请求(判断proxyReceptorUrl
参数是否存在),源码如下
/** * This processes the ProxyReceptor request before the ticket validation code executes. */ protected final boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) servletRequest; final HttpServletResponse response = (HttpServletResponse) servletResponse; final String requestUri = request.getRequestURI(); if (CommonUtils.isEmpty(this.proxyReceptorUrl) || !requestUri.endsWith(this.proxyReceptorUrl)) { return true; } try { CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage); } catch (final RuntimeException e) { logger.error(e.getMessage(), e); throw e; } return false; }
CommonUtils.readAndRespondToProxyReceptorRequest()
方法用来解析Proxy代理回调内容,获取PGTIOU
、PGT
,调用ProxyGrantingTicketStorageImpl.save()
方法,将两者保存在ProxyGrantingTicketStorage
缓存中
public static void readAndRespondToProxyReceptorRequest(final HttpServletRequest request, final HttpServletResponse response, final ProxyGrantingTicketStorage proxyGrantingTicketStorage) throws IOException { final String proxyGrantingTicketIou = request.getParameter(PARAM_PROXY_GRANTING_TICKET_IOU); final String proxyGrantingTicket = request.getParameter(PARAM_PROXY_GRANTING_TICKET); if (CommonUtils.isBlank(proxyGrantingTicket) || CommonUtils.isBlank(proxyGrantingTicketIou)) { response.getWriter().write(""); return; } LOGGER.debug("Received proxyGrantingTicketId [{}] for proxyGrantingTicketIou [{}]", proxyGrantingTicket, proxyGrantingTicketIou); proxyGrantingTicketStorage.save(proxyGrantingTicketIou, proxyGrantingTicket); LOGGER.debug("Successfully saved proxyGrantingTicketId [{}] for proxyGrantingTicketIou [{}]", proxyGrantingTicket, proxyGrantingTicketIou); response.getWriter().write("<?xml version=\"1.0\"?>"); response.getWriter().write("<casClient:proxySuccess xmlns:casClient=\"http://www.yale.edu/tp/casClient\" />"); }
proxyGrantingTicketStorage.save()
方法用来将PGTIOU
为Key,PGT
为Value存储在cache缓存中。
public void save(final String proxyGrantingTicketIou, final String proxyGrantingTicket) { final ProxyGrantingTicketHolder holder = new ProxyGrantingTicketHolder(proxyGrantingTicket); logger.debug("Saving ProxyGrantingTicketIOU and ProxyGrantingTicket combo: [{}, {}]", proxyGrantingTicketIou, proxyGrantingTicket); this.cache.put(proxyGrantingTicketIou, holder); }
ProxyGrantingTicketStorage.java 介绍
ProxyGrantingTicketStorage
本身又是一个接口,仅提供了保存、获取、清除这3个方法 ProxyGrantingTicketStorage
接口,提供了两个实现类:AbstractEncryptedProxyGrantingTicketStorageImpl
和 ProxyGrantingTicketStorageImpl
从字面意思可以看得出来第一个时提供了加密方式进行存储PGT信息,默认使用非加密方式。
package org.jasig.cas.client.proxy; /** * Interface for the storage and retrieval of ProxyGrantingTicketIds by mapping * them to a specific ProxyGrantingTicketIou. * * @author Scott Battaglia * @version $Revision: 11729 $ $Date: 2007-09-26 14:22:30 -0400 (Tue, 26 Sep 2007) $ * @since 3.0 */ public interface ProxyGrantingTicketStorage { /** * Method to save the ProxyGrantingTicket to the backing storage facility. * * @param proxyGrantingTicketIou used as the key * @param proxyGrantingTicket used as the value */ public void save(String proxyGrantingTicketIou, String proxyGrantingTicket); /** * Method to retrieve a ProxyGrantingTicket based on the * ProxyGrantingTicketIou. Note that implementations are not guaranteed to * return the same result if retrieve is called twice with the same * proxyGrantingTicketIou. * * @param proxyGrantingTicketIou used as the key * @return the ProxyGrantingTicket Id or null if it can't be found */ public String retrieve(String proxyGrantingTicketIou); /** * Called on a regular basis by an external timer, * giving implementations a chance to remove stale data. */ public void cleanUp(); }
Cas-Server流程Log日志如下
1、AUTHENTICATION_EVENT_TRIGGERED 认证事件触发 2、AUTHENTICATION_SUCCESS 认证成功 3、TICKET_GRANTING_TICKET_CREATED 创建 TGT 4、SERVICE_TICKET_CREATED 为代理 url 创建 ST 5、AUTHENTICATION_SUCCESS 代理 url 认证成功 6、PROXY_GRANTING_TICKET_CREATED 创建 PGT 7、SERVICE_TICKET_VALIDATED ST 验证确认 8、PROXY_TICKET_CREATED 为代理 url 创建 PT 9、SERVICE_TICKET_VALIDATED ST 验证确认
2018-03-22 18:34:26,112 INFO [org.apereo.cas.web.flow.InitialFlowSetupAction] - <Setting path for cookies for warn cookie generator to: [/cas/] > 2018-03-22 18:34:26,160 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: audit:unknown WHAT: [event=success,timestamp=Thu Mar 22 18:34:26 CST 2018,source=RankedAuthenticationProviderWebflowEventResolver] ACTION: AUTHENTICATION_EVENT_TRIGGERED APPLICATION: CAS WHEN: Thu Mar 22 18:34:26 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= > 2018-03-22 18:34:34,433 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: casuser WHAT: Supplied credentials: [casuser] ACTION: AUTHENTICATION_SUCCESS APPLICATION: CAS WHEN: Thu Mar 22 18:34:34 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= > 2018-03-22 18:34:34,567 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: casuser WHAT: TGT-***************************mpqZiUg8aB-RHpPBzqQeaVhKGQ10987-n2eft5AZgEFim4-liurenkuideMBP ACTION: TICKET_GRANTING_TICKET_CREATED APPLICATION: CAS WHEN: Thu Mar 22 18:34:34 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= > 2018-03-22 18:34:37,766 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: casuser WHAT: ST-1-4txtY5ALkSP02-1sSpPOPJmrXtI-liurenkuideMBP for http://client1.com:8888/proxy/books ACTION: SERVICE_TICKET_CREATED APPLICATION: CAS WHEN: Thu Mar 22 18:34:37 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= > 2018-03-22 18:34:43,424 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: http://client1.com:8888/proxy/callback WHAT: Supplied credentials: [http://client1.com:8888/proxy/callback] ACTION: AUTHENTICATION_SUCCESS APPLICATION: CAS WHEN: Thu Mar 22 18:34:43 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= > 2018-03-22 18:34:43,444 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: http://client1.com:8888/proxy/callback WHAT: PGT-***************************************************************mL1jfNtzEs-liurenkuideMBP ACTION: PROXY_GRANTING_TICKET_CREATED APPLICATION: CAS WHEN: Thu Mar 22 18:34:43 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= > 2018-03-22 18:34:43,451 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: casuser WHAT: ST-1-4txtY5ALkSP02-1sSpPOPJmrXtI-liurenkuideMBP ACTION: SERVICE_TICKET_VALIDATED APPLICATION: CAS WHEN: Thu Mar 22 18:34:43 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= > 2018-03-22 18:34:48,736 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: casuser WHAT: PT-2-VCbjdoA30A7uWLMbV0IRf6SDR-0-liurenkuideMBP for http://client2.com:8889/book/books ACTION: PROXY_TICKET_CREATED APPLICATION: CAS WHEN: Thu Mar 22 18:34:48 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= > 2018-03-22 18:34:49,599 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: casuser WHAT: PT-2-VCbjdoA30A7uWLMbV0IRf6SDR-0-liurenkuideMBP ACTION: SERVICE_TICKET_VALIDATED APPLICATION: CAS WHEN: Thu Mar 22 18:34:49 CST 2018 CLIENT IP ADDRESS: 127.0.0.1 SERVER IP ADDRESS: 127.0.0.1 ============================================================= >
四、问题总结
(1) 所提供的代理回调网址'https://xxxx'不能提供认证。
客户端问题
org.jasig.cas.client.validation.TicketValidationException
: 所提供的代理回调网址'https://xxxx'不能提供认证。
2018-04-20 20:03:21.940 ERROR 2421 --- [0.1-8888-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [org.jasig.cas.client.validation.TicketValidationException: 所提供的代理回调网址'https://client1.com:8888/proxy/callback'不能提供认证。] with root cause org.jasig.cas.client.validation.TicketValidationException: 所提供的代理回调网址'https://client1.com:8888/proxy/callback'不能提供认证。 at org.jasig.cas.client.validation.Cas20ServiceTicketValidator.parseResponseFromServer(Cas20ServiceTicketValidator.java:84) ~[cas-client-core-3.5.0.jar:3.5.0]
解决方法:
-
检查以下上面的代理、被代理、cas服务是否配置正确
-
检查是否 PGT 是否获取异常
-
检查 PT 是是否过期
(2)Proxy policy for service [^(https|http)://.*]
cannot authorize the requested callback url
cas服务端问题
2018-03-25 18:15:33,022 WARN [org.apereo.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler] - <Proxy policy for service [^(https|http)://.*] cannot authorize the requested callback url [https://client1.com:8888/proxy/callback].> 2018-03-25 18:15:33,023 ERROR [org.apereo.cas.authentication.PolicyBasedAuthenticationManager] - <Authentication has failed. Credentials may be incorrect or CAS cannot find authentication handler that supports [https://client1.com:8888/proxy/callback] of type [HttpBasedServiceCredential].> 2018-03-25 18:15:33,028 INFO [org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN ============================================================= WHO: https://client1.com:8888/proxy/callback WHAT: Supplied credentials: [https://client1.com:8888/proxy/callback] ACTION: AUTHENTICATION_SUCCESS
这种情况一般是因为你的service的json服务在注册时,代理策略出现问题,检查正则是否正确,推荐: "pattern": "^(https|http)?://.*"
(3)PKIX路径构建失败:找不到要求的目标的有效证书路径
cas服务端问题
PKIX路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException
:找不到要求的目标的有效证书路径
2018-03-21 17:53:46,161 ERROR [org.apereo.cas.util.http.SimpleHttpClient] - <sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target> javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) ~[?:1.8.0_161] at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1959) ~[?:1.8.0_161]
解决方法:
-
参考文章:
五、参考文档
-
CAS代理协议流程图:https://github.com/X-rapido/CAS_SSO_Record/blob/master/assets/pdf/cas_proxy_protocol.pdf
-
CAS协议3.0规范,参考:https://apereo.github.io/cas/5.2.x/protocol/CAS-Protocol-Specification.html
-
cas-client.jar 配置项参数,参考:https://github.com/apereo/java-cas-client