【SpringSecurity】十一、SpringSecurity集成JWT实现token的方法与校验

文章目录

  • 1、依赖与配置
  • 2、JWT工具类
  • 3、认证成功处理器
  • 4、创建JWT过滤器
  • 5、安全配置类

1、依赖与配置

添加JWT的maven依赖:

<!-- 添加jwt的依赖 -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.11.0</version>
</dependency>

application.yaml中配置密钥的值,方便代码中引用和后续更改:

jwt:secretKey: mykey

2、JWT工具类

这里的命名改为JWTService好点,Utils命名似乎偏静态方法一点。

@Component
@Slf4j
public class JwtUtils {//算法密钥@Value("${jwt.secretKey}")private String jwtSecretKey;/*** 创建jwt** @param userInfo 用户信息* @param authList 用户权限列表* 根据登录用户的数据库信息和权限信息,加上服务端密钥,创建token* @return 返回jwt(JSON WEB TOKEN)*/public String createToken(String userInfo, List<String> authList) {//创建时间Date currentTime = new Date();//过期时间,5分钟后过期Date expireTime = new Date(currentTime.getTime() + (1000 * 60 * 5));//jwt的header信息Map<String, Object> headerClaims = new HashMap<>();headerClaims.put("type", "JWT");headerClaims.put("alg", "HS256");//创建jwtreturn JWT.create().withHeader(headerClaims) // 头部信息.withIssuedAt(currentTime) //已注册声明:签发日期,发行日期.withExpiresAt(expireTime) //已注册声明 过期时间.withIssuer("llg")  //已注册声明,签发人.withClaim("userInfo", userInfo) //私有声明,可以自己定义.withClaim("authList", authList) //私有声明,可以自定义.sign(Algorithm.HMAC256(jwtSecretKey)); // 签名,使用HS256算法签名,并使用密钥//HS256是一种对称算法,这意味着只有一个密钥,在双方之间共享。 使用相同的密钥生成签名并对其进行验证。 应特别注意钥匙是否保密。}/*** 验证jwt的签名,简称验签* @param token 需要验签的jwt* @return 验签结果*/public boolean verifyToken(String token) {//获取验签类对象JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();try {//验签,如果不报错,则说明jwt是合法的,而且也没有过期DecodedJWT decodedJWT = jwtVerifier.verify(token);return true;} catch (JWTVerificationException e) {//如果报错说明jwt 为非法的,或者已过期(已过期也属于非法的)log.error("验签失败:{}", token);e.printStackTrace();}return false;}/*** 从token中获取用户信息* 这个userInfo是创建token时我自己塞进去的* @param token jwt* @return 用户信息*/public String getUserInfo(String token) {//创建jwt验签对象JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();try {//验签(主要为了同时的获取解析的结果)DecodedJWT decodedJWT = jwtVerifier.verify(token);//获取payload中userInfo的值,并返回return decodedJWT.getClaim("userInfo").asString();} catch (JWTVerificationException e) {e.printStackTrace();}return null;}/*** 获取用户权限** @param token* @return*/public List<String> getUserAuth(String token) {//创建jwt验签对象JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();try {//验签(主要为了同时的获取解析的结果)DecodedJWT decodedJWT = jwtVerifier.verify(token);//获取payload中的自定义数据authList(权限列表),并返回return decodedJWT.getClaim("authList").asList(String.class);} catch (JWTVerificationException e) {e.printStackTrace();}return null;}}

再贴一下下统一结果类的定义:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class HttpResult implements Serializable {private Integer code; //响应码private String msg; //响应消息private Object data; //响应对象
}

下面是安全用户类,用于在数据库的用户对象类SysUser和返给框架的官方对象类UserDetails之间做过渡转换。UserDetails <====> SecurityUser <====> SysUser

@Setter
public class SecurityUser implements UserDetails {private  final SysUser sysUser;private List<SimpleGrantedAuthority> simpleGrantedAuthorities;public SecurityUser(SysUser sysUser) {this.sysUser=sysUser;}public SysUser getSysUser() {return sysUser;
}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return simpleGrantedAuthorities;}@Overridepublic String getPassword() {String userPassword=this.sysUser.getPassword();//注意清除密码this.sysUser.setPassword(null);return userPassword;}@Overridepublic String getUsername() {return sysUser.getUsername();}@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired().equals(1);}@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked().equals(1);}@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired().equals(1);}@Overridepublic boolean isEnabled() {return sysUser.getEnabled().equals(1);}
}

3、认证成功处理器

自定义处理器,实现AuthenticationSuccessHandler,当用户登录认证成功后,会执行这个处理器,即认证成功处理器

/*** 认证成功处理器,当用户登录成功后,会执行此处理器*/
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {//使用此工具类进行序列化@Resourceprivate ObjectMapper objectMapper;@Resourceprivate JwtUtils jwtUtils;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {//从认证对象中获取认证用户信息//查看前面第六章的UserDetailsService接口的loadUserByUsername方法,//返回给框架的是一个自定义的SecurityUser对象(Security实现了UserDetails)SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();//从SecurityUser中拿出和底层MYSQL挂钩的SysUser类信息String userInfo=objectMapper.writeValueAsString(securityUser.getSysUser());List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) securityUser.getAuthorities();//List<SimpleGrantedAuthority>转List<String>List<String> authList=new ArrayList<>();for (SimpleGrantedAuthority authority : authorities) {authList.add(authority.getAuthority());}//也可使用stream流代替上面的for循环List<String> authList = authorities.stream().map(a -> {return a.getAuthority();}).collect(Collectors.toList());//也可使用stream流+Lambda表达式List<String> authList = authorities.stream().map(SimpleGrantedAuthority::getAuthority).collect(Collectors.toList());// 调用前面的JWT工具类方法创建jwtString token = jwtUtils.createToken(userInfo,authList);//返回给前端token@Builder模式创建对象HttpResult httpResult = HttpResult.builder().code(200).msg("OK").data(token).build();response.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=utf-8");PrintWriter writer = response.getWriter();writer.write(objectMapper.writeValueAsString(httpResult));writer.flush();}
}

4、创建JWT过滤器

定义JWT过滤器,用来检验一个个对接口的请求中token是否合法,注意放行登录接口:

/*** 定义一次性请求过滤器*/
@Component
@Slf4j
public class JwtCheckFilter extends OncePerRequestFilter {@Resourceprivate ObjectMapper objectMapper;@Resourceprivate JwtUtils jwtUtils;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取请求uriString requestURI = request.getRequestURI();// 如果是登录页面,放行if (requestURI.equals("/login")) {filterChain.doFilter(request, response);return;}//获取请求头中的Authorization,前端一般这么传,key为AuthorizationString authorization = request.getHeader("Authorization");//如果Authorization为空,那么不允许用户访问,直接返回if (!StringUtils.hasText(authorization)) {printFront(response, "没有登录!");return;}//Authorization 去掉头部的Bearer 信息,获取token值String jwtToken = authorization.replace("Bearer ", "");//验签boolean verifyTokenResult = jwtUtils.verifyToken(jwtToken);//验签不成功if (!verifyTokenResult) {printFront(response, "jwtToken 已过期");return;}//到这儿算是验证通过,但还没结束,还要将信息填充后返给SpringSecurity框架//从payload中获取userInfoString userInfo = jwtUtils.getUserInfo(jwtToken);//从payload中获取授权列表List<String> userAuth = jwtUtils.getUserAuth(jwtToken);//在认证成功处理器中,创建token时,userInfo里放的是SysUser对象的序列化字符串,这里反序列化SysUser sysUser = objectMapper.readValue(userInfo, SysUser.class);SecurityUser securityUser = new SecurityUser(sysUser);//设置权限List<SimpleGrantedAuthority> authList = userAuth.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());securityUser.setAuthorityList(authList);//填充信息UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToke = new UsernamePasswordAuthenticationToken(securityUser, null, authList);//通过安全上下文设置认证信息SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToke);//继续访问相应的rul等filterChain.doFilter(request, response);}/*** 定义一个通过response向前端返回数据的方法* 这里不是controller层,不是你直接返回个结果类就行的,注意区别*/private void printFront(HttpServletResponse response, String message) throws IOException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=utf-8");PrintWriter writer = response.getWriter();HttpResult httpResult = new HttpResult();httpResult.setCode(401);httpResult.setMsg(message);writer.print(objectMapper.writeValueAsString(httpResult));writer.flush();}
}

上面的过滤器中,除了正常的验签,最后的消息填充与保存在安全上下文,就是下图中的第十步:

在这里插入图片描述

5、安全配置类

修改下安全配置类,把上面的处理器和过滤器加进来。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@Resourceprivate JwtCheckFilter jwtCheckFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {//先看token是否合法,再走框架的用户名密码校验过滤器http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);//要是有之间的验证码校验,则它应该在token校验之前//认证通过后,走认证成功处理器,颁发tokenhttp.formLogin().successHandler(myAuthenticationSuccessHandler).permitAll();//简单按接口加个权限要求http.authorizeRequests().mvcMatchers("/student/**").hasAnyAuthority("student:query","student:update").anyRequest().authenticated(); //任何请求均需要认证(登录成功)才能访问http.csrf().disable();  //跨域//禁用session方式http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}}

效果:
在这里插入图片描述

登录认证后返给前端token:

在这里插入图片描述

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

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

相关文章

多线程场景下谨慎使用@Transactional注解,你不信我也没办法

最近遇到一个很诡异的bug&#xff0c;觉得很有趣也很值得分享&#xff0c;于是想写篇文章记录下来&#xff0c;希望有缘人看到以后少踩坑~ 先简单说下场景&#xff1a;有个任务平台&#xff0c;功能很多但我们只关注 提交任务和取消任务 两个功能&#xff0c;并且取消任务后会有…

K210-CanMV IDE开发软件

K210-CanMV IDE开发软件 界面功能简介连接设备临时运行开机运行程序 界面功能简介 区域①菜单栏&#xff1a;操作文件&#xff0c;使用工具等。 区域②快捷按钮&#xff1a;区域①中的文件和编辑中部分功能的快捷方式。 区域③连接设备&#xff1a;连接设备和程序控制按钮。 …

【网络安全带你练爬虫-100练】第20练:数据处理-并写入到指定文档位置

目录 一、目标1&#xff1a;解码去标签 二、目标2&#xff1a;提取标签内内容 三、目标3&#xff1a;处理后的数据插入原位置 四、目标4&#xff1a;将指定的内容插入指定的位置 五、目标5&#xff1a;设置上下文字体格式 六、目标6&#xff1a;向多个不同位置插入不同的…

PMP是什么?项目管理专业人士资格认证介绍

PMP认证旨在评估和确认具备一定经验和知识的项目管理专业人士的能力。通过获得PMP认证&#xff0c;项目经理可以证明他们具备在各个行业中成功领导和管理项目所需的技能。这些技能包括十二原则、8大绩效等方面的知识。 以下是PMP认证的详细介绍&#xff1a; 1. 资格要求&…

详解TCP/IP的三次握手和四次挥手

文章目录 前言一、TCP/IP协议的三次握手1.1 三次握手流程 二、TCP/IP的四次挥手2.1 四次挥手流程 三、主要字段3.1、标志位&#xff08;Flags&#xff09;3.2、序号&#xff08;sequence number&#xff09;3.3、确认号&#xff08;acknowledgement number&#xff09; 四、状态…

PAT 1164 Good in C 测试点3,4

个人学习记录&#xff0c;代码难免不尽人意。 When your interviewer asks you to write “Hello World” using C, can you do as the following figure shows? Input Specification: Each input file contains one test case. For each case, the first part gives the 26 …

手写Mybatis:第9章-细化XML语句构建器,完善静态SQL解析

文章目录 一、目标&#xff1a;XML语句构建器二、设计&#xff1a;XML语句构建器三、实现&#xff1a;XML语句构建器3.0 引入依赖3.1 工程结构3.2 XML语句构建器关系图3.3 I/O资源扫描3.4 SQL源码3.4.1 SQL对象3.4.2 SQL源码接口3.4.3 原始SQL源码实现类3.4.4 静态SQL源码实现类…

《TCP/IP网络编程》阅读笔记--地址族和数据序列

目录 1--IP地址和端口号 2--地址信息的表示 3--网络字节序与地址变换 4--网络地址的初始化与分配 5--Windows部分代码案例 1--IP地址和端口号 IP 地址分为两类&#xff1a; ① IPv4 表示 4 字节地址族&#xff1b; ② IPv6 表示 16 字节地址族&#xff1b; IPv4 标准的 4 …

B081-Lucene+ElasticSearch

目录 认识全文检索概念lucene原理全文检索的特点常见的全文检索方案 Lucene创建索引导包分析图代码 搜索索引分析图代码 ElasticSearch认识ElasticSearchES与Kibana的安装及使用说明ES相关概念理解和简单增删改查ES查询DSL查询DSL过滤 分词器IK分词器安装测试分词器 文档映射(字…

使用ELK收集解析nginx日志和kibana可视化仪表盘

文章目录 ELK生产环境配置filebeat 配置logstash 配置 kibana仪表盘配置配置nginx转发ES和kibanaELK设置账号和密码 ELK生产环境配置 ELK收集nginx日志有多种方案&#xff0c;一般比较常见的做法是在生产环境服务器搭建filebeat 收集nginx的文件日志并写入到队列&#xff08;k…

uniapp - 倒计时组件-优化循环时间倒计时

使用定时器的规避方法 为了避免定时器误差导致倒计时计算错误&#xff0c;可以采用一些规避方法&#xff0c;比如将倒计时被中断时的剩余时间记录下来&#xff0c;重新开启定时器时再将这个剩余时间加到新的计算中。同时&#xff0c;为了避免定时器延迟&#xff0c;可以在每次执…

Python数据分析实战-Series转DataFrame并将index设为新的一列(附源码和实现效果)

实现功能 Series转DataFrame并将index设为新的一列 实现代码 import pandas as pd# 创创建series series pd.Series([1, 2, 3, 4, 5])# 创建一个DataFrame对象 data {column_name: series} df pd.DataFrame(data)# 重新设置索引&#xff0c;将原有的索引作为新的一列 df.r…

GIT实战篇,教你如何使用GIT可视化工具

系列文章目录 手把手教你安装Git&#xff0c;萌新迈向专业的必备一步 GIT命令只会抄却不理解&#xff1f;看完原理才能事半功倍&#xff01; 快速上手GIT命令&#xff0c;现学也能登堂入室 GIT实战篇&#xff0c;教你如何使用GIT可视化工具 系列文章目录一、GIT有哪些常用工具…

2023高教社杯数学建模A题B题C题D题E题思路模型 国赛建模思路分享

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 全国大学生数学建模…

如何截取视频中的一段视频?分享几种视频分割方法

当处理长视频时&#xff0c;视频分割可以使您更加高效。如果您只需要处理其中的一部分&#xff0c;而不是整个视频&#xff0c;那么分割视频可以使您更容易找到需要处理的部分。而且&#xff0c;分割视频还可以使您更容易在不同的项目之间重复使用视频片段。教大家几种简单的视…

基于Python的IOS自动化测试环境搭建

文章目录 一、测试架构介绍1.1 WebDriverAgent原理分析1.2 tidevice原理分析二、环境安装2.1 iOS 设备安装 WebDriverAgent2.2 安装iTunes2.3 安装tidevice2.4 安装facebook-wda自动化三、操作流程四、Weditor的安装和使用一、测试架构介绍 以下为测试架构原理图 手机端的WDA…

【vue2第十二章】ref和$refs获取dom元素 和 vue异步更新与$nextTick使用

ref和$refs获取dom元素 为什么会有 ref 和 $refs&#xff1f; 因为在vue页面中使用dom查找元素&#xff0c;不管你是不是在子组件里面查找&#xff0c;查找的都是整个页面的元素&#xff0c;如果你想查找单独组件里面的元素是不容易实现的&#xff0c;除非把每个组件的class写…

【Java转Go】Go中使用WebSocket实现聊天室(私聊+群聊)

目录 前言功能效果&#xff08;一人分饰多角.jpg&#x1f60e;&#xff09;用户上线、群聊私聊和留言下线 实现思路代码服务端 chat.go 完整代码客户端 html 完整代码 最后 前言 之前在Java中&#xff0c;用 springbootwebsocket 实现了一个聊天室&#xff1a;springbootwebso…

【广州华锐互动】利用AR远程指导系统进行机械故障排查,实现远程虚拟信息互动

随着工业自动化和智能化的不断发展&#xff0c;机械故障诊断已经成为了工业生产中的重要环节。为了提高故障诊断的准确性和效率&#xff0c;近年来&#xff0c;AR&#xff08;增强现实&#xff09;远程协助技术逐渐应用于机械故障诊断领域。本文将探讨AR远程协助技术在机械故障…

华为数通方向HCIP-DataCom H12-821题库(单选题:201-220)

第201题 BGP 协议用​​ beer default-route-advertise​​ 命令来给邻居发布缺省路由,那么以下关于本地 BGP 路由表变化的描述&#xff0c;正确的是哪一项? A、在本地 BGP 路由表中生成一条活跃的缺省路由并下发给路由表 B、在本地 BGP 路由表中生成一条不活跃的缺省路由&…