Cas客户端源码解析

Cas客户端的调用流程主要有几个过滤器实现:

  • casSingleSignOutFilter
  • casValidationFilter
  • casAuthenticationFilter
  • casHttpServletRequestWrapperFilter
  • casAssertionThreadLocalFilter

 这5个过滤器的调用顺序之上而下依次执行,只有这几个过滤器执行完毕后,才会进入自己的过滤器中。

  • SingleSignOutFilter

       1. 拦截登录请求,通过有无ticket(url参数)参数判断,即登录后回来的第一步。如果有ticket,则创建session,并且记录session和ticket的一对一关系,此后将不会有ticket参数;

      2.  拦截登出请求,根据服务端传过来的ticket参数,找到对应的session,销毁session;

 源码如下:

public boolean process(HttpServletRequest request, HttpServletResponse response) {
    if (this.isTokenRequest(request)) {
        this.logger.trace("Received a token request");
        // 将cookie和session建立对应关系
        this.recordSession(request);
        return true;
    } else if (this.isLogoutRequest(request)) {
        this.logger.trace("Received a logout request");
        // 根据cookie找到session,删除
        this.destroySession(request);
        return false;
    } else {
        this.logger.trace("Ignoring URI for logout: {}", request.getRequestURI());
        return true;
    }
}

1. 如何判断登录登出请求:

登录请求根据ticket参数判定

Cas客户端源码解析

 登出请求服务端发送的post请求,根据logoutRequest参数判断

Cas客户端源码解析Cas客户端源码解析

2. 记录session的实现

  • 创建session
  • 获取token,其实就是st
  • 将session与token一对一的关系存储起来-map
private void recordSession(final HttpServletRequest request) {
    final HttpSession session = request.getSession(this.eagerlyCreateSessions);

    final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters);
           this.sessionMappingStorage.removeBySessionById(session.getId());
    } 
    // 存储session
    sessionMappingStorage.addSessionById(token, session);
}

3. 销毁session的实现

  • 根据logoutRequest参数获取参数值:logoutMessage 
  • 解析xml得到ticket(token)
  • 根据token获取到httpSession
  • 销毁session
private void destroySession(final HttpServletRequest request) {
//根据logoutRequest参数获取参数值:logoutMessage  ,xml格式
    String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters);
    if (CommonUtils.isBlank(logoutMessage)) {
        logger.error("Could not locate logout message of the request from {}", this.logoutParameterName);
        return;
    }
    
    if (!logoutMessage.contains("SessionIndex")) {
        logoutMessage = uncompressLogoutMessage(logoutMessage);
    }
    
    //解析xml得到ST(token)
    final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
    if (CommonUtils.isNotBlank(token)) {
    // 根据token获取到httpSession
        final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);

        if (session != null) {
            final String sessionID = session.getId();
            logger.debug("Invalidating session [{}] for token [{}]", sessionID, token);

            try {
            //销毁session
                session.invalidate();
            } catch (final IllegalStateException e) {
                logger.debug("Error invalidating session.", e);
            }
            this.logoutStrategy.logout(request);
        }
    }
}

 

登出的接收到的消息为xml,示列如下:

<samlp:LogoutRequest

    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-4-mt3j3uZ0yd1TASsSyBAXLoEN" Version="2.0" IssueInstant="2020-07-23T18:07:08Z">

    <saml:NameID

        xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">@NOT_USED@

    </saml:NameID>

    <samlp:SessionIndex>ST-6-pcFgrWGzkDwaTjKQkkSzmAYQfzYA013935-PC</samlp:SessionIndex>

</samlp:LogoutRequest>

 

  • Cas30ProxyReceingTicketValidattionFilter-AbstractTicketValidationFilter

      主要逻辑: 无ticket,放过;有ticket,去cas server校验ticket是否合法,url参数中会携带ticket及service地址,示例如下:

https://appcas.com:9433/cas/p3/serviceValidate?ticket=ST-1-NmeS2nEH5y6bJRewb56JDM6B-4sA013935-PC&service=http%3A%2F%2Fappportal.com%3A18835%2F

 如果校验成功,重定向到service地址,源码如下:

final Assertion assertion = this.ticketValidator.validate(ticket,
        constructServiceUrl(request, response));
        // 用户信息设置进request
request.setAttribute(CONST_CAS_ASSERTION, assertion);

if (this.useSession) {
    request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
}
// 里面什么没有
onSuccessfulValidation(request, response, assertion);
// redirectAfterValidation 默认为true
if (this.redirectAfterValidation) {
    logger.debug("Redirecting after successful ticket validation.");
    response.sendRedirect(constructServiceUrl(request, response));
    return;
}

 

  • AuthenticationFilter:

根据相关参数判断用户是否合法,没有则去服务端登录,主要逻辑如下:

  1. 根据Session获取Assertion对象(用户信息),有放过:
// 如果符合白名单,直接放过	
if (isRequestUrlExcluded(request)) {
    logger.debug("Request is ignored.");
    filterChain.doFilter(request, response);
    return;
}

final HttpSession session = request.getSession(false);
// 从session中获取用户信息
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;

if (assertion != null) {
    filterChain.doFilter(request, response);
    return;
}

 

2. 查找ticket或者gateway,有放过

final String serviceUrl = constructServiceUrl(request, response);
final String ticket = retrieveTicketFromRequest(request);
final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);

if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
    filterChain.doFilter(request, response);
    return;
}

 

3. 以上逻辑都没有,则去cas server登录

final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
    getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway, this.method);

logger.debug("redirecting to \"{}\"", urlToRedirectTo);
this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);

 

  • HttpServletRequestWrapperFilter

将Request进行重新包装,以便可以从Request中可以获取到用户信息,以下三个方法都可以获取到用户信息:

  • HttpServletRequest.getRemoteUser();
  • HttpServletRequest.getUserPrincipal();
  • HttpServletRequest.isUserInRole()
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
                     final FilterChain filterChain) throws IOException, ServletException {
                     //从session或者request中获取principal
    final AttributePrincipal principal = retrievePrincipalFromSessionOrRequest(servletRequest);
//根据principal包装一个Reqeust,
    filterChain.doFilter(new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal),
            servletResponse);
}

包装实现:

final class CasHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private final AttributePrincipal principal;

    CasHttpServletRequestWrapper(final HttpServletRequest request, final AttributePrincipal principal) {
        super(request);
        this.principal = principal;
    }

    @Override
    public Principal getUserPrincipal() {
        return this.principal;
    }

    @Override
    public String getRemoteUser() {
        return principal != null ? this.principal.getName() : null;
    }

    @Override
    public boolean isUserInRole(final String role) {

 

  • AssertionThreadLocalFilter

从Request中取出用户信息,封装到AssertionHolder中,以便其他地方(无Request对象)也可以获取到当前用户信息:

Assertion userInfo=AssertionHolder.getAssertion();
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();
    }
}

 

最后看一眼Assertion对象有什么:认证的类型,是否第一次登录,认证日期,认证的处理器

 Cas客户端源码解析

Cas客户端源码解析

相关文章:https://www.360wenxue.cn/

上一篇:说说你对Node.js 的理解?优缺点?应用场景?


下一篇:CDH5.13 集成Kerberos配置