基于JWT+SpringSecurity整合一个单点认证授权机制
基于 JWT + Spring Security 的授权认证机制,在整体架构设计上体现了高度的安全性与灵活性。其在整合框架中的应用,充分展示了模块化、可扩展性和高效鉴权的设计理念,为开发者提供了一种值得借鉴的安全架构模式。
1.SpringSecurity概念理解
1.1 一般的Web应用需要进行认证和授权。
认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户。
授权:经过认证后判断当前用户是否有权限进行某些操作
1.2 RBAC权限模型的理解
可以理解为用户对应角色,角色又对应权限,在不考虑加入部门等参数的干预下,整个RBAC模型可以简化为5张表来描述:
1.3 SpringSecurity机制:
整个SpringSecurity机制可以理解为是一个过滤器链机制,前端发请求给后端,请求会经过整个过滤器链的认证和授权逻辑才能执行后续正常的系统接口逻辑。下方三个为核心过滤器
第一个负责处理在登录页面填写用户名和密码后登录请求
第二个负责处理过滤器链中抛出的授权和认证异常
第三个是负责权限校验的过滤器
认证点认证流程:
2.认证架构设计
2.1 登录的整体流程设计:
从login()逻辑开始执行
2.1 SpringSecurity的配置类
登录接口实现:
/*** 登录方法* @param loginBody 登录信息* @return 结果*/ @Operation(summary = "登录方法") @PostMapping("/login") public AjaxResult login(@RequestBody LoginBody loginBody) {AjaxResult ajax = AjaxResult.success();// 生成令牌String token = loginService.login(loginBody.getUsername(),loginBody.getPassword(),loginBody.getCode(),loginBody.getUuid());ajax.put(Constants.TOKEN, token);return ajax; }
/*** 登录验证* @param username 用户名* @param password 密码* @param code 验证码* @param uuid 唯一标识* @return 结果*/ public String login(String username, String password, String code, String uuid) {// 验证码校验validateCaptcha(username, code, uuid);// 登录前置校验loginPreCheck(username, password);String ip = IpUtils.getIpAddr();// 验证 IP 是否被封锁passwordService.validateIp(ip);// 用户验证Authentication authentication = null;try{UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);AuthenticationContextHolder.setContext(authenticationToken);// 该方法会去调用UserDetailsServiceImpl.loadUserByUsernameauthentication = authenticationManager.authenticate(authenticationToken);}catch (Exception e){if (e instanceof BadCredentialsException){AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));throw new UserPasswordNotMatchException();}else{passwordService.incrementIpFailCount(ip);AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));throw new ServiceException(e.getMessage());}}finally{AuthenticationContextHolder.clearContext();}AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());// 生成tokenreturn tokenService.createToken(loginUser); }
2.2 SpringSecurity配置类
/*** spring security配置** @author Dftre*/ @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) @Configuration public class SecurityConfig {/*** 自定义用户认证逻辑*/@Autowiredprivate UserDetailsService userDetailsService;/*** 认证失败处理类*/@Autowiredprivate AuthenticationEntryPointImpl unauthorizedHandler;/*** 退出处理类*/@Autowiredprivate LogoutSuccessHandlerImpl logoutSuccessHandler;/*** token认证过滤器*/@Autowiredprivate JwtAuthenticationTokenFilter authenticationTokenFilter;/*** 跨域过滤器*/@Autowiredprivate CorsFilter corsFilter;/*** 允许匿名访问的地址*/@Autowiredprivate PermitAllUrlProperties permitAllUrl;/*** @return* @throws Exception*/@BeanAuthenticationManager authenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(userDetailsService);daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());return new ProviderManager(daoAuthenticationProvider);}/*** anyRequest | 匹配所有请求路径* access | SpringEl表达式结果为true时可以访问* anonymous | 匿名可以访问* denyAll | 用户不能访问* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问* hasRole | 如果有参数,参数表示角色,则其角色可以访问* permitAll | 用户可以任意访问* rememberMe | 允许通过remember-me登录的用户访问* authenticated | 用户登录后可访问*/@BeanSecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {return httpSecurity// CSRF禁用,因为不使用session.csrf(csrf -> csrf.disable())// 禁用HTTP响应标头.headers((headersCustomizer) -> {headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());})// 认证失败处理类.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))// 基于token,所以不需要session.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 注解标记允许匿名访问的url.authorizeHttpRequests((requests) -> {permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());// 对于登录login 注册register 验证码captchaImage 允许匿名访问requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()// 静态资源,可匿名访问.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js","/profile/**").permitAll().requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs","/druid/**", "/*/api-docs/**").permitAll().requestMatchers("/websocket/**").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();})// 添加Logout filter.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))// 添加JWT filter.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)// 添加CORS filter.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class).addFilterBefore(corsFilter, LogoutFilter.class).build();}/*** 强散列哈希加密实现*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();} }
2.3 UserDetailsServiceImpl 重写 UserDetailsService接口的loadUserbyUsername方法
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser user = userService.selectUserByUserName(username);if (StringUtils.isNull(user)) {log.info("登录用户:{} 不存在.", username);throw new ServiceException("登录用户:" + username + " 不存在");} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {log.info("登录用户:{} 已被删除.", username);throw new ServiceException("对不起,您的账号:" + username + " 已被删除");} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {log.info("登录用户:{} 已被停用.", username);throw new ServiceException("对不起,您的账号:" + username + " 已停用");}passwordService.validate(user);return createLoginUser(user); }public UserDetails createLoginUser(SysUser user) {return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); }
2.4 保存LoginUser到Redis的逻辑在创建token的方法逻辑中实现
2.5 拦截器配置
/*** token过滤器 验证token有效性* * @author ruoyi*/ @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate TokenService tokenService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException{LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())){tokenService.verifyToken(loginUser);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authenticationToken);}chain.doFilter(request, response);} }
3.授权架构设计
权限的验证最核心的是使用的Spring Security的提供的权限注解`@PreAuthorize `
当 @PreAuthorize 注解被应用于某个方法时,Spring Security 在该方法执行前会先对当前认证的用户进行权限检查。如果检查通过,方法调用得以继续;否则,框架会抛出相应的权限异常(如 AccessDeniedException),阻止方法执行。
@ss
引用了名为 "ss" 的 Spring Bean,即我们的PermissionService
hasPermi('manage:order:list')
调用了PermissionService
的hasPermi
方法,检查用户是否拥有 "manage:order:list" 权限
授权相关校验操作实现:
@Service("ss") public class PermissionService implements IPermissionService {/** 所有权限标识 */private static final String ALL_PERMISSION = "*:*:*";/** 管理员角色权限标识 */private static final String SUPER_ADMIN = "admin";private static final String ROLE_DELIMETER = ",";private static final String PERMISSION_DELIMETER = ",";/*** 验证用户是否具备某权限* * @param permission 权限字符串* @return 用户是否具备某权限*/public boolean hasPermi(String permission) {if (StringUtils.isEmpty(permission)) {return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {return false;}PermissionContextHolder.setContext(permission);return hasPermissions(loginUser.getPermissions(), permission);}/*** 验证用户是否具有以下任意一个权限** @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表* @return 用户是否具有以下任意一个权限*/public boolean hasAnyPermi(String permissions) {if (StringUtils.isEmpty(permissions)) {return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {return false;}PermissionContextHolder.setContext(permissions);Set<String> authorities = loginUser.getPermissions();for (String permission : permissions.split(PERMISSION_DELIMETER)) {if (permission != null && hasPermissions(authorities, permission)) {return true;}}return false;}/*** 判断用户是否拥有某个角色* * @param role 角色字符串* @return 用户是否具备某角色*/public boolean hasRole(String role) {if (StringUtils.isEmpty(role)) {return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {return false;}for (SysRole sysRole : loginUser.getUser().getRoles()) {String roleKey = sysRole.getRoleKey();if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {return true;}}return false;}/*** 验证用户是否具有以下任意一个角色** @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表* @return 用户是否具有以下任意一个角色*/public boolean hasAnyRoles(String roles) {if (StringUtils.isEmpty(roles)) {return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {return false;}for (String role : roles.split(ROLE_DELIMETER)) {if (hasRole(role)) {return true;}}return false;}/*** 判断是否包含权限* * @param permissions 权限列表* @param permission 权限字符串* @return 用户是否具备某权限*/private boolean hasPermissions(Set<String> permissions, String permission) {return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));} }
相关文章:

基于JWT+SpringSecurity整合一个单点认证授权机制
基于 JWT Spring Security 的授权认证机制,在整体架构设计上体现了高度的安全性与灵活性。其在整合框架中的应用,充分展示了模块化、可扩展性和高效鉴权的设计理念,为开发者提供了一种值得借鉴的安全架构模式。 1.SpringSecurity概念理解 …...

IDEA 打开文件乱码
问题:文件乱码 底部编码无法切换 解决方案: 第一步 使用Nodepad 查询文件编码 本项目设置为 转为 UTF-8 无 BOM 第二步:在 IntelliJ IDEA 中:右键点击文件 → File Encoding → 选择目标编码(如 UTF-8) 最…...

第2章:Neo4j安装与配置
在了解了Neo4j的基本概念和优势之后,下一步就是将其安装并配置好,以便开始实际操作。本章将详细介绍Neo4j的各种部署方式,涵盖不同操作系统的安装步骤,深入探讨关键配置项,并介绍常用的管理工具,为读者顺利…...

Shell 命令及运行原理 + 权限的概念(7)
文章目录 Shell 命令以及运行原理(4-1.22.08)Linux权限的概念1. 什么是权限2. 认识人(普通用户,root用户)以及两种用户的切换认识普通用户和root用户两种用户之间的切换指令提权 3. 文件的属性解析 权限属性指令ll显示…...

抽奖系统核心——抽奖管理
目录 前端逻辑: 核心全局变量: reloadConf函数: nextStep函数: 后端实现: 抽奖接口: Controller层: Service层: MqReceiver: drawPrizeService: statusConvert()方法: activityStatu…...
Android 蓝牙通信
Android 平台提供了完整的蓝牙 API,支持 传统蓝牙(Bluetooth Classic)和低功耗蓝牙(BBluetooth Low Energy, BLE)两种通信方式。 以下是开发蓝牙应用的关键知识点。 1. 基本概念 传统蓝牙(Bluetooth Classic) 适合大流量数据传输(如音频、文件传输) 典型协议: R…...
任务调度器-关于中心化调度 vs 去中心化调度的核心区别
1. 定义与架构模型 维度中心化调度去中心化调度核心角色存在一个中央调度器(如XXL-JOB的调度中心),统一管理任务分配、状态监控和故障处理。无中心节点,调度逻辑分散在多个节点,通过共识算法(如选举机制&a…...
二、【ESP32开发全栈指南:ESP32 GPIO深度使用】
GPIO(通用输入输出) 是ESP32最基础却最核心的功能。本文将带你深入ESP32的GPIO操作,通过按键读取和LED控制实现物理按键→ESP32→LED的完整信号链路。 一、ESP32 GPIO核心特性速览 34个可编程GPIO(部分引脚受限)输入模…...
力扣刷题(第四十九天)
灵感来源 - 保持更新,努力学习 - python脚本学习 反转链表 解题思路 迭代法:通过遍历链表,逐个改变节点的指针方向。具体步骤如下: 使用三个指针:prev(初始为None)、curr(初始为…...

机器学习:集成学习概念和分类、随机森林、Adaboost、GBDT
本文目录: 一、集成学习概念**核心思想:** 二、集成学习分类(一)Bagging集成(二)Boosting集成(三)两种集成方法对比 三、随机森林(一)构造过程(二…...

基于J2EE架构的在线考试系统设计与实现【源码+文档】
目录 摘要: Abstract: 1 引言 2 在线考试系统构架 2.1 在线考试系统一般需求分析 2.2 当前在线考试系统现状分析 2.3 基于J2EE的在线考试系统架构介绍及拥有的优势 2.3.1 结构总体介绍 2.3.2 客户层组件 2.3.2.1 Applets 2.3.2.2 应用程序客户端 2.3.3 …...
tpc udp http
TCP(传输控制协议)、UDP(用户数据报协议)和 HTTP(超文本传输协议)是网络通信中常用的三种协议,它们在不同的层次和场景中发挥作用。以下是对这三种协议的详细解释以及它们之间的区别:…...

联想拯救者R9000P 网卡 Realtek 8852CE Ubuntu/Mint linux 系统睡眠后,无线网卡失效
联想拯救者R9000P 网卡型号 Realtek PCle GbE Family Controller Realtek 8852CE WiFi 6E PCI-E NIC 系统版本 Ubuntu 24.04 / mint 22.1 问题现象 rtw89_8852ce,Link is Down,xtal si not ready,mac init fail,xtal si not …...

Python训练营打卡 Day46
道注意力(SE注意力) 知识点回顾: 不同CNN层的特征图:不同通道的特征图什么是注意力:注意力家族,类似于动物园,都是不同的模块,好不好试了才知道。通道注意力:模型的定义和插入的位置通道注意力后…...

解决微软应用商店 (Microsoft store) 打不开,无网络连接的问题!
很多小伙伴都会遇见微软应用商店 (Microsoft store)打开后出现无网络的问题,一般出现这种问题基本都是因为你的电脑安装了某些银行的网银工具,因为网银工具为了安全会关闭Internet 选项中的最新版本的TLS协议,而微软商店又需要最新的TLS协议才…...

《影像引导下骨盆创伤手术的术前骨折复位规划:基于学习的综合流程》|文献速递-深度学习医疗AI最新文献
Title 题目 Preoperative fracture reduction planning for image-guided pelvic trauma surgery: A comprehensive pipeline with learning 《影像引导下骨盆创伤手术的术前骨折复位规划:基于学习的综合流程》 01 文献速递介绍 《影像引导下骨盆创伤手术的术前…...

如何使用Webhook触发器,在 ONLYOFFICE 协作空间构建智能工作流
在数字化办公中,ONLYOFFICE 协作空间作为一款功能强大的文档协作平台,提供了丰富的自动化功能。对于开发者而言,Webhook 触发器是实现业务流程自动化与系统集成的关键工具。本文将深入探讨如何在 ONLYOFFICE 协作空间中高效利用 Webhook&…...
跟我学c++中级篇——理解类型推导和C++不同版本的支持
一、类型推导 在前面反复分析过类型推导(包括前面提到的类模板参数推导CTAD),类型推导其实就是满足C语言这种强类型语言的要求即编译期必须确定对象的数据类型。换一句话说,理论上如果编译器中能够自动推导所有的相关数据类型&am…...

什么是DevOps智能平台的核心功能?
在数字化转型的浪潮中,DevOps智能平台已成为企业提升研发效能、加速产品迭代的核心工具。然而,许多人对“DevOps智能平台”的理解仍停留在“自动化工具链”的表层概念。今天,我们从一个真实场景切入:假设你是某互联网公司的技术负…...

Windows账户管理,修改密码,创建帐户...(无需密码)
前言 我们使用wWindows操作系统时,账户是非常重要的概念 它不仅能够帮助我们区分文档主题权限等等 嗯还有最重要的解锁电脑的作用! 但想要管理他,不仅需要原本的密码,而且设置中的管理项也非常的不全。 Windows有一款netplwi…...
软件功能模块归属论证方法
文章目录 **一、核心设计原则****二、论证方法****三、常见决策模式****四、验证方法****五、反模式警示****总结** 在讨论软件功能点应该归属哪些模块时,并没有放之四海而皆准的固定方法,但可以通过系统化的论证和设计原则来做出合理决策。以下是常见的…...

【Java后端基础 005】ThreadLocal-线程数据共享和安全
📚博客主页:代码探秘者 ✨专栏:文章正在持续更新ing… ✅C语言/C:C(详细版) 数据结构) 十大排序算法 ✅Java基础:JavaSE基础 面向对象大合集 JavaSE进阶 Java版数据结构JDK新特性…...

【C语言】C语言经典小游戏:贪吃蛇(下)
文章目录 一、游戏前准备二、游戏开始1、游戏开始函数(GameStart)1)打印欢迎界⾯(WelcomeToGame)2)创建地图(CreateMap)3)初始化蛇⾝(InitSnake)4…...

NTT印地赛车:数字孪生技术重构赛事体验范式,驱动观众参与度革命
引言:数字孪生技术赋能体育赛事,开启沉浸式观赛新纪元 在传统体育赛事观赛模式遭遇体验天花板之际,NTT与印地赛车系列赛(NTT INDYCAR SERIES)的深度合作,通过数字孪生(Digital Twin)…...

30.【新型数据架构】-区块链数据架构
30.【新型数据架构】-区块链数据架构:分布式账本,不可篡改性,用于数据溯源 一、区块链数据架构的本质:分布式账本的革新 区块链的核心是分布式账本技术(Distributed Ledger Technology, DLT),它颠覆了传统中心化数据库的架构模式: 去中心化存储: 账本数据不再集中存储…...
使用docker 安装Redis 带配置文件(x86和arm)版本
一、安装redis 1.1 拉去ARM镜像(7.4.2) docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/linux_arm64_redis:latest1.2 拉去x86镜像(8.0.1)版本 docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/redis:latest新建文件夹 mkd…...

在CSDN发布AWS Proton解决方案:实现云原生应用的标准化部署
引言:云原生时代的部署挑战 在云原生应用开发中,基础设施管理的复杂性已成为团队面临的核心挑战。随着微服务架构的普及,每个服务可能包含数十个AWS资源(如Lambda、API Gateway、ECS集群等),传统的手动配置…...
小白的进阶之路系列之十----人工智能从初步到精通pytorch综合运用的讲解第三部分
本文将介绍Autograd基础。 PyTorch的Autograd特性是PyTorch灵活和快速构建机器学习项目的一部分。它允许在一个复杂的计算中快速而简单地计算多个偏导数(也称为梯度)。这个操作是基于反向传播的神经网络学习的核心。 autograd的强大之处在于它在运行时动态地跟踪你的计算,…...
[蓝桥杯]整理玩具
整理玩具 题目描述 小明有一套玩具,一共包含 NMNM 个部件。这些部件摆放在一个包含 NMNM 个小格子的玩具盒中,每个小格子中恰好摆放一个部件。 每一个部件上标记有一个 0 ~ 9 的整数,有可能有多个部件标记相同的整数。 小明对玩具的摆放有…...

C++11 Move Constructors and Move Assignment Operators 从入门到精通
文章目录 一、引言二、基本概念2.1 右值引用(Rvalue References)2.2 移动语义(Move Semantics) 三、移动构造函数(Move Constructors)3.1 定义和语法3.2 示例代码3.3 使用场景 四、移动赋值运算符ÿ…...