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

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

环境配置

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

Maxkey 服务端

认证服务器地址端口:9527

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

业务系统

server:
  port: 8989
cas:
  server-url-prefix: http://localhost:9527/sign/authz/cas/ # 认证地址
  server-login-url: http://localhost:8527/maxkey/#/passport/login #登录地址
  client-host-url: http://localhost:8989 #客户端地址

  # 认证方式,默认cas
  validation-type: cas3
  # CAS拦截的URL地址
  authentication-url-patterns:
    - /casTest/user

maxkey配置地址如下:

在这里插入图片描述

CAS 原理简单分析

未登录时流程分析

  1. 访问地址 http://localhost:8989/casTest/user

  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;
    			//获取请求中的 ticket
                String ticket = this.retrieveTicketFromRequest(request);
                if (CommonUtils.isNotBlank(ticket)) {
    				//...
                }
    			//无 ticket则放行进入下一个过滤器
                filterChain.doFilter(request, response);
            }
        }
    
  3. 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);
    			//重定向之后的请求 session中拿出以上过滤器存入的 assertion
                Assertion 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 过滤器拦截请求

    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;
    			//获取请求中的ticket
                String 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) {
                            throw new ServletException(var8);
                        }
    
                        response.sendError(403, var8.getMessage());
                        return;
                    }
                }
                filterChain.doFilter(request, response);
            }
        }
    
  3. 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);
    			//重定向之后的请求 session中拿出以上过滤器存入的 assertion
                Assertion 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/cas
    public ModelAndView authorize(  @PathVariable("id") String id,
    								HttpServletRequest request,
    								HttpServletResponse response
    		){
    
    	AppsCasDetails casDetails = casDetailsService.getAppDetails(id , true);
    
    	return buildCasModelAndView(request,response,casDetails,casDetails == null ? id : casDetails.getCallbackUrl());
    }
    
    private  ModelAndView buildCasModelAndView( 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/granting
    public ModelAndView grantingTicket( Principal principal,
    									HttpServletRequest request,
    									HttpServletResponse response){
    	ModelAndView modelAndView = new ModelAndView("authorize/cas_sso_submint");
    	AppsCasDetails casDetails = (AppsCasDetails)WebContext.getAttribute(CasConstants.PARAMETER.ENDPOINT_CAS_DETAILS);
    
    	ServiceTicketImpl serviceTicket = new ServiceTicketImpl(AuthorizationUtils.getAuthentication(),casDetails);
    
    	_logger.trace("CAS start create ticket ... ");
    	//获取 ticket
    	String ticket = ticketServices.createTicket(serviceTicket,casDetails.getExpires());
    	_logger.trace("CAS ticket {} created . " , ticket);
    
    	StringBuffer callbackUrl = new StringBuffer(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)
    public ModelAndView casLogin(@RequestParam(value=CasConstants.PARAMETER.SERVICE,required=false) String casService,
    							 HttpServletRequest request,
    							 HttpServletResponse response
    		){
      
    	AppsCasDetails  casDetails = casDetailsService.getAppDetails(casService , true);
    
    	return buildCasModelAndView(request,response,casDetails,casService);
    }
    
    @Operation(summary = "CAS页面跳转应用ID认证接口", description = "传递参数应用ID",method="GET")
    @GetMapping(CasConstants.ENDPOINT.ENDPOINT_BASE + "/{id}")
    public ModelAndView authorize(  @PathVariable("id") String id,
    								HttpServletRequest request,
    								HttpServletResponse response
    		){
    
    	AppsCasDetails casDetails = casDetailsService.getAppDetails(id , true);
    
    	return buildCasModelAndView(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 转换为 /

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/318213.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

优化 - 重构一次Mysql导致服务器的OOM

概述 优化了一次前后端处理不当导致的CPU的一次爆机行为,当然,这和服务器的配置低也有着密不可分的关系,简单的逻辑学告诉我们,要找到真正的问题,进行解决,CPU爆机的关键点在于前后端两个方面,…

POI-tl 知识整理:整理1 -> 利用模板向word中写入数据

1 文本传值 Testpublic void testText() throws Exception {XWPFTemplate template XWPFTemplate.compile("D:\\Idea-projects\\POI_word\\templates.docx");Map<String, Object> map new HashMap<>();map.put("title", "Hi, girl"…

如何在你的网站接入QQ登录?

文章目录 准备阶段申请QQ登录的权限创建应用最后上传qqlogin.php代码 准备阶段 国内服务器和备案域名需要你有张独一无二本人的身份证你正面手持身份证的图片一张100px*100px的网站图标 申请QQ登录的权限 首先访问qq互联&#xff0c;点击我直接访问 登陆完成后我们点击面的…

橘子学Spring01之spring的那些工厂和门面使用

一、Spring的工厂体系 我们先来说一下spring的工厂体系(也称之为容器)&#xff0c;得益于大佬们对于单一职责模式的坚决贯彻&#xff0c;在十几年以来spring的发展路上&#xff0c;扩展出来大量的工厂类&#xff0c;每一个工厂类都承担着自己的功能(其实就是有对应的方法实现)…

(三)CMake为什么几乎一统C++跨平台构建?

先看几个简单的例子再回头来看这个问题 回想一下当我们用windows写C第一个Hello World!的步骤&#xff0c;先用VS IDE 创建一个控制台的工程&#xff0c;IDE 会自动生成一个 cpp 文件&#xff0c;里面有一句 输出"Hello World!" 代码&#xff0c;这个时候按下F5 就可…

PTA 1117 数字之王 C++实现 简易代码

给定两个正整数 N1​<N2​。把从 N1​ 到 N2​ 的每个数的各位数的立方相乘&#xff0c;再将结果的各位数求和&#xff0c;得到一批新的数字&#xff0c;再对这批新的数字重复上述操作&#xff0c;直到所有数字都是 1 位数为止。这时哪个数字最多&#xff0c;哪个就是“数字…

在线直线度测量仪确保了出厂圆棒无不合格品

在线直线度测量仪确保了出厂圆棒无不合格品 随着生产设备的改进&#xff0c;利用基础材料进行生产的厂家对品质要求也越来越高&#xff0c;其中圆形棒管材的直线度尺寸&#xff0c;也是广受关注&#xff0c;对其进行矫直检测&#xff0c;使其出厂无不合格品。 变抽检为全检 以前…

逼格满满,推荐一个高效测试用例工具:XMind2TestCase !

一、背景 软件测试的核心是什么&#xff1f;毫无疑问是测试分析和测试用例设计&#xff0c;也是日常测试投入最多时间的工作内容之一。 然而&#xff0c;传统的测试用例设计过程有很多痛点&#xff1a; 1、使用Excel表格进行测试用例设计&#xff0c;虽然成本低&#xff0c;但…

Java 并发性和多线程3

七、线程安全及不可变性 当多个线程同时访问同一个资源&#xff0c;并且其中的一个或者多个线程对这个资源进行了写操作&#xff0c;才会产生竞态条件。多个线程同时读同一个资源不会产生竞态条件。 我们可以通过创建不可变的共享对象来保证对象在线程间共享时不会被修改&…

Github 2FA验证的解决方法

当前使用GitHub需要启用 2FA 验证&#xff0c;也就是除了账号密码外还有一个实时码&#xff0c;需要额外输入这个正确的实时码才能开启 2FA 验证和后续登陆。 浏览器插件 这是目前我在使用的方法。在浏览器中添加一个叫做Authenticator的插件&#xff0c;传送地址&#xff1a;…

MySQL 和 Redis 如何保证数据一致性,通过MySQL的binlog实现

1、简介 MySQL 和 Redis 如何保证数据一致性&#xff0c;目前大多讨论的是先更新Redis后更新MySQL&#xff0c;还是先更新MySQL 后更新Redis&#xff0c;这两种方式在实际的应用场景中都不能确保数据的完全一致性&#xff0c;在某些情况下会出现问题&#xff0c;本文介绍使用 C…

京东宣布启动鸿蒙原生应用开发,全力支持鸿蒙生态 | 百能云芯

华为常务董事、终端BG CEO、智能汽车解决方案BU董事长余承东于1月10日在微博上发布了一条令人振奋的消息&#xff1a;京东即将启动鸿蒙原生应用的开发。这一消息在科技圈掀起了不小的波澜&#xff0c;也为鸿蒙系统的发展注入了新的动力。 京东集团首席执行官兼执行董事许冉和余…

RPN(Region Proposal Networks)候选区域网络算法解析(附PyTorch代码)

0. 前言 按照国际惯例&#xff0c;首先声明&#xff1a;本文只是我自己学习的理解&#xff0c;虽然参考了他人的宝贵见解及成果&#xff0c;但是内容可能存在不准确的地方。如果发现文中错误&#xff0c;希望批评指正&#xff0c;共同进步。 本文基于论文Faster R-CNN: Towards…

大一统的监控探针采集器 cprobe

需求背景 监控数据采集领域&#xff0c;比如 Prometheus 生态有非常多的 Exporter&#xff0c;虽然生态繁荣&#xff0c;但是无法达到开箱即用的大一统体验&#xff0c;Exporter 体系的核心问题有&#xff1a; 良莠不齐&#xff1a;有的 Exporter 写的非常棒&#xff0c;有的…

uniapp怎么开发插件并发布

今天耳机坏了,暂时内卷不了,所以想开发几个插件玩玩,也好久没写博客了,就拿这个来写了 首先,发布插件时需要你有项目 这里先拿uniapp创建一个项目, 如下,创建好的项目长这样 然后根据uniapp官网上说的,我们发布插件时,需要在uni_modules里面编写和发布 ps:还需要使用uniapp…

计算机组成原理期末复习

文章目录 第一章&#xff1a;计算机系统漫游编译系统进程线程之间的关系存储器层次结构虚拟地址 第二章&#xff1a;信息的表示和处理大端与小端整数运算浮点数运算 第三章&#xff1a;程序的机器级表示栈的压入和弹出算数与逻辑运算操作指令条件判断与循环 第六章&#xff1a;…

U盘抜太快打不开恢复方法

U盘是一种常用的存储设备&#xff0c;由于其便携性和大容量等特点&#xff0c;被广泛应用于数据存储和传输。然而&#xff0c;有时候我们会遇到U盘拔出后无法再次使用的问题&#xff0c;这通常是由于U盘拔出太快导致的。本文将深入探讨U盘拔太快打不开的原因&#xff0c;并提供…

【elfboard linux 开发板】10. 设备树与烧录

1. 设备树介绍 设备树由一系列被命名的node和property组成 可以描述如下信息&#xff1a; CPU的数量和类别内存基地址和大小总线和桥外设连接中断控制器和中断使用情况GPIO控制器和GPIO使用情况Clock 控制器和 Clock 使用情况 由dts文件以文本方式对系统设备树进行描述&…

GPT-4V的图片识别和分析能力原创

GPT-4V是OpenAI开发的大型语言模型&#xff0c;是GPT-4的升级版本。GPT-4V在以下几个方面进行了改进&#xff1a; 模型规模更大&#xff1a;GPT-4V的参数量达到了1.37T&#xff0c;是GPT-4的10倍。 训练数据更丰富&#xff1a;GPT-4V的训练数据包括了1.56T的文本和代码数据。 …

Ubuntu 22.04 安装prometheus

服务器监控和报警软件有很多&#xff0c;为什么我们会选择Prometheus而不是其他软件呢&#xff1f; 因为它有以下优点&#xff1a; 自带简易web监控页面&#xff0c;用户可以很方便地查看监控数据和使用仪表盘。能实时收集数据并根据自定义警报规则推送告警&#xff1b;具有丰…