cas-clicet-core-3.1.10.jar(cas客户端)源码分析
项目背景:
- 基于springboot集成cas客户端
- 没有使用springboot自带集成方式,使用传统原始的集成方式
- 由于cas服务端版本比较低,客户端使用的版本比较低
一、先了解如何集成,如何使用
1、cas配置参数实体@Configuration
@Getter
@Setter
public class CasConfiguration {
//cas登录路径
@Value("${cas.casServerUrlPrefix}${cas.casServerLoginUrl}")
private String casServerLoginUrl;
@Value("${cas.casServerUrlPrefix}${cas.casServerLogoutUrl}")
private String casServerLogoutUrl;
//cas客户端服务器
@Value("${cas.clientServerName}${cas.clientService}")
private String clientService;
//登录成功地址
@Value("${cas.clientServerName}${cas.clientService}${cas.clientLoginSuccessUrl}")
private String clientLoginSuccessUrl;
//白名单
@Value("${cas.whiteList}")
private String whiteList;
//cas服务器
@Value("${cas.casServerUrlPrefix}")
private String casServerUrlPrefix;
//cas客户端服务器根目录
@Value("${cas.clientServerName}")
private String clientServerName;
}
2、动态参数配置在application.yml中
cas:
#cas服务器地址
casServerUrlPrefix: http://111.198.186.80:18080/cas
#本地地址
clientServerName: http://127.0.0.1:9090
#登录路径
casServerLoginUrl: /login
#注销路径
casServerLogoutUrl: /logout
#项目名称,上下文
clientService: /project-test
#成功后返回路径
clientLoginSuccessUrl: /vue/index.html
#白名单
whiteList: /login/**.**/css/**
3、配置多个过滤器
/*
* @Description:cas客户端过滤器配置
* @Author: wangwei
* @Date:2020/3/27 10:46
*/
@Configuration
public class CasFilter {
@Autowired
CasConfiguration casConfiguration;
/*
* @Description:退出登录过滤器,需要放在最前面
* @Param:[]
* @Return: org.springframework.boot.web.servlet.FilterRegistrationBean
* @Throws:
* @Author: wangwei
* @Date:2020/3/31 15:44
*/
@Bean
public FilterRegistrationBean CasSingleSignOutFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
//配置拦截器参数map
Map<String, String> map = new HashMap<>(16);
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
filterRegistrationBean.setFilter(singleSignOutFilter);
map.put("casServerUrlPrefix", casConfiguration.getCasServerUrlPrefix());
filterRegistrationBean.setInitParameters(map);
String url = "/*";
filterRegistrationBean.addUrlPatterns(url);
filterRegistrationBean.setName("CasSingleSignOutFilter");
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
//配置 SingleSignOutHttpSessionListener
@Bean
public ServletListenerRegistrationBean<org.jasig.cas.client.session.SingleSignOutHttpSessionListener> casListener() {
return new ServletListenerRegistrationBean<>(
new org.jasig.cas.client.session.SingleSignOutHttpSessionListener());
}
/*
* @Description:CAS认证filter casServerLoginUrl参数:表示CAS Server登录URL,后面追加appResId参数,表明应用类型(公文系统暂时使用GONGWEN,备案系统使用BHXT)。
service参数:表示在通过CAS Server认证后的返回页面。 localLoginUrl参数:本地登录URL。 renew参数:请不要修改。
whiteList参数:不进行认证检查的URI,使用分号进行分割。如果以/为结尾,则表示该路径下的所有URI均不进行认证检查。
* @Param:[]
* @Return: org.springframework.boot.web.servlet.FilterRegistrationBean
* @Throws:
* @Author: wangwei
* @Date:2020/3/27 11:10
*/
@Bean
public FilterRegistrationBean CasAuthenticationFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
//配置拦截器参数map
Map<String, String> map = new HashMap<>(16);
WhiteListJwtAndAuthenticationFilter casAuthenticationFilter = new WhiteListJwtAndAuthenticationFilter();
filterRegistrationBean.setFilter(casAuthenticationFilter);
map.put("casServerLoginUrl", casConfiguration.getCasServerLoginUrl());
map.put("service", casConfiguration.getClientLoginSuccessUrl());
map.put("localLoginUrl", casConfiguration.getClientLoginSuccessUrl());
map.put("renew", "false");
map.put("whiteList", casConfiguration.getWhiteList());
filterRegistrationBean.setInitParameters(map);
String url = "/*";
filterRegistrationBean.addUrlPatterns(url);
filterRegistrationBean.setName("casAuthenticationFilter");
filterRegistrationBean.setOrder(2);
return filterRegistrationBean;
}
/*
* @Description:CAS验证filter serverName参数:应用根路径。 CAS Http请求Wrapper filter:在通过CAS认证或验证通过后,将user id赋值到request中remoteUser中
* @Param:[]
* @Return: org.springframework.boot.web.servlet.FilterRegistrationBean
* @Throws:
* @Author: wangwei
* @Date:2020/3/27 11:10
*/
@Bean
public FilterRegistrationBean CasValidationFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
//配置拦截器参数map
Map<String, String> map = new HashMap<>(16);
CustomCas30ProxyReceivingTicketValidationFilter casValidationFilter = new CustomCas30ProxyReceivingTicketValidationFilter();
filterRegistrationBean.setFilter(casValidationFilter);
map.put("casServerUrlPrefix", casConfiguration.getCasServerUrlPrefix());
map.put("serverName", casConfiguration.getClientServerName());
filterRegistrationBean.setInitParameters(map);
String url = "/*";
filterRegistrationBean.addUrlPatterns(url);
filterRegistrationBean.setName("casValidationFilter");
filterRegistrationBean.setOrder(3);
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean CasHttpServletRequestFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
//配置拦截器参数map
HttpServletRequestWrapperFilter casHttpServletRequestFilter = new HttpServletRequestWrapperFilter();
filterRegistrationBean.setFilter(casHttpServletRequestFilter);
String url = "/*";
filterRegistrationBean.addUrlPatterns(url);
filterRegistrationBean.setName("casHttpServletRequestFilter");
filterRegistrationBean.setOrder(4);
return filterRegistrationBean;
}
}
4、CustomCas30ProxyReceivingTicketValidationFilter过滤器
public class CustomCas30ProxyReceivingTicketValidationFilter extends Cas10TicketValidationFilter {
/*
* @Description:校验成功后设置sesion
* @Param:[request, response, assertion]
* @Return: void
* @Throws:
* @Author: wangwei
* @Date:2020/3/29 10:40
*/
@Override
protected void onSuccessfulValidation(HttpServletRequest request, HttpServletResponse response, Assertion assertion) {
String dcpLoginInfo = (String) assertion.getPrincipal().getName();
javax.servlet.http.HttpSession session=request.getSession(false);
if(session!=null){
session.setAttribute("systemUser",dcpLoginInfo);
}
}
}
5、WhiteListJwtAndAuthenticationFilter 复写认证过滤器,添加了白名单,跳过cas认证的条件
public class WhiteListJwtAndAuthenticationFilter extends AbstractCasFilter {
private String casServerLoginUrl;
private boolean renew = false;
private boolean gateway = false;
private List<String> whiteList;
private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
public WhiteListJwtAndAuthenticationFilter() {
}
@Override
protected void initInternal(FilterConfig filterConfig) throws ServletException {
if (!this.isIgnoreInitConfiguration()) {
super.initInternal(filterConfig);
this.setCasServerLoginUrl(this.getPropertyFromInitParams(filterConfig, "casServerLoginUrl", (String)null));
this.log.trace("Loaded CasServerLoginUrl parameter: " + this.casServerLoginUrl);
this.setRenew(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "renew", "false")));
this.log.trace("Loaded renew parameter: " + this.renew);
this.setGateway(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "gateway", "false")));
this.log.trace("Loaded gateway parameter: " + this.gateway);
this.setWhiteList(this.parseStringList(this.getPropertyFromInitParams(filterConfig, "whiteList", "")));
String gatewayStorageClass = this.getPropertyFromInitParams(filterConfig, "gatewayStorageClass", (String)null);
if (gatewayStorageClass != null) {
try {
this.gatewayStorage = (GatewayResolver)Class.forName(gatewayStorageClass).newInstance();
} catch (Exception var4) {
this.log.error(var4, var4);
throw new ServletException(var4);
}
}
}
}
private List<String> parseStringList(String whiteListStr) {
String[] whiteListArray = whiteListStr.split(",");
if (whiteListArray != null && whiteListArray.length > 0) {
List<String> whiteList = new ArrayList(whiteListArray.length);
String[] arr$ = whiteListArray;
int len$ = whiteListArray.length;
for(int i$ = 0; i$ < len$; ++i$) {
String s = arr$[i$];
whiteList.add(s);
}
return whiteList;
} else {
return null;
}
}
/*
* @Description:白名单路径匹配
* @Author: wangwei
* @Date:2020/5/14 17:11
*/
private boolean isSkipCheck(String uri, String contextPath) {
PathMatcher pathMatcherToUse = new AntPathMatcher();
if (!ObjectUtils.isEmpty(this.whiteList)) {
for(int i = 0; i< whiteList.size(); i++) {
String pattern = whiteList.get(i);
if (pathMatcherToUse.match(pattern, uri)) {
return true;
}
}
}
return false;
}
@Override
public void init() {
super.init();
CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");
}
@Override
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
HttpSession session = request.getSession(false);
Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
if (assertion != null) {
filterChain.doFilter(request, response);
} else {
String contextPath = request.getContextPath();
String uri = request.getRequestURI().substring(contextPath.length(),request.getRequestURI().length());
//获取参数
String headerFlag = request.getHeader("flagCas");
String urlFlag = request.getParameter("flagCas");
//判断是否为白名单
if (this.isSkipCheck(uri, contextPath)) {
filterChain.doFilter(request, response);
//根据参数判断是否需要cas认证
} else if("no".equals(headerFlag)||"no".equals(urlFlag)){
filterChain.doFilter(request, response);
}else {
String serviceUrl = this.constructServiceUrl(request, response);
String ticket = CommonUtils.safeGetParameter(request, this.getArtifactParameterName());
boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
this.log.debug("no ticket and no assertion found");
String modifiedServiceUrl;
if (this.gateway) {
this.log.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
if (this.log.isDebugEnabled()) {
this.log.debug("Constructed service url: " + modifiedServiceUrl);
}
String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
if (this.log.isDebugEnabled()) {
this.log.debug("redirecting to \"" + urlToRedirectTo + "\"");
}
response.sendRedirect(urlToRedirectTo);
} else {
filterChain.doFilter(request, response);
}
}
}
}
public final void setRenew(boolean renew) {
this.renew = renew;
}
public final void setGateway(boolean gateway) {
this.gateway = gateway;
}
public final void setCasServerLoginUrl(String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
public final void setGatewayStorage(GatewayResolver gatewayStorage) {
this.gatewayStorage = gatewayStorage;
}
public void setWhiteList(List<String> whiteList) {
this.whiteList = whiteList;
}
}
二、结合实现,以及流程图,分析客户端源码,请参考CasFilter文件
1、WhiteListJwtAndAuthenticationFilter过滤器(这是复写了jar包中AuthenticationFilter过滤器,主要的改造的是添加了白名单,添加了跳过cas单点认证方式)
@Override
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
HttpSession session = request.getSession(false);
//判断有没有session
Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
//session有数据说明本地已经登录了,直接跳转路径
if (assertion != null) {
filterChain.doFilter(request, response);
} else {
/*
否则用户没有登录
1、判断是不是白名单路径,如果是白名单路径放行
2、判断是否关闭单点登录验证,这样用户就可以使用自己的登录认证方式,这样就既可以使用单点登录,也可以使用本地登录
3、如果上面两个条件都不满足
3.1判断用户是户是否有token值,如果有放行(有token值说明用户已经经过cas服务认证登录了,并且返回了token值)
3.2如果没有token值重定向cas认证登录
*/
String contextPath = request.getContextPath();
String uri = request.getRequestURI().substring(contextPath.length(),request.getRequestURI().length());
//获取参数
String headerFlag = request.getHeader("flagCas");
String urlFlag = request.getParameter("flagCas");
//判断是否为白名单
if (this.isSkipCheck(uri, contextPath)) {
filterChain.doFilter(request, response);
//根据参数判断是否需要cas认证
} else if("no".equals(headerFlag)||"no".equals(urlFlag)){
filterChain.doFilter(request, response);
}else {
String serviceUrl = this.constructServiceUrl(request, response);
String ticket = CommonUtils.safeGetParameter(request, this.getArtifactParameterName());
boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
this.log.debug("no ticket and no assertion found");
String modifiedServiceUrl;
if (this.gateway) {
this.log.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
if (this.log.isDebugEnabled()) {
this.log.debug("Constructed service url: " + modifiedServiceUrl);
}
String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
if (this.log.isDebugEnabled()) {
this.log.debug("redirecting to \"" + urlToRedirectTo + "\"");
}
response.sendRedirect(urlToRedirectTo);
} else {
filterChain.doFilter(request, response);
}
}
}
}
2、Cas10TicketValidationFilter过滤继承的AbstractTicketValidationFilter过滤器(登录成功了返回token,验证token是否有效)
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (this.preFilter(servletRequest, servletResponse, filterChain)) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String ticket = CommonUtils.safeGetParameter(request, this.getArtifactParameterName());
/*
重点:***********************************************************
这个拦截器只拦截带有token值的路径,这样就实现了只在用户登录成功后返回token值验证token值有效后,不会每一个请求都验证token的有效性,这样安全性得到了保证,也不会频繁访问cas服务端验证有效性
重点:***********************************************************
*/
if (CommonUtils.isNotBlank(ticket)) {
if (this.log.isDebugEnabled()) {
this.log.debug("Attempting to validate ticket: " + ticket);
}
try {
Assertion assertion = this.ticketValidator.validate(ticket, this.constructServiceUrl(request, response));
if (this.log.isDebugEnabled()) {
this.log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());
}
request.setAttribute("_const_cas_assertion_", assertion);
if (this.useSession) {
request.getSession().setAttribute("_const_cas_assertion_", assertion);
}
this.onSuccessfulValidation(request, response, assertion);
} catch (TicketValidationException var8) {
response.setStatus(403);
this.log.warn(var8, var8);
this.onFailedValidation(request, response);
if (this.exceptionOnValidationFailure) {
throw new ServletException(var8);
}
}
if (this.redirectAfterValidation) {
this.log.debug("Redirecting after successful ticket validation.");
response.sendRedirect(response.encodeRedirectURL(this.constructServiceUrl(request, response)));
return;
}
}
filterChain.doFilter(request, response);
}
}
3、SingleSignOutFilter,单点登录注销过滤器
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
String artifact;
if ("POST".equals(request.getMethod())) {
artifact = CommonUtils.safeGetParameter(request, "logoutRequest");
if (CommonUtils.isNotBlank(artifact)) {
if (log.isTraceEnabled()) {
log.trace("Logout request=[" + artifact + "]");
}
String sessionIdentifier = XmlUtils.getTextForElement(artifact, "SessionIndex");
if (CommonUtils.isNotBlank(sessionIdentifier)) {
HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier);
if (session != null) {
String sessionID = session.getId();
if (log.isDebugEnabled()) {
log.debug("Invalidating session [" + sessionID + "] for ST [" + sessionIdentifier + "]");
}
try {
session.invalidate();
} catch (IllegalStateException var10) {
log.debug(var10, var10);
}
}
return;
}
}
} else {
artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName);
if (CommonUtils.isNotBlank(artifact)) {
HttpSession session = request.getSession(true);
if (log.isDebugEnabled()) {
log.debug("Storing session identifier for " + session.getId());
}
try {
SESSION_MAPPING_STORAGE.removeBySessionById(session.getId());
} catch (Exception var11) {
}
SESSION_MAPPING_STORAGE.addSessionById(artifact, session);
} else {
log.debug("No Artifact Provided; no action taking place.");
}
}
filterChain.doFilter(servletRequest, servletResponse);
}