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…...
【C语言】寻找隐藏字母游戏
编程实现一个游戏程序,会将连续三个字母中的一个隐去,由玩家填写隐去的那个字母,如屏幕上显示A ? C,则玩家需要输入B;屏幕上显示?B C,则玩家需要输入A。记录玩家完成20次游戏的时间以及正确率。…...
【C++】list 相关接口的模拟实现
list 模拟实现回顾准备构造析构函数的构造构造方法析构方法赋值运算符重载容量相关接口元素获取元素修改相关接口push 、popinserterase清空交换迭代器 **(重点)迭代器基本概念迭代器模拟实现回顾 在上一篇博客中我们大致了解了 list 相关接口的使用方法…...
快速找到外贸客户的9种方法(建议收藏)
所有外贸企业想要做好外贸出口的头等大事,就是要快速的找到优质的外贸客户和订单,没有订单的达成,所有的努力都是图劳,还有可能会陷入一种虚假的繁荣,每天都很忙,但是没有结果。今天,小编就来分…...
TCP状态转换
欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 TCP状态转换专栏:《Linux从小白到大神》《网络编程》 TCP状态转换示意图如下 针对上面的示…...
3500年里,印度被11个文明征服
转自:3500年里,印度被11个文明征服,如今看似统一,实际上却是缝合怪 (qq.com)今天的印度是亚洲第二大国,南亚第一大国,世界第二人口大国。如果我们将时间线拉长,纵观历史的长河,就会惊…...
Java编程问题top100---基础语法系列(一)
Java编程问题top100---基础语法系列一一、Java 操作符实质二、将InputStream转换为String使用IOUtils自己写轮子三、将数组转换为List四、如何遍历map对象使用For-Each迭代entries(方法一)使用For-Each迭代keys和values(方法二)使…...
【C#基础】C# 异常处理操作
序号系列文章6【C#基础】C# 常用语句讲解7【C#基础】C# 常用数据结构8【C#基础】C# 面向对象编程文章目录前言1,异常的概念2,处理异常3,自定义异常4,编译器异常结语前言 🌷大家好,我是writer桑,…...
系统分析师---操作系统思维导图
进程管理(5星) 进程与线程:共享:内存地址空间、代码、数据、文件等不能共享:独立的cpu运行上下文和栈指针、寄存器 信号量与PV操作:信号量,一种特殊的变量分为:信号量可以表示资源数…...
Linux | Ubuntu20.04系统使用命令从移动硬盘/U盘拷贝文件到服务器上
*确认自己移动硬盘、U盘的格式,本文为exfat格式STEP1:把移动硬盘插到Ubuntu系统的主机上查看disk默认位置#查看移动硬盘/U盘在哪个位置命令 fdisk -l #查询后出现了包含电脑系统的所有硬盘查看最后的位置,我的显示为Device, 位置为 /dev/sdb1…...
【经验总结】10年的嵌入式开发老手,到底是如何快速学习和使用RT-Thread的?
【经验总结】一位近10年的嵌入式开发老手,到底是如何快速学习和使用RT-Thread的? RT-Thread绝对可以称得上国内优秀且排名靠前的操作系统,在嵌入式IoT领域一直享有盛名。近些年,物联网产业的大热,更是直接将RT-Thread这…...
国际网站怎么做/余姚网站seo运营
说明: <1>阅读本文,最好阅读之前的block文章加以理解; <2>本文内容:三种block类型的copy情况(MRC)、是否深拷贝、错误copy; 一、MRC模式下,三种block类型的copy情况 //代…...
网站备案需要什么/河北seo网络优化师
Description Linux用户和OSX用户一定对软件包管理器不会陌生。通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个软件包的安装所依赖的其…...
资讯网站模板带会员投稿功能/168推广网
新闻 《Android Studio 2.0发布稳定版》:Android Studio 2.0稳定版终于发布了,还在使用1.5版本或beta版的同学可以放心升级了。作为Google的官方IDE,Android Studio包括你构建应用时需要的所有东西,包括代码编辑器、代码分析工具、…...
wordpress 房产插件/百度投广告怎么收费
第二章 2.1 class文件的生成 java文件为源代码文件 class为程序. class文件实时修改. eclipse自动生成. project下面clean.2.2 jar文件 如何将有用的类传给别人使用. 1.把*.java文件发给对方. 2.把*.class打包发给对方.导出为jar文件. 右键export Java JAR file 2.3使用jar文…...
广西建设培训中心网站/软文自助发布平台系统
今天在用nobody跑 脚本时,报错:登录root su apache 报错: su: cannot set user id: 资源暂时不可用 以前也遇到过这种问题,apache账号突然不可用。当时找的原因是系统进程太多,kill几个进程就可以了。 根本原因是&…...
免费做网站支持绑定/市场营销
在概念艺术中,将抽象概念转化为具体视觉效果,这之间所花的时间越短越好。它适用于所有涉及概念艺术的行业,但在视频游戏行业的快节奏开发流程方面,速度尤为重要。这里将通过为独立游戏 Star Dynasties 制作的数字插图,…...