这篇紧接上一篇代理文章解读。
一、请求示例
配置好以后接下来将展示一个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继承自Cas20ProxyReceivingTicketValidationFilterCas20ProxyReceivingTicketValidationFilter又继承自AbstractTicketValidationFilterCas20ProxyReceivingTicketValidationFilter有一个前置过滤器方法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
未经允许请勿转载:程序喵 » Cas 5.2.x版本使用 —— 代理认证拓展理解(十五)
程序喵