客户端依赖包
<dependency>
<groupId>net.unicon.cas</groupId>
<artifactId>cas-client-autoconfig-support</artifactId>
<version>2.3.0-GA</version>
</dependency>
未登录时
-
浏览器向客户端发送请求
http://localhost:8989/test1/index
-
客户端:
AbstractTicketValidationFilter
过滤器拦截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 = this.retrieveTicketFromRequest(request); //校验请求中是否有ticket if (CommonUtils.isNotBlank(ticket)) { this.logger.debug("Attempting to validate ticket: {}", ticket); try { //校验ticket是否正确 Assertion assertion = this.ticketValidator.validate(ticket, this.constructServiceUrl(request, response)); this.logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName()); //校验成功设置 _const_cas_assertion_属性 request.setAttribute("_const_cas_assertion_", assertion); if (this.useSession) { request.getSession().setAttribute("_const_cas_assertion_", assertion); } this.onSuccessfulValidation(request, response, assertion); if (this.redirectAfterValidation) { //重定向请求 this.logger.debug("Redirecting after successful ticket validation."); response.sendRedirect(this.constructServiceUrl(request, response)); return; } } catch (TicketValidationException var8) { //校验失败抛出异常 this.logger.debug(var8.getMessage(), var8); this.onFailedValidation(request, response); if (this.exceptionOnValidationFailure) { throw new ServletException(var8); } response.sendError(403, var8.getMessage()); return; } } //没有ticket直接放行 filterChain.doFilter(request, response); } }
-
客户端:重定向之后的请求是没有 ticket的,所以经过上述的过滤器会直接放行,放行后来到下一个过滤器
AuthenticationFilter
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; //判断请求需不需要拦截 if (this.isRequestUrlExcluded(request)) { this.logger.debug("Request is ignored."); filterChain.doFilter(request, response); } else { HttpSession session = request.getSession(false); Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null; //获取请求中的 _const_cas_assertion_值 if (assertion != null) { //如果以上过滤器检验通过此时是有值得,直接放行请求 filterChain.doFilter(request, response); } else { String serviceUrl = this.constructServiceUrl(request, response); String ticket = this.retrieveTicketFromRequest(request); boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl); //无 _const_cas_assertion_值,再次判断有无 ticket if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) { //也没有ticket重定向到配置文件配置的服务器 this.logger.debug("no ticket and no assertion found"); String modifiedServiceUrl; if (this.gateway) { this.logger.debug("setting gateway attribute in session"); modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl); } else { modifiedServiceUrl = serviceUrl; } this.logger.debug("Constructed service url: {}", modifiedServiceUrl); //获取重定向请求 String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway); this.logger.debug("redirecting to \"{}\"", urlToRedirectTo); //重定向发送 this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo); } else { filterChain.doFilter(request, response); } } } }
-
服务器端:接收到重定向请求后被拦截器
SingleSignOnlnterceptor
拦截@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.trace("Single Sign On Interceptor"); AuthorizationUtils.authenticateWithCookie(request,authTokenService,sessionManager); //判断请求是否有current_authentication值 if(AuthorizationUtils.isNotAuthenticated()) { //没有则重定向 `/sign/static/index.html/#/passport/login?redirect_uri=http://localhost:9527/sign/authz/cas/login?service=http%3A%2F%2Flocalhost%3A8989%2Ftest1%2Findex` String loginUrl = applicationConfig.getFrontendUri() + "/index.html/#/passport/login?redirect_uri=%s"; String redirect_uri = UrlUtils.buildFullRequestUrl(request); String base64RequestUrl = Base64Utils.base64UrlEncode(redirect_uri.getBytes()); logger.debug("No Authentication ... Redirect to /passport/login , redirect_uri {} , base64 {}", redirect_uri ,base64RequestUrl); response.sendRedirect(String.format(loginUrl,base64RequestUrl)); return false; } //... }
注意:
applicationConfig.getFrontendUri()
获取的是配置文件的maxkey.server.frontend.uri
,记得配置好 -
重定向登录界面后输入账号密码登录,登录成功成功后返回jwt信息
-
前端处理响应信息,设置token和ticket等信息
auth(authJwt: any) { let user: User = { name: `${authJwt.displayName}(${authJwt.username})`, displayName: authJwt.displayName, username: authJwt.username, userId: authJwt.id, avatar: './assets/img/avatar.svg', email: authJwt.email, passwordSetType: authJwt.passwordSetType }; //token this.cookieService.set(CONSTS.CONGRESS, authJwt.token, { path: '/' }); //ticket this.cookieService.set(CONSTS.ONLINE_TICKET, authJwt.ticket, { domain: this.getSubHostName(), path: '/' }); if (authJwt.remeberMe) { localStorage.setItem(CONSTS.REMEMBER, authJwt.remeberMe); } this.settingsService.setUser(user); this.tokenService.set(authJwt); this.tokenService.get()?.expired; }
-
如果地址后面有拼接
redirect_uri
,则会重定向到拼接的地址,如上述的http://localhost:9527/sign/authz/cas/login?service=http%3A%2F%2Flocalhost%3A8989%2Ftest1%2Findex
navigate(authJwt: any) { // 重新获取 StartupService 内容,我们始终认为应用信息一般都会受当前用户授权范围而影响 this.startupService.load().subscribe(() => { let url = this.tokenService.referrer!.url || '/'; if (url.includes('/passport')) { url = '/'; } if (localStorage.getItem(CONSTS.REDIRECT_URI) != null) { this.redirect_uri = `${localStorage.getItem(CONSTS.REDIRECT_URI)}`; localStorage.removeItem(CONSTS.REDIRECT_URI); } if (this.redirect_uri != '') { console.log(`redirect_uri ${this.redirect_uri}`); //重定向 location.href = this.redirect_uri; } this.router.navigateByUrl(url); }); }
-
服务端:服务器接受重定向后的地址请求,并由
SingleSignOnInterceptor
再次拦截,相比未登录的,此时的请求携带了一些登录后设置的jwt信息,就可以设置current_authentication
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.trace("Single Sign On Interceptor"); //根据请求携带的信息设置 current_authentication AuthorizationUtils.authenticateWithCookie(request,authTokenService,sessionManager); if(AuthorizationUtils.isNotAuthenticated()) { //... } //判断请求是否有 current_authentication值 if(AuthorizationUtils.isAuthenticated()){ logger.debug("preHandle {}",request.getRequestURI()); Apps app = (Apps)WebContext.getAttribute(WebConstants.AUTHORIZE_SIGN_ON_APP); if(app == null) { String requestURI = request.getRequestURI(); if(requestURI.contains("/authz/cas/login")) {//for CAS service //获取`service`后面的值,即 http://localhost:8989/test1/index,并根据此service查询配置的应用信息 app = casDetailsService.getAppDetails( request.getParameter(CasConstants.PARAMETER.SERVICE), true); } //... } if(app == null) { logger.debug("preHandle app is not exist . "); return true; } SignPrincipal principal = AuthorizationUtils.getPrincipal(); if(principal != null && app !=null) { //判断是否有权限访问应用,有则放行 if(principal.getGrantedAuthorityApps().contains(new SimpleGrantedAuthority(app.getId()))) { logger.trace("preHandle have authority access {}" , app); return true; } } logger.debug("preHandle not have authority access {}" , app); response.sendRedirect(request.getContextPath()+"/authz/refused"); return false; } return true; }
-
放行之后,再经过一系列操作跳转 http://localhost:8989/test1/index界面
成功登录后
-
成功登录后再次请求 http://localhost:8989/test1/index地址
-
此时请求中含有ticket,而客户端校验ticket没问题后会设置
_const_cas_assertion_
属性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 = this.retrieveTicketFromRequest(request); if (CommonUtils.isNotBlank(ticket)) { this.logger.debug("Attempting to validate ticket: {}", ticket); try { Assertion assertion = this.ticketValidator.validate(ticket, this.constructServiceUrl(request, response)); this.logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName()); //在服务器 session中设置 _const_cas_assertion_属性 request.setAttribute("_const_cas_assertion_", assertion); if (this.useSession) { request.getSession().setAttribute("_const_cas_assertion_", assertion); } this.onSuccessfulValidation(request, response, assertion); if (this.redirectAfterValidation) { this.logger.debug("Redirecting after successful ticket validation."); response.sendRedirect(this.constructServiceUrl(request, response)); return; } } catch (TicketValidationException var8) { this.logger.debug(var8.getMessage(), var8); this.onFailedValidation(request, response); if (this.exceptionOnValidationFailure) { throw new ServletException(var8); } response.sendError(403, var8.getMessage()); return; } } filterChain.doFilter(request, response); } }
-
后续请求直接判断请求中是否有
const_cas_assertion
属性,有就说明登录过了,请求放行public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; if (this.isRequestUrlExcluded(request)) { this.logger.debug("Request is ignored."); filterChain.doFilter(request, response); } else { HttpSession session = request.getSession(false); Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null; if (assertion != null) { //不为空请求放行 filterChain.doFilter(request, response); } //... } }
-
此时登录别的应用地址,由于服务器session中已经设置 _const_cas_assertion_属性值,所以也可以直接校验通过