cas深度解析:cas 客户端是如何验证serviceTicket(简称st)的?

今天有同事问子涵先生,cas获取用户数据的时候,是请求了什么接口?额,,,一时语顿……似乎问题没提在点子上。
请求什么接口……明明是客户端远程请求cas Server验证了ticket之后,由cas Server重定向送过来的嘛!当然你也可以理解为请求了什么接口,只是这个接口的参数是ticket和客户端的serviceUrl组成的,对,就是你登录的那个url。想了解更多,请阅读下文吧。

文章目录

    • 一、Cas ticket两种验证方式
      • 1-1 基于TicketValidator
      • 1-2 Filter

一、Cas ticket两种验证方式

本以为cas源码中提供的仅一种方式,于是乎想一探究竟,居然发现了多种。
cas ticke验证方式大体上讲有2个体系:

  • AbstractTicketValidationFilter,基于Filter的ticket的验证;
  • AbstractUrlBasedTicketValidator,基于TicketValidator的验证;

1-1 基于TicketValidator

有几个实现类

  • Cas20ServiceTicketValidator
  • Cas20ProxyTicketValidator
  • 等等

接下来看一下Cas20ServiceTicketValidator类图:

cas深度解析:cas 客户端是如何验证serviceTicket(简称st)的?_第1张图片

AbstractUrlBasedTicketValidator中有个validate方法:

    public Assertion validate(final String ticket, final String service) throws TicketValidationException {


        final String validationUrl = constructValidationUrl(ticket, service);
        if (log.isDebugEnabled()) {
            log.debug("Constructing validation url: " + validationUrl);
        }

        try {
        	log.debug("Retrieving response from server.");
            //从后端访问cas Server验证st的有效性
            final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);

            if (serverResponse == null) {
                throw new TicketValidationException("The CAS server returned no response.");
            }
            
            if (log.isDebugEnabled()) {
            	log.debug("Server response: " + serverResponse);
            }
			//根据服务端的返回信息解析用户数据
            return parseResponseFromServer(serverResponse);
        } catch (final MalformedURLException e) {
            throw new TicketValidationException(e);
        }
    }

org.jasig.cas.client.validation.Cas20ServiceTicketValidator#parseResponseFromServer

    protected final Assertion parseResponseFromServer(final String response) throws TicketValidationException {
        final String error = XmlUtils.getTextForElement(response,
                "authenticationFailure");

        if (CommonUtils.isNotBlank(error)) {
            throw new TicketValidationException(error);
        }

        final String principal = XmlUtils.getTextForElement(response, "user");
        final String proxyGrantingTicketIou = XmlUtils.getTextForElement(response, "proxyGrantingTicket");
        final String proxyGrantingTicket = this.proxyGrantingTicketStorage != null ? this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou) : null;

        if (CommonUtils.isEmpty(principal)) {
            throw new TicketValidationException("No principal was found in the response from the CAS server.");
        }

        final Assertion assertion;
        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;
    }

1-2 Filter

当时这个项目是基于Filter集成的,在web.xml中配置了ticket验证的Filter。

<filter>
    <filter-name>CAS Validation Filterfilter-name>
    <filter-class>com.xxxx.fuie.filter.Cas20ProxyReceivingTicketValidationFilterfilter-class>
    <init-param>
        <param-name>encodingparam-name>
        <param-value>UTF-8param-value>
    init-param>
filter>
<filter-mapping>
    <filter-name>CAS Validation Filterfilter-name>
    <url-pattern>/*url-pattern>
filter-mapping>

我们参考源码自定义了com.xxxx.fuie.filter.Cas20ProxyReceivingTicketValidationFilter并重新替换了CAS Validation Filter中的class。该class类图如下:

cas深度解析:cas 客户端是如何验证serviceTicket(简称st)的?_第2张图片

AbstractTicketValidationFilter负责解析用户数据,并放到session中,此处对于cas登录流程不展开描述了,请自行搜索,或结合server端的源码中的login-webflow进行查看。
com.xxxx.fuie.filter.Cas20ProxyReceivingTicketValidationFilter源码片段如下:

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 = CommonUtils.safeGetParameter(request, getArtifactParameterName());

        if (CommonUtils.isNotBlank(ticket)) {
            if (log.isDebugEnabled()) {
                log.debug("Attempting to validate ticket: " + ticket);
            }

            try {
                final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));

                if (log.isDebugEnabled()) {
                    log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());
                }
				//用户信息放入request
                request.setAttribute(CONST_CAS_ASSERTION, assertion);
				// 用户信息放入session
                if (this.useSession) {
                    request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
                }
                onSuccessfulValidation(request, response, assertion);

                if (this.redirectAfterValidation) {
                    log. debug("Redirecting after successful ticket validation.");
                    response.sendRedirect(constructServiceUrl(request, response));
                    return;
                }
            } catch (final TicketValidationException e) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                log.warn(e, e);

                onFailedValidation(request, response);

                if (this.exceptionOnValidationFailure) {
                    throw new ServletException(e);
                }

                return;
            }
        }

        filterChain.doFilter(request, response);

    }

用户认证完成后,就可以从此次request中获取用户信息:

@Override
    public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
        this.outTarget = target;
        this.request = request;

        int index = target.lastIndexOf(";jsessionid");

        if (index > -1) {
            outTarget = target.substring(0, index);
        }

        AttributePrincipal principal1 = (AttributePrincipal) request.getUserPrincipal();
        //=======忽略不相干的代码===================
    }

cas深度解析:cas 客户端是如何验证serviceTicket(简称st)的?_第3张图片

感谢您的赏读。客官,点赞、留言再走呗~或者留下您的问题我们一起探讨

你可能感兴趣的:(#,cas,吾爱开源,java,http,https)