0


MaxKey 单点登录认证系统——实现登录后自动跳转及分析思路

Maxkey单点登录系统集成业务系统应用之后,登录界面登录之后不会自动跳转业务系统,需要在首页点击相应应用之后,才能实现跳转业务系统,故以下本人提供解决方法和分析思路。

环境配置

本例使用的是CAS协议实现单点登录

Maxkey 服务端

认证服务器地址端口:9527

前段登录界面地址:http://localhost:8527/maxkey/#/passport/login

业务系统

server:port:8989cas:server-url-prefix: http://localhost:9527/sign/authz/cas/ # 认证地址server-login-url: http://localhost:8527/maxkey/#/passport/login #登录地址client-host-url: http://localhost:8989#客户端地址# 认证方式,默认casvalidation-type: cas3
  # CAS拦截的URL地址authentication-url-patterns:- /casTest/user

maxkey配置地址如下:

在这里插入图片描述

CAS 原理简单分析

未登录时流程分析

  1. 访问地址 http://localhost:8989/casTest/user
  2. AbstractTicketValidationFilter 过滤器拦截请求publicfinalvoiddoFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,ServletException{if(this.preFilter(servletRequest, servletResponse, filterChain)){HttpServletRequest request =(HttpServletRequest)servletRequest;HttpServletResponse response =(HttpServletResponse)servletResponse;//获取请求中的 ticketString ticket =this.retrieveTicketFromRequest(request);if(CommonUtils.isNotBlank(ticket)){//...}//无 ticket则放行进入下一个过滤器 filterChain.doFilter(request, response);}}
  3. AuthenticationFilter 过滤器拦截请求publicfinalvoiddoFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,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);//重定向之后的请求 session中拿出以上过滤器存入的 assertionAssertion assertion = session !=null?(Assertion)session.getAttribute("_const_cas_assertion_"):null;if(assertion !=null){ filterChain.doFilter(request, response);}else{//无 assertion说明之前过滤器无 ticket校验String serviceUrl =this.constructServiceUrl(request, response);String ticket =this.retrieveTicketFromRequest(request);boolean wasGatewayed =this.gateway &&this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);if(!CommonUtils.isNotBlank(ticket)&&!wasGatewayed){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);}}}}

登录后流程分析

  1. 访问地址 http://localhost:8989/casTest/user
  2. AbstractTicketValidationFilter 过滤器拦截请求publicfinalvoiddoFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,ServletException{if(this.preFilter(servletRequest, servletResponse, filterChain)){HttpServletRequest request =(HttpServletRequest)servletRequest;HttpServletResponse response =(HttpServletResponse)servletResponse;//获取请求中的ticketString ticket =this.retrieveTicketFromRequest(request);if(CommonUtils.isNotBlank(ticket)){this.logger.debug("Attempting to validate ticket: {}", ticket);try{//有ticket时校验并返回 assertion Assertion assertion =this.ticketValidator.validate(ticket,this.constructServiceUrl(request, response));this.logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName()); request.setAttribute("_const_cas_assertion_", assertion);if(this.useSession){//session中存入 assertion request.getSession().setAttribute("_const_cas_assertion_", assertion);}this.onSuccessfulValidation(request, response, assertion);if(this.redirectAfterValidation){this.logger.debug("Redirecting after successful ticket validation.");//请求之后加上jsessionid并重定向请求 response.sendRedirect(this.constructServiceUrl(request, response));return;}}catch(TicketValidationException var8){this.logger.debug(var8.getMessage(), var8);this.onFailedValidation(request, response);if(this.exceptionOnValidationFailure){thrownewServletException(var8);} response.sendError(403, var8.getMessage());return;}} filterChain.doFilter(request, response);}}
  3. AuthenticationFilter 过滤器拦截请求publicfinalvoiddoFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,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);//重定向之后的请求 session中拿出以上过滤器存入的 assertionAssertion assertion = session !=null?(Assertion)session.getAttribute("_const_cas_assertion_"):null;if(assertion !=null){//如果存在 assertion说明上个过滤器校验ticket通过,请求放行 filterChain.doFilter(request, response);}else{//...}}}

存在问题

登录之后如果不先点击一次首页应用跳转,而是直接地址栏输入 http://localhost:8989/casTest/user 跳转,还是会跳转到登录界面,无法实现登录之后直接跳转应用业务界面

解决思路和步骤

  1. 通过以上 CAS原理分析在以下过滤器位置打个断点,发现 ticket为空在这里插入图片描述
  2. 而如果通过点击首页应用进行第一次跳转请求是会携带 ticket的在这里插入图片描述
  3. 说明问题就出在 ticket,所以就得搞清楚ticket是在什么时候设置的
  4. 通过截图发现点击应用时会调用跳转以下地址在这里插入图片描述
  5. 但是该地址通过项目搜索栏搜索却搜不到,所以想别的方法
  6. 接着想到点击应用就可以跳转到相应的应用业务系统,而maxkey和应用的唯一关联的地方就是以下设置的回调地址,说明这个回调地址肯定在服务器中哪个地方有使用来进行跳转在这里插入图片描述
  7. 通过查看源码发现这回调地址放在一个 callbackUrl的变量里在这里插入图片描述
  8. 接着我们查找整个项目发现 callbackUrl会频繁出现在 CasAuthorizeEndpoint.java 类里在这里插入图片描述
  9. 点进去发现以下接口,即 /authz/cas/应用id 请求接口,该接口会获取应用 callbackUrl并重定向到 /authz/cas/granting 请求地址,这就与以上的接口对应上了,说明我们分析我找对位置了@Operation(summary ="CAS页面跳转应用ID认证接口", description ="传递参数应用ID",method="GET")@GetMapping(CasConstants.ENDPOINT.ENDPOINT_BASE+"/{id}")//ENDPOINT_BASE:authz/caspublicModelAndViewauthorize(@PathVariable("id")String id,HttpServletRequest request,HttpServletResponse response ){AppsCasDetails casDetails = casDetailsService.getAppDetails(id ,true);returnbuildCasModelAndView(request,response,casDetails,casDetails ==null? id : casDetails.getCallbackUrl());}privateModelAndViewbuildCasModelAndView(HttpServletRequest request,HttpServletResponse response,AppsCasDetails casDetails,String casService){//...省略//ENDPOINT_SERVICE_TICKET_GRANTING:"authz/cas/granting"ModelAndView redirect =WebContext.redirect(CasConstants.ENDPOINT.ENDPOINT_SERVICE_TICKET_GRANTING);return redirect;}
  10. 接着发现该类还有一个接口,这个接口就是 /authz/cas/granting 接口,该接口进行的操作就是获取 ticket并且设置 ticket,那么我们登录完成之后只要也跟这部分功能一样有获取 ticket并设置的操作就能实现自动跳转@RequestMapping(CasConstants.ENDPOINT.ENDPOINT_SERVICE_TICKET_GRANTING)// /authz/cas/grantingpublicModelAndViewgrantingTicket(Principal principal,HttpServletRequest request,HttpServletResponse response){ModelAndView modelAndView =newModelAndView("authorize/cas_sso_submint");AppsCasDetails casDetails =(AppsCasDetails)WebContext.getAttribute(CasConstants.PARAMETER.ENDPOINT_CAS_DETAILS);ServiceTicketImpl serviceTicket =newServiceTicketImpl(AuthorizationUtils.getAuthentication(),casDetails); _logger.trace("CAS start create ticket ... ");//获取 ticketString ticket = ticketServices.createTicket(serviceTicket,casDetails.getExpires()); _logger.trace("CAS ticket {} created . ", ticket);StringBuffer callbackUrl =newStringBuffer(casDetails.getCallbackUrl());if(casDetails.getCallbackUrl().indexOf("?")==-1){ callbackUrl.append("?");}if(callbackUrl.indexOf("&")!=-1||callbackUrl.indexOf("=")!=-1){ callbackUrl.append("&");}//append ticket 设置ticket callbackUrl.append(CasConstants.PARAMETER.TICKET).append("=").append(ticket); callbackUrl.append("&");//append service callbackUrl.append(CasConstants.PARAMETER.SERVICE).append("=").append(casDetails.getService());//...//重定向到应用业务系统 _logger.debug("redirect to CAS Client URL {}", callbackUrl); modelAndView.addObject("callbackUrl", callbackUrl.toString());return modelAndView;}
  11. 在该类中有以下两个接口,点击应用时通过第二个接口来生成 ticket并设置的,而第一个接口为 /authz/cas/login?service=xxx 来实现的,那是不是我们在登录完成之后请求该接口就可实现自动转发@Operation(summary ="CAS页面跳转service认证接口", description ="传递参数service",method="GET")@GetMapping(CasConstants.ENDPOINT.ENDPOINT_LOGIN)publicModelAndViewcasLogin(@RequestParam(value=CasConstants.PARAMETER.SERVICE,required=false)String casService,HttpServletRequest request,HttpServletResponse response ){AppsCasDetails casDetails = casDetailsService.getAppDetails(casService ,true);returnbuildCasModelAndView(request,response,casDetails,casService);}@Operation(summary ="CAS页面跳转应用ID认证接口", description ="传递参数应用ID",method="GET")@GetMapping(CasConstants.ENDPOINT.ENDPOINT_BASE+"/{id}")publicModelAndViewauthorize(@PathVariable("id")String id,HttpServletRequest request,HttpServletResponse response ){AppsCasDetails casDetails = casDetailsService.getAppDetails(id ,true);returnbuildCasModelAndView(request,response,casDetails,casDetails ==null? id : casDetails.getCallbackUrl());}
  12. 根据以上思路我们在登录完成之后添加以上请求地址测试一下navigate(authJwt:any){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);}let service =this.getService('service');//添加请求地址this.redirect_uri ='http://localhost:9527/sign/authz/cas/login?service=http://localhost:8989/casTest/user';if(this.redirect_uri !=''){console.log(`redirect_uri ${this.redirect_uri}`); location.href =this.redirect_uri;}this.router.navigateByUrl(url);});}
  13. 发现登录之后实现了跳转故我们就可以在以上方法中添加 this.redirect_uri 来实现登录后自动跳转,至此分析结束
  14. 因为访问业务系统时未登录会跳转回登录界面,跳转地址为:http://localhost:8527/maxkey/#/passport/login?service=http:%2F%2Flocalhost:8989%2FcasTest2%2Fuser2我们可以通过获取地址栏的serive来拼接到 this.redirect_uri后,这样就可以兼容多应用了,记得把 %2F 转换为 /
标签: 学习 java

本文转载自: https://blog.csdn.net/2301_78055266/article/details/135576065
版权归原作者 努力的Ethan 所有, 如有侵权,请联系我们删除。

“MaxKey 单点登录认证系统——实现登录后自动跳转及分析思路”的评论:

还没有评论