MaxKey 单点登录认证系统——登录验证流程分析

客户端依赖包

<dependency>
     <groupId>net.unicon.casgroupId>
     <artifactId>cas-client-autoconfig-supportartifactId>
     <version>2.3.0-GAversion>
dependency>

未登录时

  1. 浏览器向客户端发送请求 http://localhost:8989/test1/index

  2. 客户端: 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);
            }
        }
    
  3. 客户端:重定向之后的请求是没有 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);
                    }
                }
            }
        }
    
  4. 服务器端:接收到重定向请求后被拦截器 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,记得配置好

    MaxKey 单点登录认证系统——登录验证流程分析_第1张图片

  5. 重定向登录界面后输入账号密码登录,登录成功成功后返回jwt信息

  6. 前端处理响应信息,设置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;
      }
    
  7. 如果地址后面有拼接 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);
        });
      }
    
  8. 服务端:服务器接受重定向后的地址请求,并由 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;
        }
    
  9. 放行之后,再经过一系列操作跳转 http://localhost:8989/test1/index界面

成功登录后

  1. 成功登录后再次请求 http://localhost:8989/test1/index地址

  2. 此时请求中含有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);
            }
        }
    
  3. 后续请求直接判断请求中是否有 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);
                } 
    			//...
            }
        }
    
  4. 此时登录别的应用地址,由于服务器session中已经设置 _const_cas_assertion_属性值,所以也可以直接校验通过

你可能感兴趣的:(MaxKey,单点登录认证系统,java)