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参数判定

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

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对象有什么:认证的类型,是否第一次登录,认证日期,认证的处理器

 

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

猜你喜欢

转载自www.cnblogs.com/1994jinnan/p/13400868.html