CAS方式实现单点登录SSO

news2024/12/24 11:32:43

1. CAS介绍

CAS(Central Authentication Service)中心认证服务
下面这张图来自官网,清晰简单的介绍了CAS的继续交互过程
在这里插入图片描述

2. CAS具体实现

首先需要分别搭建CAS-server和CAS-client服务,
这两个服务分别在2台机器上,官方地址如下:

https://github.com/apereo/java-cas-client

2.1 搭建CAS-server

这一步就不详细阐述了,许多公司内部都已经搭建好了CAS server,我们只需要把我们的域名注册到CAS server即可。

2. 搭建CAS-client

在我们自己的项目中,我们首先需要导入依赖

        <dependency>
            <groupId>org.jasig.cas.client</groupId>
            <artifactId>cas-client-core</artifactId>
            <version>3.6.4</version>
        </dependency>

根据这个库的实现,我们还需要写2个Filter,分别为Filter1_CasAuthenticationFilterFilter2_CasTicketValidationFilter
具体实现如下:
Filter1_CasAuthenticationFilter

package com.vip.data.unific.server.config;

import lombok.extern.slf4j.Slf4j;
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;


@WebFilter(urlPatterns = "/*")
@Slf4j
public class Filter1_CasAuthenticationFilter implements Filter {

    private AuthenticationFilter authentication;
    @Autowired
    private CasProperties casProperties;
    public Filter1_CasAuthenticationFilter() {
        super();
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        this.authentication = new AuthenticationFilter();
        this.authentication.setIgnoreInitConfiguration(true);
        this.authentication.setServerName(casProperties.getServerName());
        this.authentication.setCasServerLoginUrl(casProperties.getCasUrlPrefix() + "/login");
        authentication.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        if (Boolean.TRUE.equals(casProperties.getSkipFilter())) {
            chain.doFilter(request, response);
            return ;
        }
        // 不需要 CAS 单点登录的页面直接跳过
        if (Arrays.stream(casProperties.getIgnorePaths()).filter(p -> request.getRequestURI().matches(p)).count() > 0) {
//            log.info(LogMsgKit.of("doFilter").p("uri", request.getRequestURI()).end("跳过cas认证"));
            chain.doFilter(request, response);
            return;
        }
        this.authentication.doFilter(request, response, chain);
    }
    @Override
    public void destroy() {
        this.authentication.destroy();
    }
}

Filter2_CasTicketValidationFilter如下

package com.vip.data.unific.server.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

@WebFilter(urlPatterns = "/*")
@Slf4j
public class Filter2_CasTicketValidationFilter implements Filter  {

    private static String casServerUrlPrefix = "casServerUrlPrefix";

    private static String serverName = "serverName";

    private static String encoding = "encoding";

    private final TicketValidationWrapper ticketValidation;

    @Autowired
    private CasProperties casProperties;

    public Filter2_CasTicketValidationFilter() {
        super();
        this.ticketValidation = new TicketValidationWrapper();
    }

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        ticketValidation.init(new FilterConfig() {
            @Override
            public String getFilterName() {
                return filterConfig.getFilterName();
            }

            @Override
            public ServletContext getServletContext() {
                return filterConfig.getServletContext();
            }

            @Override
            public String getInitParameter(String name) {
                String value = null;
                if (casServerUrlPrefix.equals(name)) {
                    value = casProperties.getCasUrlPrefix();
                } else if (serverName.equals(name)) {
                    value = casProperties.getServerName();
                } else if(encoding.equals(name)){
                    value = casProperties.getEncoding();
                }
                if (value == null) {
                    value = filterConfig.getInitParameter(name);
                }
                return value;
            }

            @Override
            public Enumeration<String> getInitParameterNames() {
                Enumeration<String> name = filterConfig.getInitParameterNames();
                Set<String> set = new HashSet<>();
                while (name.hasMoreElements()) {
                    set.add(name.nextElement());
                }
                set.add(casServerUrlPrefix);
                set.add(serverName);
                set.add(encoding);
                final Iterator<String> iterator = set.iterator();
                set = null;
                return new Enumeration<String>() {
                    @Override
                    public boolean hasMoreElements() {
                        return iterator.hasNext();
                    }
                    @Override
                    public String nextElement() {
                        return iterator.next();
                    }
                };
            }
        });
        ticketValidation.setRedirectAfterValidation(false);//取消重定向,自定义重定向
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        if (Boolean.TRUE.equals(casProperties.getSkipFilter())) {
            chain.doFilter(request, response);
            return ;
        }
        // 不需要 CAS 单点登录的页面直接跳过
        if (Arrays.stream(casProperties.getIgnorePaths()).filter(p -> request.getRequestURI().matches(p)).count() > 0) {
//            log.info(LogMsgKit.of("doFilter").p("uri", request.getRequestURI()).end("跳过cas认证"));
            chain.doFilter(request, response);
            return;
        }
        ticketValidation.doFilter(request, response, chain);
    }

    @Override
    public void destroy() {
        ticketValidation.destroy();
    }
}

这2个Filter基本就是固定写法,顺序最好不要交换(虽然交换了也能执行成功),

3、常见问题讨论

常见问题

如果我有多台服务器,如何实现分布式session共享?

单点登录其本质就是分布式session共享的一种解决方案,也就是集中管理session,所以单点登录已经解决了session共享问题

用户访问/api/xxx路径,登录成功后跳转到/api/aaa路径,如何修改重定向路径?

修改重定向有2个方法,一个是修改response如下,但是cas-client中是直接response.sendRedirect();所以这种方法不管用

response.setHeader(“Location”,“/index”);
response.setStatus(302);
第二种方法,继承Cas20ProxyReceivingTicketValidationFilter,然后重写onSuccessfulValidation方法,自己定义重定向地址

public class TicketValidationWrapper extends Cas20ProxyReceivingTicketValidationFilter {
 
//    @Value("${cas.redirectURL}") //filter启动先于spring bean初始化
    public static final String redirectURL="/";
    @Override
    protected void onSuccessfulValidation(final HttpServletRequest request, final HttpServletResponse response,
                                          final Assertion assertion) {
        try {
            response.sendRedirect(redirectURL);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

为什么需要2个Filter,能写在一起吗?

2个Filter负责的职责不同,理论上可以写在一起,但是分开写逻辑更加清楚

这两个Filter的顺序能交换吗?

通过实验测试,发现2个Filter交换顺序并不会影响登录,用户体验结果是一样,但是F2放在F1前面的话会多走1此Filter,建议Filter1 auth,Filter2 ticket

Filter1_CasAuthenticationFilter
这个主要用来判断用户是否登录,没有登录则重定向到登录界面进行登录,登录完成继续重定向到原始路径

Filter2_CasTicketValidationFilter
这个主要用来验证是否有ST-ticket(ticket是一次性使用的)

session到底存储在哪?

登录成功后,CAS-server有一个session,当用户请求的cookie中携带TGT-xxx访问CAS-server时,可以得到一个ST-ticket

自己的app中也会存储一个session,当用户请求的cookie中携带jsessionid时,则判断为登录成功

也就是CAS-server和自己的app都存储一份session,这两个session是不一样的

编码时可能出现的问题?

注册Filter时,使用2个注解即可,否则会多次注册Filer,导致一个请求执行多次Filter

@ServletComponentScan 加在springboot启动main函数上
@WebFilter(urlPatterns = “/*”) 加在Filter上

Filter上不用加@Component,加了可能会报错或者filter多次注册
Filer执行多次原因还有可能是浏览器默认请求了favicon.ico这个文件,可能检查网络请求中是否有

参考链接:https://blog.csdn.net/chaijunkun/article/details/7646338

在Filter进行注入时要注意,无法使用@Value注入,因为Filter启动时,springbean还没初始化?(可能)

如何控制Filter执行顺序

@order注解不管用,默认是按照类名,所以建议以Filer1xxx,Filter2xxx命名

参考链接:https://www.cnblogs.com/tfgzs/p/4571137.html

下面是一些调研

分布式session解决方案
方案1:Tomcat集群Session全局复制(集群内每个tomcat的session完全同步)【会影响集群的性能】

方案2:根据请求的IP进行Hash映射到对应的机器上【如果服务器宕机了,会丢失Session的数据,实现最简单】

方案3:引入中间件Redis,把Session数据放在Redis中,已经实现的框架有Spring session 使用Spring Session和Redis解决分布式Session共享【有一定的侵入性,实现难度中等,】

方案4:JWT方式,user信息保存在token中,每次请求都携带token【可能存在安全问题,网络开销大一点】

(单点登录其实就属于方案1和3的结合,CAS-server保存登录状态,每个app中也保存的单独的session)

共同点(单点登录的核心)
问题:单点登录的问题是session是各个系统所独自拥有的,各个系统不知道用户是否登录,无法共享用户的登录状态,
目标/切入点:目标/切入点是 “一定要让所有的系统就都可以知道现在用户登录没有”,只要能够实现这个目标/切入点,就可以作为方案,所以 Tomcat集群Session全局复制、请求的IP一直会访问同一个服务器、引入中间件Redis 都是方案。

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

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

相关文章

2023年国自然植物科学相关面上项目信息公布(小麦、大麦、棉花、大豆、玉米)

2024年申报国自然项目基金撰写及技巧http://mp.weixin.qq.com/s?__bizMzA4NTAwMTY1NA&mid2247575761&idx1&sn32dbacd3393f3b76a1e0668e4b8b3c89&chksm9fdd7c08a8aaf51ec31d4790067bb57751a09947eeb7e728b8c008d26b89adba37e0cab32a62&scene21#wechat_redi…

梨花教育,精心的声音可提升罪案剧的吸引力和体验

在为罪案剧录制配音时&#xff0c;配音员应致力于营造剧集所需的紧绚和不确定性&#xff0c;同时准确地传达角色的心理活动和情绪纠葛。罪案剧往往围绕着刑侦探索、法律较量、道德抉择等主题展开&#xff0c;因此配音需要与这些情境相适应。以下是进行罪案剧配音时的几点建议&a…

Vue 2使用element ui 表格不显示

直接修改package.json文件 把这两个依赖修改成对应的 删除node_modules 重新安装依赖 重启

集成电路工厂用什么ERP?哪家的集成电路ERP比较好

集成电路通常对制造工艺、生产设备、品质检验等方面有较高的要求&#xff0c;而随着智能技术和自动化技术的发展成熟&#xff0c;如今集成电路行业逐渐迈入数字化和智能化阶段&#xff0c;而至这个时代背景当中&#xff0c;很多集成电路工厂借助ERP实现信息化转型升级。 时至今…

汽车托运汽车会产生公里数吗?

汽车托运&#xff0c;顾名思义就是把汽车放在板车上进行托运&#xff0c;既然是被托运&#xff0c;那为什么还会产生公里数呢?是被司机私用了吗?还是被当成租赁工具租借出去了呢? 其实不然&#xff0c;回到托运流程里&#xff0c;特别是大板车&#xff0c;我们的线路有很多需…

008 OpenCV matchTemplate 模板匹配

目录 一、环境 二、模板匹配算法原理 三、代码演示 一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、模板匹配算法原理 cv.matchTemplate是OpenCV库中的一个函数&#xff0c;用于在图像中查找与模板匹配的特征。它的主要应用场景…

为何内存不够用?微服务改造启动多个Spring Boot的陷阱与解决方案

在生产环境中我们会遇到一些问题&#xff0c;此文主要记录并复盘一下当时项目中的实际问题及解决过程。 背景简述 最初系统上线后都比较正常风平浪静的。在系统运行了一段时间后&#xff0c;业务量上升后&#xff0c;生产上发现java应用内存占用过高&#xff0c;服务器总共64…

穿山甲SDK接入收益·android广告接入·app变现·广告千展收益·eCPM收益(2023.11)

接入穿山甲SDK的app 全屏文字滚动APP 数独训练APP 广告接入示例: Android 个人开发者如何接入广告SDK&#xff0c;实现app流量变现 接入穿山甲SDK app示例&#xff1a; android 数独小游戏 经典数独休闲益智 2023.11.11 ~ 2023.11.22 app接入上架有一段时间了&#xff0c;接…

毕业设计2049网上选课系统JSP【程序源码+文档+调试运行】

摘要 本文详细介绍了一个网上选课系统的设计与实现过程。该系统主要分为学生用户、管理员和教师用户三个模块&#xff0c;涵盖了用户登录、在线选课、信息管理、密码修改等功能。通过对系统功能的分析&#xff0c;进行了数据库设计和界面设计&#xff0c;并进行了测试和优化。…

5G将如何改变我们的生活和工作方式?

本文翻译自 How Will 5G Transform the Way We Live and Work?&#xff0c;作者&#xff1a;Ansam Yousry&#xff0c; 略有删改。 5G有潜力为所有人创造一个更加互联、高效和可持续的世界。 欢迎来到5G革命&#xff01; 想象一下这样一个世界&#xff0c;您可以在几秒钟内下…

人在国企,年薪30w,每天工作1小时,觉得没意思,要不要走?

精彩回顾&#xff1a;进了央企&#xff0c;拿了户口&#xff0c;却感觉被困住了。 我们经常对自己的掌握的技能视若无睹&#xff0c;认为那不过是寻常之物&#xff0c;但是对于不懂的人来说&#xff0c;那就是宝藏啊。就好像&#xff0c;你可能会做一个超级棒的PPT&#xff0c;…

上海站活动回顾 | 聚焦私募视野,助力量化投研交易

11月16日下午&#xff0c;DolphinDB 携手华金证券&#xff0c;在上海成功举办了 D-Day 私募行业交流会&#xff0c;为大家带来了详实的私募行业场景解析、功能介绍、案例分享及现场演示。三十余位来自私募机构的核心策略研发、量化交易员、数据分析专家们齐聚现场&#xff0c;深…

迎接“全全闪”时代 星辰天合发布星海架构和星飞产品

11 月 17 日&#xff0c;北京市星辰天合科技股份有限公司&#xff08;简称&#xff1a;XSKY星辰天合&#xff09;在北京首钢园举办了主题为“星星之火”的 XSKY 星海全闪架构暨星飞存储发布会&#xff0c;到场嘉宾共同见证了全新的分布式全闪架构“星海&#xff08;XSEA&#x…

解密高性能查询!小米亲授:如何轻松查出1000条数据的后十条前7条?

大家好&#xff0c;我是小米&#xff01;今天要跟大家分享一道火辣辣的面试题&#xff1a;在一张表中&#xff0c;如何高性能地查出1000条数据的后十条的前7条&#xff1f;这可是一个考察你数据库查询优化能力的好题目哦&#xff01;废话不多说&#xff0c;让我们直奔主题&…

requests 解决 itz 文档中的 Content-Encoding 问题

在使用Python中的requests库进行网络请求时&#xff0c;我们经常需要获取服务器的响应内容。 itz文档中没有明确说明如何使用r.content&#xff0c;而不是r.read()来获取响应内容。这可能会导致一些开发者在使用requests库时感到困惑&#xff0c;特别是对于那些希望更清晰地了…

持续集成失败:hudson.plugins.git.GitException: Failed to delete workspace

持续集成环境(git gitlab jenkins pipeline maven harbor docker k8s)之前都是ok的&#xff0c;突然就报错了&#xff1a; Cloning the remote Git repository Cloning repository git192.168.117.180:qzcsbj/gift.git ERROR: Failed to clean the workspace jenkins.ut…

数据库基础入门 — 认识数据库

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

Unity 头顶图文字性能优化

如图&#xff1a;常规的排版&#xff0c;会有很多Batches。这是优化后的Batches只有3。 常用解决方案&#xff1a; 1、创建两个Canvas&#xff0c;一个放所有文本Text&#xff0c;一个放所有Image。但这里有会有两个问题&#xff1a;一旦文字夹在两个Image中间&#xff0c;还有…

从传统到智能 | 拓世法宝AI智能直播一体机为商家注入活力

2023年即将结束&#xff0c;直播仍然是商业舞台上的主旋律&#xff0c;本地生活也不例外。据数据显示&#xff0c;到2022年&#xff0c;中国本地生活服务市场规模已经达到29.8万亿元&#xff0c;而预计到2025年&#xff0c;这一数字将继续攀升至35.3万亿元。伴随着当地生活直播…

EDIFACT学习手册

EDIFACT 又名 UN/EDIFACT&#xff08;全称为 United Nations/Electronic Data Interchange For Administration, Commerce and Transport&#xff09;&#xff0c;是由联合国主导开发制定的国际通用 EDI 标准。EDI术语中的EDIFACT是指 EDIFACT 报文标准&#xff0c;本视频将为大…