客户端依赖包
<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);//校验请求中是否有ticketif (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_值,再次判断有无 ticketif (!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
拦截@Overridepublic 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};//tokenthis.cookieService.set(CONSTS.CONGRESS, authJwt.token, { path: '/' });//ticketthis.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
@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {logger.trace("Single Sign On Interceptor");//根据请求携带的信息设置 current_authenticationAuthorizationUtils.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_属性值,所以也可以直接校验通过