实战指南:Shiro、CAS打造完美单点登录体验

引言

想象一下,在日常工作中,我们经常需要进行系统认证授权。当用户尝试登录一个网站时,他们需要提供用户名和密码,网站会检查这些信息,确认用户是谁。这就是认证的过程

一旦用户被认证,他们可能会尝试访问网站的某些部分,比如他们的个人资料页面,在这个时候,网站需要确定用户有权访问这个页面。这就是授权的过程

Shiro 与 CAS

认证、授权如何做?能否抽象、通用、一次登录跨系统访问?

当然可以,Shiro 和 CAS 就是一种常见的组合解决方案,也是我们今天要详细讨论的方案。

Shiro 是一个 Java 安全框架,主要用于认证和授权。CAS 是一个单点登录解决方案,主要用于认证。

结合 Shiro 和 CAS,是一种能力组合,我们可以实现一个既安全又便捷的认证和授权方案。

两者的关系?简单理解 Shiro 将认证、授权能力进行抽象,而 CAS 则是【认证】的一种实现方案。授权的实现和具体业务相关联,你可以扩展思考下。

为什么需要单点登录?

在大型企业环境中,我们常常需要在后台系统A、后台系统B、用户中心、配置中心 … 等多个系统之间进行切换。

如果每次切换都需要重新输入账号密码,这无疑会大大降低工作效率,同时还可能增加账号密码泄露的风险。为了解决这个问题,我们引入了单点登录(Single Sign-On)技术。

通过单点登录,用户只需进行一次身份验证,就可以在所有互相信任的系统中自由切换,无需再次进行身份验证。

这不仅提高了工作效率,也大大降低了安全风险。

当然,如果你的系统只有一个应用,那么可能不需要使用 CAS。用户可以直接在这个应用上进行登录,然后就可以使用这个应用的所有功能了。

实践

整体流程

我们先看看整体的认证/授权流程:

在这里插入图片描述

我们一般选择在网关统一做认证、授权逻辑:

  1. 在网关利用 Shiro 的过滤器机制,校验用户是否认证,如果没有则重定向登录页
  2. 用户输入账号密码,走 CAS 认证
  3. 认证通过,返回 ticket 票据
  4. 客户端携带 ticket 票据重新访问业务系统
  5. 网关 Shiro 过滤器从 param 参数检测到 ticket 信息,走 Shiro 的 login 登录,最终通过 Realm 请求 CAS 换取用户信息。
  6. 认证成功,并获取到用户信息后,将其存到业务 session 中。
  7. Shiro 的授权 filter 继续拦截判断权限信息,如果缓存(可选)没有,尝试从权限系统获取权限列表
  8. 拿到权限后,判断有权限,则继续业务操作。
  9. 响应业务结果

注意:与 CAS 的交互会还有更多的必要信息,以及可能多次重新定向操作,这里只简单列出了最主要的信息以及流程。

实践

核心步骤:

  1. Realm:定义 SsoCasRealm 用来与 CAS 服务、权限系统交互
  2. Filter:定义 SsoFilter 用来拦截判断认证、授权信息

本文只讲核心逻辑,各种包引入、配置等不赘述!

1、Realm

public class SsoCasRealm extends CachingRealm implements Authorizer {...// 1、通过ticket获取认证的用户信息protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 通过 ticket 从 cas 获取用户信息String ticket = (String) casToken.getCredentials();String userId = casServer.ticketValidateAndGet(ticket, service);if (null == userId) {throw new UnsupportedTokenException("用户名或密码错误");}User user = new SimpleUser(userId);Session session = SecurityUtils.getSubject().getSession();session.setAttribute("user", user);return new SimpleAuthenticationInfo(user, user.getExt("ticket"), getName());}// 2、通过认证通过后的凭证,获取用户权限信息protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {if (principals == null) {return null;}AuthorizationInfo info = null;// 首先尝试从缓存取Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();if (cache != null) {Object key = getAuthorizationCacheKey(principals);info = cache.get(key);if (log.isTraceEnabled()) {if (info == null) {log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");} else {log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");}}}// 如果缓存没有if (info == null) {// 从权限系统获取权限信息// 如 RolePermissions rolePermissions = authorization.authorizate(user);info = doGetAuthorizationInfo(principals);if (info != null && cache != null) {// 缓存权限信息Object key = getAuthorizationCacheKey(principals);cache.put(key, info);}}return info;}    
...
  1. 认证信息:通过 ticket 从 CAS 换取用户信息
  2. 权限信息:先尝试从缓存获取,如果缓存没有就直接从权限系统获取

在 Authorizer 接口中定义了 isPermitted,我们可以重写它,通过SsoCasRealm.getAuthorizationInfo 获取权限列表:

    public boolean[] isPermitted(PrincipalCollection principals, List<Permission> permissions) {AuthorizationInfo info = getAuthorizationInfo(principals);return isPermitted(permissions, info);}

这样上层拦截器就可以直接通过该方法判断是否有权限了。

2、Filter

public abstract class AuthenticatingFilter extends AuthenticationFilter {...// 当访问失败时,执行 login 逻辑@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {boolean isAccessed = executeLogin(request, response);return isAccessed;}// 执行登录逻辑@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {AuthenticationToken token = createToken(request, response);if (token == null) {/*** 直接跳转*/redirectToLogin(request, response);return false;}try {Subject subject = getSubject(request, response);subject.login(token);return onLoginSuccess(token, subject, request, response);} catch (AuthenticationException e) {return onLoginFailure(token, e, request, response);}}// 换取 ticket 包装实体@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpRequest = (HttpServletRequest) request;String ticket = httpRequest.getParameter("ticket");if (ticket == null) return null;Map<String, String> attributes = new HashMap<>();Enumeration<String> paramNames = request.getParameterNames();while (paramNames.hasMoreElements()) {String paramName = paramNames.nextElement();if (paramName.equals("ticket")) continue;attributes.put(paramName, request.getParameter(paramName));}return new SessionCasToken(token);}...

值得注意的是:当我们通过 subject.login(token) 登录时,底层就会使用到 SsoCasRealm.doGetAuthenticationInfo 从 CAS 去获取用户信息。

在 AuthenticationFilter 以及其父类中定义了拦截控制:

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {Subject subject = this.getSubject(request, response);return subject.isAuthenticated();}
  1. true:则继续往下走
  2. false:则走重定向登录逻辑
关于 subject.login

在 Apache Shiro 中,login 方法的主要任务是执行认证操作,也就是验证用户提供的身份(如用户名)和凭证(如密码)是否匹配。

当你调用 Subject.login(token) 方法时,Shiro 会将这个 token 传递给安全管理器(SecurityManager),然后由安全管理器协调相应的认证器(Authenticator)和认证策略进行认证。

在认证过程中,Shiro 会从相应的 Realm 中获取用户的认证信息,然后与 token 中的信息进行比较。如果匹配,认证成功;否则,认证失败,会抛出相应的 AuthenticationException。

一旦认证成功,Shiro 会将用户的认证信息(如用户的身份、角色、权限等)保存到用户的 session 中,以便后续的授权操作。这样,当用户在后续的请求中需要进行授权操作时,Shiro 可以直接从 session 中获取用户的认证信息,而无需再次进行认证。

简单理解:login 方法的主要任务是将 Realm 获取的认证信息存储到 session 中。

结论

在日常工作中,我们通常需要进行系统认证和授权。认证是验证用户身份的过程,而授权则是确定用户权限的过程。

Shiro是一个Java安全框架,主要用于认证和授权。它通过收集用户的身份和凭证进行认证,成功后返回已认证的身份。在授权方面,Shiro提供了基于角色和基于权限的访问控制,满足不同的授权需求。

CAS(Central Authentication Service)是一个单点登录解决方案,主要用于认证。用户在CAS中进行一次认证后,就可以访问所有与CAS集成的应用,无需再次认证。

结合Shiro和CAS,我们可以实现一个既安全又便捷的认证和授权方案:使用Shiro进行应用内的认证和授权,使用CAS实现跨应用的单点登录。

相关参考:

# Apache Shiro 核心原理及实践

# 一站式登录:揭秘CAS单点登录的原理与流程

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

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

相关文章

cuda-cuDnn

cuda sudo /bin/sh cuda_11.7.0_515.43.04_linux.run cudnn cuDNN Archive | NVIDIA Developer Linux 系统 CUDA 多版本共存以及切换 – 颢天 安装cuda # 如果已经安装过驱动&#xff0c;驱动不需要再安装&#xff0c;取消勾选 安装cuDNN&#xff0c;cuda-cuDNN对应关系见…

QComboBox中使用树形控件进行选择

事情是这样的&#xff0c;要在一个ComboBox中通过树形结构进行内容的选择。 默认的QComboBox展开是下拉的列表。因此需要定制一下。 效果就是这样的 实现上面效果的核心代码就是下面这样的 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { treenew…

【网络协议】路由信息协议 (RIP)

未经许可&#xff0c;不得转载。 路由信息协议&#xff08;Routing Information Protocol&#xff0c;简称 RIP&#xff09;是一种使用跳数&#xff08;hop count&#xff09;作为路由度量标准的路由协议&#xff0c;用于确定源网络和目标网络之间的最佳路径。 文章目录 什么是…

LoRA微调系列笔记

系列文章目录 第一章&#xff1a;LoRA微调系列笔记 第二章&#xff1a;Llama系列关键知识总结 第三章&#xff1a;LLaVA模型讲解与总结 文章目录 系列文章目录LoRA&#xff1a;Low-Rank Adaptation of Large Language Models目的&#xff1a;依据&#xff1a;优势&#xff1a;…

SRS 服务器入门:实时流媒体传输的理想选择

在当今视频流媒体需求爆炸式增长的时代&#xff0c;如何选择一款高效、稳定且功能强大的流媒体服务器成为了许多开发者和企业关注的焦点。而 SRS&#xff08;Simple Realtime Server&#xff09;作为一款开源的流媒体服务器&#xff0c;以其卓越的性能和灵活的功能&#xff0c;…

C++第五六单元测试

1【单选题】在公有派生类的成员函数不能直接访问基类中继承来的某个成员&#xff0c;则该成员一定是基类中的&#xff08; C &#xff09;。&#xff08;2.0分&#xff09; A、公有成员B、保护成员C、私有成员D、保护成员或私有成员 注意从类外访问与从派生类中访问 2【单…

使用Python可视化有压缩格式的Bitmap(BMP)图像调色板数据

使用Python可视化有压缩格式的Bitmap BMP图像调色板数据 参考文章一、调色板数据二、测试代码三、测试结果 参考文章 有压缩格式的Bitmap(BMP)图像显示调色板数据和图像数据Bitmap(BMP)图像信息分析主要说明带压缩的形式Bitmap(BMP)图像信息验证 一、调色板数据 Color Palette…

「Mac畅玩鸿蒙与硬件49」UI互动应用篇26 - 数字填色游戏

本篇教程将带你实现一个数字填色小游戏&#xff0c;通过简单的交互逻辑&#xff0c;学习如何使用鸿蒙开发组件创建趣味性强的应用。 关键词 UI互动应用数字填色动态交互逻辑判断游戏开发 一、功能说明 数字填色小游戏包含以下功能&#xff1a; 数字选择&#xff1a;用户点击…

html+css+js网页设计 美食 美食家6个页面

htmlcssjs网页设计 美食 美食家6个页面 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#xf…

标准库以及HAL库——按键控制LED灯代码

按键控制LED本质还是控制GPIO,和点亮一个LED灯没什么区别 点亮一个LED灯&#xff1a;是直接控制输出引脚&#xff0c;GPIO初始化推挽输出即可 按键控制LED&#xff1a;是按键输入信号从而控制输出引脚&#xff0c;GPIO初始化推挽输出一个引脚以外还得加一个GPIO上拉输入 但是…

Java的list中状态属性相同返回true的实现方案

文章目录 项目背景方案一、for循环实现实现思路 方案二、stream实现实现思路 项目背景 在项目中会遇到list中多个状态判断&#xff0c;状态值相等时&#xff0c;总体返回为true。 方案一、for循环实现 实现思路 遍历list&#xff0c;当出现不一致时&#xff0c;直接跳出循环…

模型选择+过拟合欠拟合

训练误差和泛化误差 训练误差&#xff1a;模型在训练数据上的误差 泛化误差&#xff1a;模型在新数据上的误差 验证数据集&#xff1a;一个用来评估模型好坏的数据集 例如拿出50%的数据作为训练 测试数据集&#xff1a;只能用一次 K则交叉验证 在没有足够数据时使用 算法…

Web安全攻防入门教程——hvv行动详解

Web安全攻防入门教程 Web安全攻防是指在Web应用程序的开发、部署和运行过程中&#xff0c;保护Web应用免受攻击和恶意行为的技术与策略。这个领域不仅涉及防御措施的实现&#xff0c;还包括通过渗透测试、漏洞挖掘和模拟攻击来识别潜在的安全问题。 本教程将带你入门Web安全攻防…

语音识别基础算法——动态时间规整算法

前言 动态时间规整算法&#xff0c;Dynamic Time Wraping&#xff0c;缩写为DTW&#xff0c;是语音识别领域的一个基础算法。 算法的提出 DTW 的提出是为了解决或尽量解决在语音识别当中的孤立词识别不正确的问题。该问题简单描述为&#xff1a;在识别阶段&#xff0c;将输入…

SAP SD信贷管理信用管理手册(下)

1、项目类别的信贷激活 图1-12-1.项目类别的信贷设置路径 图1-12-2.项目类别的信贷参数激活 说明&#xff1a;项目类别是否进行信贷管理设置。 2、定义信贷组 图1-13-1.定义信贷组路径 图1-13-2.信贷组定义 说明&#xff1a;信贷组参与后续信贷控制的组合分配。 3、销售凭证及…

分布式项目___某污水处理项目

一.分布式项目___污水处理项目 项目地址:https://gitee.com/yanyigege/collaborative-water-springboot.git ​ 1.项目背景 总公司在全国各地有处理污水的项目部,各项目部处理自己的污水,总部需要监控各地分项目部每天处理污水的原料用量,掌握各分部的污水处理情况 ​ 2.功…

Nginx详细安装配置过程

目录 1.nginx环境准备 1.1 在配置好yum源之后&#xff0c;安装如下的编译工具 1.2 安装nginx所需的依赖库 1.3 关闭防火墙&#xff0c;selinux&#xff0c;并确保网络正常 2.nginx的编译安装 2.1从nginx官网复制下载链接&#xff0c;wget 下载 2.2? 解压nginx源代码 2…

2025年阿里云认证改版新消息!2025年阿里云认证考试内容有变!

阿里云认证已经确定在2025年要进行大改&#xff0c;这次改动幅度会比2023年改动更大&#xff0c;2023年主要改变是在考试题型上的变化&#xff0c;这次则主要是考试内容的变化了&#xff01; 2023年阿里云ACP认证考试的改版变化主要有&#xff1a; &#xff08;一&#xff09…

【Scala】图书项目系统代码演练3.1/BookService

package org.app package serviceimport models.{BookModel, BorrowRecordModel}import org.app.dao.{BookDAO, BorrowRecordDAO}import java.time.LocalDateTime import scala.collection.mutable.ListBuffer// 图书业务逻辑层 class BookService {private val bookDAO new B…

Ajax数据爬取

有时我们用requests 抓取页面得到的结果&#xff0c;可能和在浏览器中看到的不一样:在浏览器中可以看到正常显示的页面数据&#xff0c;而使用requests 得到的结果中并没有这些数据。这是因为 requests 获取的都是原始 HTML 文档&#xff0c;而浏览器中的页面是JavaScript 处理…