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

 



相关文章:

cas服务端源码解析

Springboot整合Cas客户端源码解析

springboot整合Cas客户端搭建

以上是 Cas客户端源码解析 的全部内容, 来源链接: utcz.com/z/518879.html

回到顶部