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 #客户端地址# 认证方式,默认casvalidation-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;//获取请求中的 ticketString 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中拿出以上过滤器存入的 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 过滤器拦截请求

    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;//获取请求中的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中存入 assertionrequest.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中拿出以上过滤器存入的 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/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 ... ");//获取 ticketString 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 设置ticketcallbackUrl.append(CasConstants.PARAMETER.TICKET).append("=").append(ticket);callbackUrl.append("&");//append servicecallbackUrl.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 转换为 /

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业如何做到安全又极速的分发传输大文件

在当代企业运营中,文件的传输和分发是至关重要的任务。然而,随着文件体积的增大和信息敏感性的凸显,企业需要找到一种既安全又能够高效传输大文件的方法。本文将深入探讨如何在企业环境中实现安全又高效的大文件传输。 一、分发大文件时需要注…

day-08 构造限制重复的字符串

思路 首先统计每个字符的个数&#xff0c;然后从后向前按照题意添加字符 解题方法 从后向前添加字符&#xff1a;1.当前字符个数<repeatLimit,直接添加 2.当前字符个数>repeatLimit,添加repeatLimit个&#xff0c;然后插入一个下一级字符 时间复杂度:O(n) 空间复杂度:…

在CentOS中,对静态HTTP服务的性能监控

在CentOS中&#xff0c;对静态HTTP服务的性能监控和日志管理是确保系统稳定运行和及时发现潜在问题的关键。以下是对这一主题的详细探讨。 性能监控 使用工具监控&#xff1a;top、htop、vmstat、iostat等工具可以用来监控CPU、内存、磁盘I/O等关键性能指标。这些工具可以实时…

【C++】map和set的模拟实现

map和set的模拟实现 插入模拟实现正向迭代器模拟实现插入完整实现map的[ ]接口模拟实现正向迭代器完整代码反向迭代器模拟实现 喜欢的点赞&#xff0c;收藏&#xff0c;关注一下把&#xff01; 在前面几篇C的博客&#xff0c;讲过了二叉搜索树&#xff0c;AVL树&#xff0c;红黑…

Halcon边缘滤波器edges_image 算子

Halcon边缘滤波器edges_image 算子 基于Sobel滤波器的边缘滤波方法是比较经典的边缘检测方法。除此之外&#xff0c;Halcon也提供了一些新式的边缘滤波器&#xff0c;如edges_image算子。它使用递归实现的滤波器&#xff08;如Deriche、Lanser和Shen&#xff09;检测边缘&…

Android中集成FFmpeg及NDK基础知识

前言 在日常App开发中,难免有些功能是需要借助NDK来完成的,比如现在常见的音视频处理等,今天就以ffmpeg入手,来学习下Android NDK开发的套路. JNI和NDK 很多人并不清除JNI和NDK的概念,经常搞混这两样东西,先来看看它们各自的定义吧. JNI和NDK 很多人并不清除JNI和NDK的概念…

成就动机测试

成就动机测试广泛应用在职业发展领域&#xff0c;如&#xff1a;企业Hr人力资源管理部门&#xff0c;用于评估分析员工的潜能和价值&#xff0c;适用场景有人才招聘&#xff0c;岗位晋升&#xff0c;绩效考评等等。在大学生做职业规划&#xff0c;求职应聘中&#xff0c;应用成…

【python】基础知识类的语法功能讲解

Python代码定义了一个名为Calculation的类&#xff0c;用于执行基础的数学运算&#xff08;加法、减法、乘法、除法和取模&#xff09;。下面我将详细解释各个部分的功能&#xff0c;并以列表形式总结&#xff1a; 类定义&#xff1a; class Calculation: 定义了一个名为Cal…

CMake在静态库中链接动态库

hehedalinux:~/Linux/multi-v3$ tree . ├── calc │ ├── add.cpp │ ├── CMakeLists.txt │ ├── div.cpp │ ├── mult.cpp │ └── sub.cpp ├── CMakeLists.txt ├── include │ ├── calc.h │ └── sort.h ├── sort │ ├── …

SpringBoot项目里用MultipartFile作为入参获取到的file为什么null

前言&#xff1a; 最近在项目中用到了Excel模板导入功能&#xff0c;但是在测试阶段用Postman测试时&#xff0c;通过Post请求&#xff0c;传到后端的File一直为null。其中前端传参和请求都没问题&#xff0c;后端的接参也没问题&#xff0c;问题出在过滤器的复用上。 问题场…

外包干了5个月,感觉技术退步明显......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01; 而我已经在一个企业干了四…

Spring Security入门

目录 1.简介 与shiro对比 添加依赖 执行流程 2.UserDetailsService User实现类 3.PasswordEncoder BCryptPasswordEncoder 4.自定义登录逻辑 5.自定义登录界面 6.设置请求账户和密码的参数 7.自定义登陆处理器 成功 失败 8.判断 权限判断 角色判断 …

navicat for oracle

前言 Oracle中的概念并不是创建数据库&#xff0c;而是创建一个表空间&#xff0c;然后再创建一个用户&#xff0c;设置该用户的默认表空间为我们新创建的表空间&#xff0c;这些操作之后&#xff0c;便和你之前用过的mysql数据库创建完数据库一模一样了。 创建数据库 使用O…

Error: Failed to download template from registry: fetch failed

第一次构建Nuxt项目时&#xff0c;出现在这样的错误&#xff01;&#xff01;&#xff01; 如果你也是这样得错误&#xff0c;修改hosts也没用。我试了 是因为你的npm安装了其他镜像源&#xff0c; 这个时候你就需要手动下载了&#xff1a; web端访问&#xff1a; https://ra…

Himawari-8 数据下载【利用FTP】

1 波段介绍 2 注册 数据下载之前&#xff0c;必须进行注册 JAXA Himawari Monitor | Registration 注册后&#xff0c;在邮箱里点击同意 邮箱会给出FTP的账号信息 3 下载FTP软件 点击进行新站点的新建 设置刚才邮箱里的主机、用户和密码 选择远程站点&#xff0c;选择自己…

权限系统模型:RBAC模型与ABAC模型

权限系统 基于角色的访问控制&#xff08;RBAC&#xff09; 基于角色的控制访问&#xff08;Role-Based Access Control&#xff0c;简称 RBAC&#xff09;&#xff0c;即&#xff1a;给予该账号角色&#xff08;Role&#xff09;&#xff0c;授权角色对应的相关权限&#xf…

STL篇一:string

文章目录 前言1. STL的简单理解1.1 什么是STL1.2 STL的版本1.3 STL的六大组件1.4 STL的重要性1.5 STL的缺陷 2. string类2.1 为什么学习string类&#xff1f;2.1.1 C语言中的字符串2.1.2 两个面试题 2.2 标准库中的string类2.2.1 string类(了解)2.2.2 string类的常用接口说明 2…

Kafka基本介绍

消息队列 产生背景 消息队列&#xff1a;指的数据在一个容器中&#xff0c;从容器中一端传递到另一端的过程 消息(message): 指的是数据&#xff0c;只不过这个数据存在一定流动状态 队列(queue): 指的容器&#xff0c;可以存储数据&#xff0c;只不过这个容器具备FIFO(先进…

kali_linux换源教程

vim /etc/apt/sources.list #阿里云deb http://mirrors.aliyun.com/kali kali-rolling main non-free contribdeb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib#清华大学源deb http://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib…

Android14实战:打破音频默认重采样的限制(五十二)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…