Cas 5.2.x版本使用 —— 代理认证拓展理解(十五)

这篇紧接上一篇代理文章解读。

一、请求示例

配置好以后接下来将展示一个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:表示数据返回的格式,有JSONXML两种,默认XML

  • ticket:表示 ST/PT 的值

1、POST请求调用 https://cas.server.com:8443/cas/v1/tickets form格式参数:usernamepassword,获取 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格式参数:servicepgtUrlticket 通过 pgtUrl 回调,获取 PGTIOUPGT,并存储在 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

003.png

获取之后,在进行存储,具体的保存逻辑,可以参考后面代码解读

004.png

4、GET请求调用 https://cas.server.com:8443/cas/proxy ,form格式参数:targetServicePGT 获取 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格式参数:pgtUrlticketservice

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代理回调内容,获取PGTIOUPGT,调用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 5.2.x版本使用 —— 代理认证拓展理解(十五)

点  赞 (2) 打  赏
分享到: