前后端分离模式下,SpringBoot + CAS 单点登录实现方案

news2024/11/25 1:01:41

1.CAS服务端构建

1.1.war包部署

cas5.3版本

https://github.com/apereo/cas-overlay-template

构建完成后将war包部署到tomcat即可

1.2.配置文件修改

支持http协议

修改apache-tomcat-8.5.53\webapps\cas\WEB-INF\classes\services目录下的HTTPSandIMAPS-10000001.json,在serviceId中添加http即可

{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^(https|http|imaps)://.*",
  "name" : "HTTPS and IMAPS",
  "id" : 10000001,
  "description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
  "evaluationOrder" : 10000
}

apache-tomcat-8.5.53\webapps\cas\WEB-INF\classes下application.properties添加配置

cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true

配置默认登录用户名密码及登出重定向

修改apache-tomcat-8.5.53\webapps\cas\WEB-INF\classes下application.properties配置

cas.authn.accept.users=admin::admin

#配置允许登出后跳转到指定页面
cas.logout.followServiceRedirects=true

1.3.启动

2.客户端构建

2.1.pom依赖

<dependency>
    <groupId>net.unicon.cas</groupId>
    <artifactId>cas-client-autoconfig-support</artifactId>
    <version>2.3.0-GA</version>
</dependency>

2.2.yml配置

client-host-url配置的地址和前端ajax调用的地址必须一致,统一使用ip:porthostname:port;如果本地后端配置localhost,前端使用ip,会造成Ticket验证失败

cas:
  server-url-prefix: http://172.19.25.113:8080/cas
  server-login-url: http://172.19.25.113:8080/cas/login
  client-host-url: http://172.19.25.113:1010
  validation-type: cas
  use-session: true
  authentication-url-patterns:
    /auth

2.3.后端代码

启动类添加@EnableCasClient注解

@EnableCasClient
@SpringBootApplication
public class SpringbootCasDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootCasDemoApplication.class, args);
    }
}

自定义AuthenticationFilter重定向策略

public class CustomAuthRedirectStrategy implements AuthenticationRedirectStrategy {

    @Override
    public void redirect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s) throws IOException {
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write("401");
    }
}

Cors及CasClient相关filter初始化参数配置

@Configuration
public class CasAuthConfig extends CasClientConfigurerAdapter {

    @Override
    public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
        Map<String, String> initParameters = authenticationFilter.getInitParameters();
        initParameters.put("authenticationRedirectStrategyClass", "cc.jasonwang.springbootcasdemo.config.CustomAuthRedirectStrategy");
    }

    @Override
    public void configureValidationFilter(FilterRegistrationBean validationFilter) {
        Map<String, String> initParameters = validationFilter.getInitParameters();
        initParameters.put("encodeServiceUrl", "false");
    }

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CorsFilter(source));
        registrationBean.setOrder(-2147483648);
        return registrationBean;
    }
}

Controller

@RestController
public class HelloController {

    @Value("${cas.server-url-prefix}")
    private String casServerUrlPrefix;

    @GetMapping("/auth")
    public void auth(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
        Assertion assertion = (Assertion) session.getAttribute("_const_cas_assertion_");
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        response.setCharacterEncoding("utf-8");
        response.setStatus(200);
        if (assertion != null) {
            String redirectUrl= request.getParameter("redirectUrl");
            try {
                response.setHeader("Content-type", "text/html;charset=UTF-8");
                response.sendRedirect(redirectUrl);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            try {
                response.getWriter().print("401");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @GetMapping("/logout")
    public RedirectView logout(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
        session.invalidate();
        String indexPageUrl = "http://127.0.0.1";
        return new RedirectView( casServerUrlPrefix + "/logout?service=" + indexPageUrl);
    }
}

2.4.页面

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
      <span>单点地址:</span><input class="url" type="text"/><br>
      <button type="button" class="button">登录</button><br>
      <div class="response" style="width: 200px;height:200px;border: 1px solid #3333;"></div>
     <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
      <script type="text/javascript">
        $(".button").click(function(){
          $.get("http://172.19.25.113:1010/auth", function(data){
            $(".response").text(data)
            if(data == 401){
              window.location.href = "http://localhost:8080/cas/login?service=http://172.19.25.113:1010/auth?redirectUrl=http://127.0.0.1"
            }
          })
        })
      </script>
  </body>
</html>

这里只是验证前后端分离下页面url跳转问题,页面没有放在nginx服务上

3.问题记录

3.1在前后端分离情况下,AuthenticationFilter重定向问题,导致前端发生跨域

  • https://www.jianshu.com/p/7b51d04f3327

(1)描述

cas前后端不分离的情况下是能够直接跳转的,然而前后端分离后,前端ajax访问后端在经过AuthenticationFilter时,验证未登录会重定向到CAS登录,导致前端发生跨域问题

(2)解决思路

AuthenticationFilter中不进行重定向,验证未登录就直接返回一个错误状态码;由前端获取到状态码后进行判断,再跳转到CAS登录地址

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 {
     // 获取Assertion 验证是否登录
        HttpSession session = request.getSession(false);
        Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
        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);
            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);
            }
        }
    }
}

DefaultAuthenticationRedirectStrategy

public final class DefaultAuthenticationRedirectStrategy implements AuthenticationRedirectStrategy {
    public DefaultAuthenticationRedirectStrategy() {
    }

    public void redirect(HttpServletRequest request, HttpServletResponse response, String potentialRedirectUrl) throws IOException {
     //response重定向
        response.sendRedirect(potentialRedirectUrl);
    }
}

(3)实现

自定义重定向策略,将DefaultAuthenticationRedirectStrategy替换掉

CustomAuthRedirectStrategy

public class CustomAuthRedirectStrategy implements AuthenticationRedirectStrategy {
    @Override
    public void redirect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s) throws IOException {
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write("401");
    }
}
@Configuration
public class CasAuthConfig extends CasClientConfigurerAdapter {

    @Override
    public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
        Map<String, String> initParameters = authenticationFilter.getInitParameters();
        initParameters.put("authenticationRedirectStrategyClass", "cc.jasonwang.springbootcasdemo.config.CustomAuthRedirectStrategy");
 }
}

3.2AuthenticationFilter自定义重定向策略实现后,前端仍然发生跨域问题

Spring 里那么多种 CORS 的配置方式,到底有什么区别

(1)描述

原使用WebMvcConfigurationSupport实现CORS,AuthenticationFilter输出状态码后,前端仍然发生跨域问题

@Configuration
public class CorsConfig extends WebMvcConfigurationSupport {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("*")
                .maxAge(3600)
                .allowCredentials(true);
    }
}

(2)解决思路

通过查找资料发现:

实现 WebMvcConfigurationSupport.addCorsMappings 方法来进行的 CORS 配置,最后会在 Spring 的 Interceptor 或 Handler 中生效

注入 CorsFilter 的方式会让 CORS 验证在 Filter 中生效

(3)实现

修改CORS实现方式

@Bean
public FilterRegistrationBean corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);
    FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new CorsFilter(source));
    registrationBean.setOrder(-2147483648);
    return registrationBean;
}

3.3前端跳转CAS登录并传递redirectUrl参数,Ticket票据验证问题

(1)原因

Cas20ProxyReceivingTicketValidationFilter在进行Ticket验证时,CAS重定向的service地址进行了URLEncoder编码,而CAS使用Ticket获取到存储的service地址未进行编码,导致两个service不一致,造成Ticket票据验证失败

(2)debug定位问题

AbstractTicketValidationFilter

AbstractUrlBasedTicketValidator

找到CAS服务器接口地址后,便想到在CAS服务器端看下接口是怎么实现的,下面就是在CAS服务器debug后的结果

CAS Server

在web.xml中找到了servlet映射

定位到SafeDispatcherServlet,根据目录结构和类文件名称找到了ServiceValidateController

ServiceValidateController

(3)实现

Cas20ProxyReceivingTicketValidationFilter添加encodeServiceUrl=false初始化参数

@Configuration
public class CasAuthConfig extends CasClientConfigurerAdapter {

    @Override
    public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
        Map<String, String> initParameters = authenticationFilter.getInitParameters();
        initParameters.put("authenticationRedirectStrategyClass", "cc.jasonwang.springbootcasdemo.config.CustomAuthRedirectStrategy");
    }

    @Override
    public void configureValidationFilter(FilterRegistrationBean validationFilter) {
        Map<String, String> initParameters = validationFilter.getInitParameters();
        initParameters.put("encodeServiceUrl", "false");
    }
}

最后说一句(别白嫖,求关注)

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

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

相关文章

PDF文档一键自动生成目录和书签

在工作中经常会遇到编写文档的时候&#xff0c;当我们在word编写完文档后&#xff0c;一般可以自动生成一个目录。为了方便阅读和保护文档不被破坏&#xff0c;一般发送给别人的时候&#xff0c;需要把word文档转换成PDF格式。但是word文档转换为PDF格式后&#xff0c;目录虽然…

【强化学习论文合集】五.2017国际表征学习大会论文(ICLR2017)

强化学习(Reinforcement Learning, RL),又称再励学习、评价学习或增强学习,是机器学习的范式和方法论之一,用于描述和解决智能体(agent)在与环境的交互过程中通过学习策略以达成回报最大化或实现特定目标的问题。 本专栏整理了近几年国际顶级会议中,涉及强化学习(Rein…

vscode electron安装环境

1. 安装nodejs Node.js 安装18.12.1LTS版本 安装完成后确认 node –-version 2. 安装electron npm install electron –g 验证是否安装成功 electron –v 没成功&#xff01;&#xff01;&#xff01; 找解决方案 ​​​​​​​ 无法加载文件xxx.ps1&#xff0c;因为在…

信而泰OLT使用介绍-网络测试仪实操

一、OLT产品介绍 1.概述 PON作为FTTX网络发展的核心技术&#xff0c;局端设备OLT尤其重要。 本文档中主要介绍OLT的功能特性、业务配置 2.基本功能特性 2.1大容量和高集成度 ZXA10 C300集光接入、数据交换、路由处理于一体&#xff0c;提高了系统的集成度。 2.2 EPON功能…

ADSP-21489的图形化编程详解(3:音效开发例程-直通三个例程讲清楚)

Fireware 烧写好了之后&#xff0c;SigmaStudio 图形化开发的基本条件就达成了。我们重新来链接一下硬件&#xff0c;进入图形化编程的阶段&#xff0c;这个阶段我尽量多写一些例程&#xff0c;让大家能够尽快熟悉这个软件开发的全过程。 1. 直通&#xff08;1&#xff09; 1&…

C++类与对象(三)赋值运算符重载、const成员

目录 1.赋值运算符重载 1.1引入 1.2 运算符重载 1.3赋值运算符重载 1.4 前置和后置重载 2.const成员 3.取地址及const取地址操作符重载 1.赋值运算符重载 1.1引入 赋值运算符重载&#xff1a;用已存在的对象&#xff0c;给另一个已存在的对象赋值 还是使用上节日期类的…

[网络工程师]-应用层协议-电子邮件协议

常见的电子邮件协议有简单邮件传输协议、邮局协议和Internet邮件访问协议。 1、简单邮件传输协议&#xff08;Simple Mail Transfer Protocol&#xff0c;SMTP&#xff09; SMTP主要负责将电子邮件从发送方传送到接收方&#xff0c;即对传输的规则做了规定&#xff0c;该协议工…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java医院疫情管理系统4f9a9

毕业设计其实不难&#xff0c;主要毕业的时候任务太紧了&#xff0c;所以大家都非常忙没有时间去做&#xff0c;毕业设计还是早做准备比较好&#xff0c;多花点时间也可以做出来的&#xff0c;建议还是自己动手去做&#xff0c;比如先选一个题&#xff0c;这样就有方向&#xf…

Markdown 图片左右对齐、居中、大小设置

Markdown 图片左右对齐、居中、大小设置 虽然经常使用 Markdown 写博客&#xff0c;但是&#xff0c;我却不太知道 Markdown 图片的位置和大小设置&#xff0c;今天刚好发表博客的时候&#xff0c;发觉图片位置有点丑&#xff0c;Google 查到了方法&#xff0c;所以记录学习一下…

李立宗《计算机视觉40例》PPT课件:第3章

《计算机视觉40例》PPT课件&#xff1a;第3章 《计算机视觉40例》第3章是OpenCV入门&#xff0c;这一章对OpenCV的使用进行了简单的介绍。这章目录如下&#xff1a; 本章集中介绍了OpenCV中一些最通用的知识&#xff0c;这些知识在后面的案例中应用比较广泛&#xff0c;所以集…

木聚糖-聚乙二醇-牛血清白蛋白,BSA-PEG-Xylan,牛血清白蛋白-PEG-木聚糖

木聚糖-聚乙二醇-牛血清白蛋白,BSA-PEG-Xylan,牛血清白蛋白-PEG-木聚糖 中文名称&#xff1a;木聚糖-牛血清白蛋白 英文名称&#xff1a;Xylan-BSA 纯度&#xff1a;95% 别称&#xff1a;牛血清白蛋白修饰木聚糖&#xff0c;BSA-木聚糖 PEG接枝修饰木聚糖 木聚糖-聚乙二醇…

Mysql——》decimal

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 Mysql——》decimal一、作用二、语法三、示例一、…

ADSP-21489的开发详解:VDSP+自己编程写代码开发(8-延时算法)

这个程序&#xff0c;对 48Khz 或 96Khz 采样率的音频&#xff0c;进行了延时处理&#xff0c;并输出。跑程序和上面的例程一样&#xff0c;我们来看一下他音频处理部分的具体程序。&#xff08;音频输入输出需根据程序换接口&#xff09; Left_Channel_Out1 Left_Channel_In2…

MEI 论文笔记

Multi-Partition Embedding Interaction with Block Term Format for Knowledge Graph Completion- Introduction- Algorithm- Experiment- Conclusion- CodeHung-Nghiep Tran, Atsuhiro Takasu - Introduction 以前的工作通常将每个嵌入视为一个整体&#xff0c;并对这些整体…

元宇宙产业委共同主席倪健中:打开元宇宙的潘多拉魔盒,释放元宇宙产业无限的想象与发展空间|平安银行元宇宙与新终端创新沙龙

12 月 2 日&#xff0c; 平安银行携手业内元宇宙知名专家学者、行业投资人、新终端企业代表在深圳蛇口举办“洞见未来超前领航——2022 平安银行元宇宙与新终端创新沙龙”&#xff0c;共同探讨虚拟现实的多样性&#xff0c;畅游元宇宙时空下的科技文明&#xff01; 活动开场&am…

VSCode使用Qt的MinGW作为编译器编译C++

一、起因 我本人已经安装了Qt、VS、VSCode&#xff0c;因此不想再安装其他的编译器&#xff0c;但又想使用VSCode直接编译&#xff0c;所以就想看一下能否VSCode能否直接使用Qt的编译器。经过实验的确是可以的&#xff0c;这样就无需再下载MinGW-w64&#xff0c;其实我也有下载…

mongodb6创建账号

目录一、创建管理员账号二、创建普通账号一、创建管理员账号 无权限登录mongodb&#xff08;即官方默认配置登录&#xff09; mongosh --host 127.0.0.1 --port 27017创建管理员账号 #切换到admin数据库 use admin #创建admin账号 db.createUser({user:"testAdmin"…

22年下半年软考考后成绩查询、分数线、证书领取相关注意事项

一、软考成绩查询时间 软考考试时间分为上半年和下半年&#xff0c;上半年固定是在五月份下半年固定是在十一月份。 成绩查询时间一般在考试时间后的两个月左右根据历年来看&#xff0c; 2022年上半年软考考试时间是5月28-29日&#xff0c;成绩7月中旬左右在官网可以查询&am…

聊天信息框显示消息

聊天信息框显示消息 效果展示 概述 本文讲解如何制作&#xff0c;可以提交信息的聊天框&#xff0c;并且可以删除已经发布的聊天信息。 构建HTML框架 <body><textarea name"" id""></textarea><button>发布</button><…

基于java(ssm)学生在线课程学习系统源码(java毕业设计)

基于java&#xff08;ssm&#xff09;学生在线课程学习系统 学生在线课程学习系统是基于java编程语言&#xff0c;mysql数据库&#xff0c;ssm框架&#xff0c;和idea工具开发&#xff0c;本项目主要分为学生&#xff0c;管理员两个角色&#xff0c;学生的功能是登陆&#xff…