CAS方式实现单点登录SSO

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;@Autowiredprivate CasProperties casProperties;public Filter1_CasAuthenticationFilter() {super();}@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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;@Autowiredprivate CasProperties casProperties;public Filter2_CasTicketValidationFilter() {super();this.ticketValidation = new TicketValidationWrapper();}@Overridepublic void init(final FilterConfig filterConfig) throws ServletException {ticketValidation.init(new FilterConfig() {@Overridepublic String getFilterName() {return filterConfig.getFilterName();}@Overridepublic ServletContext getServletContext() {return filterConfig.getServletContext();}@Overridepublic 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;}@Overridepublic 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>() {@Overridepublic boolean hasMoreElements() {return iterator.hasNext();}@Overridepublic String nextElement() {return iterator.next();}};}});ticketValidation.setRedirectAfterValidation(false);//取消重定向,自定义重定向}@Overridepublic 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);}@Overridepublic 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="/";@Overrideprotected 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.hqwc.cn/news/207647.html

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

相关文章

关于ego-planner里面的GridMap

浙大这套开源的代码写得很nice 很值得借鉴 &#xff0c; 对于 GridMap 类的实现。该类通过智能指针的封装简化了 GridMap 实例的创建和管理过程。一旦通过 GridMap::initMap(ros::NodeHandle &nh) 方法初始化&#xff0c;就可以方便地调用 GridMap 及其所有相关功能 它主要…

C语言--每日五道选择题-- Day22

第一题&#xff08;注意&#xff09; 1.下列 C 代码中&#xff0c;不属于未定义行为的有&#xff1a;______。 A&#xff1a;int i0; i(i); B&#xff1a;char *p"hello"; p[1]E; C&#xff1a;char *p"hello"; char ch*p; D&#xff1a;int i0; printf(&q…

Oracle的控制文件多路复用,控制文件备份,控制文件手工恢复

一.配置控制文件多路复用 1.查询Oracle的控制文件所在位置 SQL> select name from v$controlfile;NAME -------------------------------------------------------------------------------- /u01/app/oracle/oradata/orcl/control01.ctl /u01/app/oracle/fast_recovery_a…

优思学院|质量管理怎样才能做好?

质量管理怎样才能做好&#xff1f;这是一个好问题&#xff0c;很多人第一时间会想到建立一个稳定的质量管理体系&#xff0c;例如ISO9001&#xff0c;又或者善用QC七大手法等等&#xff0c;虽然以上这些方法都是实用和正确的&#xff0c;绝大多数企业通常最忽略的&#xff0c;其…

数据结构学习笔记——多维数组、矩阵与广义表

目录 一、多维数组&#xff08;一&#xff09;数组的定义&#xff08;二&#xff09;二维数组&#xff08;三&#xff09;多维数组的存储&#xff08;四&#xff09;多维数组的下标的相关计算 二、矩阵&#xff08;一&#xff09;特殊矩阵和稀疏矩阵&#xff08;二&#xff09;…

专业远程控制如何塑造安全体系?向日葵“全流程安全闭环”解析

安全是远程控制的重中之重&#xff0c;作为国民级远程控制品牌&#xff0c;向日葵远程控制就极为注重安全远控服务的塑造。近期向日葵发布了以安全和核心的新版“向日葵15”以及同步发布《贝锐向日葵远控安全标准白皮书》&#xff08;下简称《白皮书》&#xff09;&#xff0c;…

【LeetCode刷题】--40.组合总和II

40.组合总和II 本题详解&#xff1a;回溯算法 class Solution {public List<List<Integer>> combinationSum2(int[] candidates, int target) {int len candidates.length;List<List<Integer>> res new ArrayList<>();if (len 0) {return re…

Golang 中的良好代码与糟糕代码

最近&#xff0c;有人要求我详细解释在 Golang 中什么是好的代码和坏的代码。我觉得这个练习非常有趣。实际上&#xff0c;足够有趣以至于我写了一篇关于这个话题的文章。为了说明我的回答&#xff0c;我选择了我在空中交通管理&#xff08;ATM&#xff09;领域遇到的一个具体用…

计算机毕业设计 基于微信小程序的“共享书角”图书借还管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

shell脚本之循环语句(for、while、untli)

循环语句&#xff1a; 一定要有跳出循环条件 循环条件&#xff1a; 1.已知循环的次数&#xff08;新来十个人&#xff0c;就要新建十个账号 2.未知循环的次数&#xff0c;但是要有跳出循环条件&#xff08;对象生气&#xff0c;要道歉到原谅为止&#xff09; for&#xff…

maven打包可执行jar含依赖lib

修改pom.xml <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!-- jdk8可用&#xff0c;其他jdk版本可能需改插件版本 --><version>2.3.7.RE…

CSS伪类选择器详细讲解

前言 伪类选择器在CSS中起到的作用可以说是至关重要的&#xff0c;如果CSS没有伪类选择器&#xff0c;有很多效果都要借助js来完成&#xff0c;这样不仅代码量增加&#xff0c;维护起来你难度也大。这样程序员的工作量大&#xff0c;也违背了CSS诞生的作用&#xff0c;就是提高…