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:
根据相关参数判断用户是否合法,没有则去服务端登录,主要逻辑如下:
- 根据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/