单点
單點(diǎn)登陸
單點(diǎn)登陸相關(guān)知識(shí)
理解Cookie和Session機(jī)制
單點(diǎn)登陸時(shí)序圖
cas-clicet-core-3.1.10.jar(cas客戶端)源碼分析
項(xiàng)目背景:
- 基于springboot集成cas客戶端
- 沒(méi)有使用springboot自帶集成方式,使用傳統(tǒng)原始的集成方式
- 由于cas服務(wù)端版本比較低,客戶端使用的版本比較低
一、先了解如何集成,如何使用
1、cas配置參數(shù)實(shí)體@Configuration
2、動(dòng)態(tài)參數(shù)配置在application.yml中
cas:#cas服務(wù)器地址casServerUrlPrefix: http://111.198.186.80:18080/cas#本地地址clientServerName: http://127.0.0.1:9090#登錄路徑casServerLoginUrl: /login#注銷路徑casServerLogoutUrl: /logout#項(xiàng)目名稱,上下文clientService: /project-test#成功后返回路徑clientLoginSuccessUrl: /vue/index.html#白名單whiteList: /login/**.**/css/**3、配置多個(gè)過(guò)濾器
/** @Description:cas客戶端過(guò)濾器配置* @Author: wangwei* @Date:2020/3/27 10:46*/ @Configuration public class CasFilter {@AutowiredCasConfiguration casConfiguration;/** @Description:退出登錄過(guò)濾器,需要放在最前面* @Param:[]* @Return: org.springframework.boot.web.servlet.FilterRegistrationBean* @Throws:* @Author: wangwei* @Date:2020/3/31 15:44*/@Beanpublic FilterRegistrationBean CasSingleSignOutFilter() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();//配置攔截器參數(shù)mapMap<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@Beanpublic ServletListenerRegistrationBean<org.jasig.cas.client.session.SingleSignOutHttpSessionListener> casListener() {return new ServletListenerRegistrationBean<>(new org.jasig.cas.client.session.SingleSignOutHttpSessionListener());}/** @Description:CAS認(rèn)證filter casServerLoginUrl參數(shù):表示CAS Server登錄URL,后面追加appResId參數(shù),表明應(yīng)用類型(公文系統(tǒng)暫時(shí)使用GONGWEN,備案系統(tǒng)使用BHXT)。service參數(shù):表示在通過(guò)CAS Server認(rèn)證后的返回頁(yè)面。 localLoginUrl參數(shù):本地登錄URL。 renew參數(shù):請(qǐng)不要修改。whiteList參數(shù):不進(jìn)行認(rèn)證檢查的URI,使用分號(hào)進(jìn)行分割。如果以/為結(jié)尾,則表示該路徑下的所有URI均不進(jìn)行認(rèn)證檢查。* @Param:[]* @Return: org.springframework.boot.web.servlet.FilterRegistrationBean* @Throws:* @Author: wangwei* @Date:2020/3/27 11:10*/@Beanpublic FilterRegistrationBean CasAuthenticationFilter() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();//配置攔截器參數(shù)mapMap<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驗(yàn)證filter serverName參數(shù):應(yīng)用根路徑。 CAS Http請(qǐng)求Wrapper filter:在通過(guò)CAS認(rèn)證或驗(yàn)證通過(guò)后,將user id賦值到request中remoteUser中* @Param:[]* @Return: org.springframework.boot.web.servlet.FilterRegistrationBean* @Throws:* @Author: wangwei* @Date:2020/3/27 11:10*/@Beanpublic FilterRegistrationBean CasValidationFilter() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();//配置攔截器參數(shù)mapMap<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;}@Beanpublic FilterRegistrationBean CasHttpServletRequestFilter() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();//配置攔截器參數(shù)mapHttpServletRequestWrapperFilter casHttpServletRequestFilter = new HttpServletRequestWrapperFilter();filterRegistrationBean.setFilter(casHttpServletRequestFilter);String url = "/*";filterRegistrationBean.addUrlPatterns(url);filterRegistrationBean.setName("casHttpServletRequestFilter");filterRegistrationBean.setOrder(4);return filterRegistrationBean;}}4、CustomCas30ProxyReceivingTicketValidationFilter過(guò)濾器
public class CustomCas30ProxyReceivingTicketValidationFilter extends Cas10TicketValidationFilter {/** @Description:校驗(yàn)成功后設(shè)置sesion* @Param:[request, response, assertion]* @Return: void* @Throws:* @Author: wangwei* @Date:2020/3/29 10:40*/@Overrideprotected 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 復(fù)寫(xiě)認(rèn)證過(guò)濾器,添加了白名單,跳過(guò)cas認(rèn)證的條件
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() {}@Overrideprotected 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;}@Overridepublic void init() {super.init();CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");}@Overridepublic 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());//獲取參數(shù)String headerFlag = request.getHeader("flagCas");String urlFlag = request.getParameter("flagCas");//判斷是否為白名單if (this.isSkipCheck(uri, contextPath)) {filterChain.doFilter(request, response);//根據(jù)參數(shù)判斷是否需要cas認(rèn)證} 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;} }二、結(jié)合實(shí)現(xiàn),以及流程圖,分析客戶端源碼,請(qǐng)參考CasFilter文件
1、WhiteListJwtAndAuthenticationFilter過(guò)濾器(這是復(fù)寫(xiě)了jar包中AuthenticationFilter過(guò)濾器,主要的改造的是添加了白名單,添加了跳過(guò)cas單點(diǎn)認(rèn)證方式)
@Overridepublic 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);//判斷有沒(méi)有sessionAssertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;//session有數(shù)據(jù)說(shuō)明本地已經(jīng)登錄了,直接跳轉(zhuǎn)路徑if (assertion != null) {filterChain.doFilter(request, response);} else {/*否則用戶沒(méi)有登錄1、判斷是不是白名單路徑,如果是白名單路徑放行2、判斷是否關(guān)閉單點(diǎn)登錄驗(yàn)證,這樣用戶就可以使用自己的登錄認(rèn)證方式,這樣就既可以使用單點(diǎn)登錄,也可以使用本地登錄3、如果上面兩個(gè)條件都不滿足3.1判斷用戶是戶是否有token值,如果有放行(有token值說(shuō)明用戶已經(jīng)經(jīng)過(guò)cas服務(wù)認(rèn)證登錄了,并且返回了token值)3.2如果沒(méi)有token值重定向cas認(rèn)證登錄*/String contextPath = request.getContextPath();String uri = request.getRequestURI().substring(contextPath.length(),request.getRequestURI().length());//獲取參數(shù)String headerFlag = request.getHeader("flagCas");String urlFlag = request.getParameter("flagCas");//判斷是否為白名單if (this.isSkipCheck(uri, contextPath)) {filterChain.doFilter(request, response);//根據(jù)參數(shù)判斷是否需要cas認(rèn)證} 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過(guò)濾繼承的AbstractTicketValidationFilter過(guò)濾器(登錄成功了返回token,驗(yàn)證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());/*重點(diǎn):***********************************************************這個(gè)攔截器只攔截帶有token值的路徑,這樣就實(shí)現(xiàn)了只在用戶登錄成功后返回token值驗(yàn)證token值有效后,不會(huì)每一個(gè)請(qǐng)求都驗(yàn)證token的有效性,這樣安全性得到了保證,也不會(huì)頻繁訪問(wèn)cas服務(wù)端驗(yàn)證有效性重點(diǎn):************************************************************/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,單點(diǎn)登錄注銷過(guò)濾器
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);}總結(jié)