SpringSecurity+JWT+Redis进行用户鉴权和接口权限的控制
系统的登录,都做些什么?
用户访问登录页时:
会发起一个获取图片验证码的请求,后端先生成一个uuid代表此次的验证码,接着生成 "a+b=?@答案" 的表达式,将@前面的内容转换成流生成图片,@后面的答案则存储到redis中,设为2分钟过期,将图片和uuid传给前端。
/*** 生成验证码*/@GetMapping("**/captchaImage")public AjaxResult getCode(HttpServletResponse response) throws IOException{AjaxResult ajax = AjaxResult.success();boolean captchaOnOff = configService.selectCaptchaOnOff();ajax.put("captchaOnOff", captchaOnOff);if (!captchaOnOff){return ajax;}// 保存验证码信息String uuid = IdUtils.simpleUUID();String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;String capStr = null, code = null;BufferedImage image = null;// 生成验证码String captchaType = RuoYiConfig.getCaptchaType();if ("math".equals(captchaType)){String capText = captchaProducerMath.createText();capStr = capText.substring(0, capText.lastIndexOf("@"));code = capText.substring(capText.lastIndexOf("@") + 1);image = captchaProducerMath.createImage(capStr);}else if ("char".equals(captchaType)){capStr = code = captchaProducer.createText();image = captchaProducer.createImage(capStr);}redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);// 转换流信息写出FastByteArrayOutputStream os = new FastByteArrayOutputStream();try{ImageIO.write(image, "jpg", os);}catch (IOException e){return AjaxResult.error(e.getMessage());}ajax.put("uuid", uuid);ajax.put("img", Base64.encode(os.toByteArray()));return ajax;}发起登录请求:
后端会先根据图片uuid从redis中取出验证码进行校验,校验通过则执行下面代码,SpringSecurity框架就会对账号密码进行一系列的过滤器进行验证和授权等,其中最重要的两个过滤器就是UsernamePasswordAuthenticationFilter负责登录认证,FilterSecurityInterceptor负责权限授权。
// 用户验证,当前登录用户的认证信息
Authentication authentication = null;// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.
authenticate(new UsernamePasswordAuthenticationToken(username, password));Spring Security大致源码:
@Override
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {...省略其他代码// 获取Spring Security的一套过滤器List<Filter> filters = getFilters(request);// 将这一套过滤器组成Spring Security自己的过滤链,并开始执行VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);vfc.doFilter(request, response);...省略其他代码
}Spring Security的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件你就掌握了Spring Security,当然我还没全部掌握,还要继续学习!
这里面我们只需要重点关注两个过滤器即可:UsernamePasswordAuthenticationFilter负责登录认证,FilterSecurityInterceptor负责权限授权。
SpringSecurity配置类
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{/*** 自定义用户认证逻辑*/@Autowiredprivate UserDetailsService userDetailsService;/*** 认证失败处理类*/@Autowiredprivate AuthenticationEntryPointImpl unauthorizedHandler;/*** 退出处理类*/@Autowiredprivate LogoutSuccessHandlerImpl logoutSuccessHandler;/*** token认证过滤器*/@Autowiredprivate JwtAuthenticationTokenFilter authenticationTokenFilter;/*** 跨域过滤器*/@Autowiredprivate CorsFilter corsFilter;/*** 解决 无法直接注入 AuthenticationManager** @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception{return super.authenticationManagerBean();}/*** anyRequest | 匹配所有请求路径* access | SpringEl表达式结果为true时可以访问* anonymous | 匿名可以访问* denyAll | 用户不能访问* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问* hasRole | 如果有参数,参数表示角色,则其角色可以访问* permitAll | 用户可以任意访问* rememberMe | 允许通过remember-me登录的用户访问* authenticated | 用户登录后可访问*/@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception{httpSecurity// CSRF禁用,因为不使用session.csrf().disable()// 认证失败处理类.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login 注册register 验证码captchaImage 允许匿名访问.antMatchers("/login", "/api/pwd/login", "/api/phone/*", "/api/email/login", "/register", "/**/captchaImage","/info/member/selectMemByName","/sys/message/getMessageCode", "/info/member","/back/oss/upload").anonymous().antMatchers(HttpMethod.GET,"/","/*.html","/**/*.html","/**/*.css","/**/*.js","/profile/**","/controller/appMenu/list").permitAll().antMatchers("/swagger-ui.html").anonymous().antMatchers("/swagger-resources/**").anonymous().antMatchers("/webjars/**").anonymous().antMatchers("/*/api-docs").anonymous().antMatchers("/druid/**").anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);// 添加JWT filterhttpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 添加CORS filterhttpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);}/*** 强散列哈希加密实现*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** 身份认证接口*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());}
}JWT生成token
先生成一个uuid代表当前登录对象(含用户的权限信息),然后将此登录对象存到redis中,key是uuid,接着JWT根据这个uuid以及签名密钥加密生成token
public String createToken(LoginUser loginUser){String token = IdUtils.fastUUID();loginUser.setToken(token);setUserAgent(loginUser);refreshToken(loginUser);Map<String, Object> claims = new HashMap<>();claims.put(Constants.LOGIN_USER_KEY, token);return createToken(claims);}JWT生成的token
以下加密的token:
eyJhbGciOiJIUzUxMiJ9.
eyJsb2dpbl91c2VyX2tleSI6IjU0ZWQwOWJmLTA4OTgtNDI5OC1hYTYzLTEyNmQ2MTNiNDk5ZSJ9.
wNI9cYMOpfSDhcECVHpF3yxVLQbefs7vJl2lRaGlBmDckIvV1U2_PrS1CyQoE53sGgDzZEXfbyeHSmqhGH0NQ
它是一个很长的字符串,中间用点(.)分隔成三个部分。
JWT生成的token包括以下三个部分:
Header(头部)
Payload(负载)
Signature(签名)
Header.Payload.Signature
Header(头部)
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{"alg":"HS256","typ":"JWT"}alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
Payload(负载)
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,还可以在这个部分自定义字段,可以将代表当前用户的uuid设置进去
{
login_user_key=2e80aa6d-d849-444d-9cbf-0f5a86d48db4
},
Signature(签名)
Signature 部分是对前两部分的签名,防止数据篡改,防止别人仿造token。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
前端保存token
客户端收到服务器返回的经JWT加密后的token后,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域(可以在前端配置代理服务器解决),所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
此后每次发请求,做些什么?
1、登录验证
用户发送请求时会经过一个前端的一个请求拦截器判断是否需要携带token,默认所有的请求都是携带token的,除了一些登录、注册的请求需要手动设置不带token。发送请求后,后端设置了一个token验证过滤器,先取出请求头中的token,然后解析这个由jwt加密后的token,接着取出token中的负载信息(也就是代表当前登录用户的uuid)。接着取出redis中登录的用户对象(含权限信息),先验证token有效期,若低于20分钟则刷新redis中的用户对象有效期(防止产生频繁登录,影响体验),最后将取出的用户对象放入SpringSecurity的安全上下文中,通过登录验证后,则执行用户原本请求的接口。若没通过验证则执行自定义的未授权处理类,返回未授权提示。
/*** token过滤器 验证token有效性** 会拦截所有的请求,进行登录认证,认证成功则放行,否则执行AuthenticationEntryPointImpl类中的方法** @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));// SpringSecurity 将当前登录用户的认证信息存到安全上下文中(SpringSecurity判断有没有登录的标志就是看安全上下文中有没有持有认证信息)SecurityContextHolder.getContext().setAuthentication(authenticationToken);}chain.doFilter(request, response);}
}
相关文章:
SpringSecurity+JWT+Redis进行用户鉴权和接口权限的控制
系统的登录,都做些什么?用户访问登录页时:会发起一个获取图片验证码的请求,后端先生成一个uuid代表此次的验证码,接着生成 "ab?答案" 的表达式,将前面的内容转换成流生成图片,后面的答案则存储到…...
七大排序(Java)
目录 一、插入排序 1. 直接插入排序 2. 希尔排序 二、选择排序 1. 直接选择排序 2. 堆排序 三、交换排序 1. 冒泡排序 2. 快速排序 四、归并排序 五、总结 一、插入排序 1. 直接插入排序 抓一张牌,在有序的牌中,找到合适的位置并且插入。 时间…...
分享一些可以快速掌握python语法的小技巧
下面是我总结的一些有助于快速掌握 Python 语法的小技巧,欢迎一起交流。 注释:在代码中添加注释可以帮助你和其他人理解代码的目的和功能。在 Python 中,使用 # 符号来添加单行注释,使用三引号 """ 或 来添加多行…...
1.FFmpeg-音视频基础
专栏介绍基于最新的FFmpeg5.1.2版本讲解学习, 跟随博主一起学习ffmpeg: 本专栏学习流程为: FFmpeg安装、...
Parasoft的自动化测试平台到底强在哪?
在如今产品迭代如此之快的大背景下,软件测试这项工作越来越被大家所重视,但是通常情况下大家都是选择在产品上线前再去做测试,这个时候就会面临很多麻烦和挑战。首先,产品已经开发好之后,体量比较大,要从哪…...
FastDDS-0.简介
FastDDS简介 eProsima Fast DDS 是 DDS (Data Distribution Service) 协议的一个C语言实现版本,该协议由 Object Management Group (OMG) 组织定义。 eProsima Fast DDS 库既提供了一个应用编程接口(API),又提供了一种通信协议&a…...
Flutter入门进阶之旅 -开源Flutter项目
开源Flutter项目 该项目为纯flutter端项目,采用aar方式寄生在原生APP中,作为APP中的一个独立模块 在业务逻辑上做到与原生APP完全隔离,Flutter端开发者,可完全不用关注原生端的业务模块 两端开发彼此业务隔离,缩小了对…...
Opencv项目实战:21 美国ASL手势识别
0、项目介绍 首先,我可以保证在这里,你并不需要多么了解深的机器学习算法,我的初衷是通过本项目,激发大家学习机器学习的动力。选择这种手势原因是因为只有24个字母,你的电脑足以带的动,虽然我只训练A、B、…...
强化学习RL 01: Reinforcement Learning 基础
目录 RL理解要点 1. RL数学基础 1.1 Random Variable 随机变量 1.2 概率密度函数 Probability Density Function(PDF) 1.3 期望 Expectation 1.4 随机抽样 Random Sampling 2. RL术语 Terminologies 2.1 agent、state 和 action 2.2 策略 policy π 2.3 奖励 reward …...
C语言之练习题合集
💗 💗 博客:小怡同学 💗 💗 个人简介:编程小萌新 💗 💗 如果博客对大家有用的话,请点赞关注再收藏 🌞 文章目录leetcode 题号:728. 自除数leetcode 题号:238.…...
sublimeText3新建文件自动添加注释头
参考: https://github.com/shiyanhui/FileHeader/blob/master/README.rst https://packagecontrol.io/packages/FileHeader https://github.com/shiyanhui/FileHeader fileheader:https://codeload.github.com/shiyanhui/FileHeader/zip/refs/heads/m…...
AndroidStudio打包HBuilderX的H5+项目为安卓App【一次过,无任何异常报错】
目录 1.查看HBuilderX的版本号 2.下载Dcloud上对应的安卓SDK 3.下载完安卓SDK后,我们解压它,注意不要放在任何有中文组成的文件夹中【是否有中文决定于你鼠标单击上面路径后,第一张图还没鼠标单击,第二张已鼠标单击,…...
【Linux】进程概念
目录 一、基本概念 二、查看进程 三、系统调用获取进程标示符 1、获取自己的PID 2、获取父进程的PID 四、创建进程 1、初识fork 2、使用fork的方式 五、进程状态 1、阻塞 2、挂起 3、R状态 4、S状态 5、D状态 6、T状态 6.1、kill指令 6.2、暂停进程与继续进程 …...
使用pyinstaller库打包exe时显示KeyError怎么办
PyInstaller是一个Python库,用于将Python应用程序转换为独立的可执行文件(executable)文件,支持多平台。它可以将Python解释器、依赖的库和脚本打包成一个单独的可执行文件,从而使应用程序可以独立运行,而无…...
k8s新增节点机器,无法拉取和推送镜像的解决方案
1、首先检查配置,查看镜像仓库是否已授权,若无授权,则进行授权。 命令:cat /etc/systemd/system/docker.service.d/docker-options.conf内容如果有这样一句就是已经授权,如果没有,就需要把这句加进去&…...
测试报告踩坑的点
测试报告作为测试人员的核心输出项,是体现自己工作价值的重要承载工具,需要我们认真对待,所以我们要重视测试报告的输出,那么在编写测试报告的时候,我们有哪些点需要注意的呢? 01 不要乱用模板 很多测试新人在编写测试…...
【Java】创建多线程的四种方式
一、方式1:继承Thread类 步骤: 创建一个继承于Thread类的子类重写Thread类的run()方法 ----> 此线程执行的操作声明在方法体中创建当前Thread子类的对象通过实例对象调用start()方法,启动线程 ----> Java虚拟机会调用run()方法 注意…...
【数据结构】队列的接口实现(附图解和源码)
队列的接口实现(附图解和源码) 文章目录队列的接口实现(附图解和源码)前言一、定义结构体二、接口实现(附图解源码)1.初始化队列2.销毁队列3.队尾入队列4.判断队列是否为空5.队头出队列6.获取队列头部元素7…...
日本知名动画公司东映动画加入 The Sandbox 元宇宙
与 Minto 合作将东映动画的 IP 呈现在元宇宙。 The Sandbox 很荣幸能与东映动画合作,与 Minto 携手在 The Sandbox 元宇宙中创建基于东映动画 IP 的相关体验。 作为日本动画的先驱,东映动画制作了日本最大和世界领先的动画作品,包括《龙珠》、…...
QuickHMI Hawk R3 Crack
基于网络的 SCADA / HMI 系统 QuickHMI Hawk R3 QuickHMI是一个 100% 基于网络的SCADA/HMI 系统。 得益于HTML5、SVG和Javascript等现代网络技术,可视化可以在任何当前浏览器和设备中显示。作为浏览器的替代品,可以使用“独立查看器”和移动应用程序。 Q…...
OpenClaw对话日志分析:优化nanobot模型交互体验
OpenClaw对话日志分析:优化nanobot模型交互体验 1. 为什么需要分析对话日志 上周我在本地部署了基于Qwen3-4B-Instruct-2507模型的nanobot实例,通过OpenClaw框架将其接入到我的日常工作中。最初几天,我发现这个轻量级模型虽然响应速度快&am…...
探索Comsol复现六角晶格光子晶体四重简并狄拉克点零折射率现象
comsol能带复现 六角晶格光子晶体四重简并狄拉克点零折射率 在光子晶体的奇妙世界里,六角晶格光子晶体因其独特的光学性质备受关注,尤其是其中的四重简并狄拉克点零折射率现象,更是充满了魅力。而Comsol作为一款强大的多物理场仿真软件&#…...
DS3231/DS3232高精度RTC驱动设计与工业时间同步实践
1. DS323x_Generic 库深度技术解析:面向工业级时间同步的嵌入式RTC驱动设计1.1 高精度时间基准的工程必要性在嵌入式系统中,时间戳的准确性直接决定系统可靠性。传统MCU内置RTC(如STM32的BKP域RTC)在-40℃~85℃工业温度范围内月漂…...
告别WSL1!手把手教你将WSL升级到WSL2,并更新Linux内核到最新版(2024保姆级教程)
2024终极指南:从WSL1无缝迁移至WSL2并升级Linux内核 如果你还在使用WSL1,可能会遇到Docker运行缓慢、文件系统操作卡顿等问题。WSL2带来了完整的Linux内核支持,性能提升显著。本文将带你完成从WSL1到WSL2的完整迁移,并确保你的Li…...
用户样式管理新范式:Stylus如何重塑跨浏览器自定义体验
用户样式管理新范式:Stylus如何重塑跨浏览器自定义体验 【免费下载链接】stylus Stylus - Userstyles Manager 项目地址: https://gitcode.com/gh_mirrors/sty/stylus 在当今多浏览器并存的互联网生态中,用户面临着网页样式个性化的核心痛点&…...
RFdiffusion 安装后别急着关!手把手带你解读生成的 .pdb 和 .trb 文件,并接入 ProteinMPNN 完成设计
RFdiffusion 实战进阶:从骨架生成到完整蛋白质设计的全流程解析 当你第一次看到 RFdiffusion 生成的 .pdb 文件时,可能会感到既兴奋又困惑——那些蓝色的骨架线条代表着什么?如何将这些抽象的结构转化为具有生物功能的蛋白质?本文…...
OrCAD Library Builder 17.2安装避坑指南:从破解失败到成功导出的完整流程
OrCAD Library Builder 17.2实战指南:从安装配置到高效建库的全流程解析 在电子设计自动化领域,OrCAD Library Builder作为Cadence生态系统中的重要工具,能够显著提升原理图符号和PCB封装库的创建效率。本文将深入剖析17.2版本的核心功能&…...
VSCode远程开发新姿势:用Remote-SSH直连Docker容器(附端口避坑指南)
VSCode远程开发新姿势:用Remote-SSH直连Docker容器(附端口避坑指南) 在云端开发时代,越来越多的工程师选择将开发环境封装在Docker容器中,以实现环境隔离和快速部署。然而,传统的SSH连接方式往往需要在终端…...
嵌入式系统链接器脚本(ld文件)详解
嵌入式系统中的链接器脚本(ld文件)深度解析1. 链接器脚本概述链接器脚本(linker script)是控制链接过程的关键文件,通常以.lds作为文件后缀名。它主要规定了如何将输入文件中的section放入输出文件,并控制输出文件内各部分在程序地址空间中的布局。每个链…...
Phi-4-Reasoning-Vision多场景:科研文献插图理解+实验数据交叉验证应用
Phi-4-Reasoning-Vision多场景:科研文献插图理解实验数据交叉验证应用 1. 项目概述 Phi-4-Reasoning-Vision是基于微软Phi-4-reasoning-vision-15B多模态大模型开发的高性能推理工具,专为双卡4090环境优化。该工具严格遵循官方SYSTEM PROMPT规范&#…...
