springSecurity自定义登陆接口和JWT认证过滤器

下面我会根据该流程图去自定义接口:

我们需要做的任务有:

 登陆:1、通过ProviderManager的方法进行认证,生成jwt;2、把用户信息存入redis;3、自定义UserDetailsService实现到数据库查询数据的方法。

校验:自定义一个jwt认证过滤器,其实现功能:获取token;解析token;从redis获取信息;存入SecurityContextHolder。

登陆:

图中的 5.1步骤是到内存中查询用户信息,而我们需要的是到数据库中查询。而图中查询用户信息是调用loadUserbyUsername方法实现的。

所以我们需要实现UserDetailsService接口并重写该方法:(下面案例中我用的mybatis plus实现的查询数据库)

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;//loadUserByUsername方法即为流程图中查询用户信息的方法。@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息LambdaQueryWrapper<User> queryWrapper= new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserName,username);User user = userMapper.selectOne(queryWrapper);//如果没有查询到用户if(Objects.isNull(user)){throw new RuntimeException("用户名或密码错误");}//封装为UserDetails类型返回return new LoginUser(user);}
}

我们先写好登陆功能的controller层代码:

@RestController
public class LoginController {@Autowiredprivate LoginService loginService;@PostMapping("/user/login")public ResponseResult login(@RequestBody User user){//登陆return loginService.login(user);}

 我们需要让springSecurity对该登陆接口放行,不需要登陆就能访问。在登陆service层接口中需要通过AuthenticationManager的authenticate方法进行用户认证,我们先在SecurityConfig中把AuthenticationManager注入容器。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Override@Bean //(name = "") //获取AuthenticationManager的bean,因为现在只有这一个AuthenticationManager,所以不写也没事。protected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}//放开接口@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf,csrf为跨域策略,不支持post.csrf().disable()//不通过session获取SecurityContext 前后端分离时session不可用.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()//对于登陆接口,允许匿名访问,登陆之后不允许访问,只允许匿名的用户,可以防止重复登陆。.antMatchers("/user/login").anonymous() //permitAll() 登录能访问,不登录也能访问,一般用于静态资源js等//除了上面外,所有请求需要鉴权认证.anyRequest().authenticated();//authenticated():任意用户,认证后都可访问。}}

然后我们去修改登陆接口的service层实现类代理:

@Service
public class LoginServiceImpl implements LoginService {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate RedisCache redisCache;//登陆@Overridepublic ResponseResult login(User user) {//获取AuthenticationManager的authenticate方法进行认证。//通过SecurityConfig获取AuthenticationManager//创建Authentication,第一个参数为认证主体,没有的话传用户名,第二个参数传密码UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());//需要Authentication参数(上面)Authentication authenticate = authenticationManager.authenticate(authenticationToken);//这样让ProviderManager调用UserDetailsService类中的loadUserByUsername方法完成认证//如果认证不通过,authenticate为null//认证没通过,给出提示if(Objects.isNull(authenticate)){throw new RuntimeException("登陆失败");}//认证通过,使用userid生成jwt,jwt存入ResponseResult返回//获取userIdLoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userId = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userId);Map<String,String> map = new HashMap<>();map.put("token",jwt);//完整信息存入redis,userid作keyredisCache.setCacheObject("login:"+userId,loginUser);return new ResponseResult(200,"登陆成功",map);}
}

springSecurity流程图中是通过获取AuthenticationManager的authenticate方法进行认证。通过SecurityConfig中注入的bean获取AuthenticationManager。

authenticationManager的authenticate方法需要一个Authentication实现类参数,所以我们创建一个UsernamePasswordAuthenticationToken实现类

其中的JwtUtil.createJWT(userId);方法,是我自定义的根据userId生成JWT的工具类方法:

public class JwtUtil {//有效期为public static final Long JWT_TTL = 60*60*1000L;//一个小时//设置密钥明文 。随便定义,方便记忆和使用即可,但需要长度要为4的倍数。public static final String JWT_KEY = "jyue";public static String getUUID(){String token = UUID.randomUUID().toString().replaceAll("-", "");return token;}//生成JWT//subject为token中存放的数据(json格式)public static String createJWT(String subject){JwtBuilder builder = getJwtBuilder(subject, null, getUUID());//设置过期时间return builder.compact();}public static JwtBuilder getJwtBuilder(String subject,Long ttlMillis,String uuid){SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;SecretKey secretKey=generalKey();long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if(ttlMillis ==null){ttlMillis=JwtUtil.JWT_TTL;}long expMills=nowMillis+ttlMillis;Date expDate = new Date(expMills);return Jwts.builder().setId(uuid) //唯一id.setSubject(subject) //主题 可以是JSON数据.setIssuer("jy") //签发者,随便写.setIssuedAt(now) //签发时间.signWith(signatureAlgorithm,secretKey) //使用HS256对称加密算法签名,第二个参数为密钥。.setExpiration(expDate);}public static SecretKey generalKey(){byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKeySpec key = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");return key;}public static Claims parseJWT(String jwt)throws Exception{SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}
}

redisCache也为自定义的redis工具类:

@SuppressWarnings(value = {"unchecked","rawtypes"})
@Component
public class RedisCache {@Autowiredpublic RedisTemplate redisTemplate;//缓存对象//key 缓存键值//value 缓存值public <T> void setCacheObject(final String key,final T value){redisTemplate.opsForValue().set(key,value);}//获取缓存的基本对象// key 键值// return 缓存键对应的数据public <T>T getCacheObject(final String key){ValueOperations<String,T> operation = redisTemplate.opsForValue();return operation.get(key);}
}

JWT认证:

@Component            //继承这个实现类,保证了请求只会经过该过滤器一次
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//首先需要从请求头中获取tokenString token = request.getHeader("token");//判断token是否为Nullif(!StringUtils.hasText(token)) {//token没有的话,直接放行,抛异常的活交给后续专门的过滤器。filterChain.doFilter(request, response);//响应时还会经过该过滤器一次,直接return,不能执行下面的解析token的代码。return;}//如何不为空,解析token,获得了UserIdString userId;try {Claims claims = JwtUtil.parseJWT(token);userId = claims.getSubject();} catch (Exception e) {e.printStackTrace();//token格式异常,不是正经tokenthrow new RuntimeException("token非法");}//根据UserId查redis获取用户数据String key = "login:"+userId;LoginUser loginUser = redisCache.getCacheObject(key);if(Objects.isNull(loginUser)){throw new RuntimeException("用户未登录");}//然后封装Authentication对象存入SecurityContextHolder// 因为后续的过滤器会从SecurityContextHolder中获取信息判断认证情况,而决定是否放行。// 这里用UsernamePasswordAuthenticationToken三个参数的构造函数,是因为其能设置已认证的状态(因为已经从redis中获取了信息,确认是认证的了)//第一个参数为用户信息,第三个参数为权限信息,目前还没获取,先填nullUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);//默认SecurityContextHolder是ThreadLocal线程私有的,这也是为什么上面要用UsernamePasswordAuthenticationToken三个参数的构造方法SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request,response);}
}

这样登陆后用户发送请求,后端会先从请求头中获取token,然后解析出userId,然后从redis中查询该用户详细信息。然后把用户的详细信息存入UsernamePasswordAuthenticationToken三个参数的构造函数,是因为其能设置已认证的状态(因为已经从redis中获取了信息,确认是认证的了),然后把UsernamePasswordAuthenticationToken存入SecurityContextHolder。

因为后续的过滤器会从SecurityContextHolder中获取信息判断认证情况,而决定是否放行。

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

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

相关文章

【adb】iqoo系统精简垃圾内置应用

免责声明 这个得谨慎点&#xff0c;虽然我验证过两部手机和不同版本的系统&#xff0c;但是总会有特殊的存在、 本教程来自于互联网搜集整理&#xff0c; 按照本教程造成的用户设备硬件或数据损失&#xff0c;本人概不承担任何责任&#xff0c;如您不同意此协议&#xff0c;请不…

计算机视觉:学习指南

一、引言 计算机视觉作为人工智能领域的一个重要分支&#xff0c;致力于让计算机理解和解释视觉信息&#xff0c;近年来取得了令人瞩目的进展&#xff0c;广泛应用于安防监控、自动驾驶、图像编辑、医学影像分析等众多领域。从入门到精通计算机视觉需要系统地学习一系列知识和…

汽车升级到底应不应该设置“可取消“功能

最近&#xff0c;汽车OTA&#xff08;Over-the-Air&#xff09;升级频频成为车主讨论的热点。有些车主反映&#xff0c;一些升级增加了实用功能&#xff0c;而另一些却让体验变得复杂甚至带来不便。于是&#xff0c;大家不禁发问&#xff1a;汽车升级功能究竟应不应该允许“可取…

三菱FX3uPLC输入接线注意事项

FX3u微型控制器(DC输入型)的输入根据外部接线&#xff0c;漏型输入和源型输入都可使用。 但是,一定要连接S/S端子的接线。 详细事宜请参考“FX3U系列微型控制器硬件说明手册 AC电源型的输入接线事例(FX3U-囗MR/UA1除外) DC电源型的输入接线事例 *请不要与(0V)、(24V)端子接线…

一文说清flink从编码到部署上线

引言&#xff1a;目前flink的文章比较多&#xff0c;但一般都关注某一特定方面&#xff0c;很少有一个文章&#xff0c;从一个简单的例子入手&#xff0c;说清楚从编码、构建、部署全流程是怎么样的。所以编写本文&#xff0c;自己做个记录备查同时跟大家分享一下。本文以简单的…

过滤器Filter,ajax异步请求,服务器响应的数据类型,json

1.过滤器Filter 按照过滤规则筛选出想要的资源 很多地方都需要判断是否登录&#xff0c;对每个资源进行判断&#xff0c;非常麻烦&#xff0c;可以使用过滤器在访问这些资源前进行判断。 案例&#xff1a; package com.ghx.filter;import javax.servlet.*; import javax.ser…

【网络协议栈】TCP/IP协议栈中重要协议和技术(DNS、ICMP、NAT、代理服务器、以及内网穿透)

每日激励&#xff1a;“请给自己一个鼓励说&#xff1a;Jack我很棒&#xff01;—Jack” 绪论​&#xff1a; 本章是TCP/IP网络协议层的完结篇&#xff0c;本章将主要去补充一些重要的协议和了解一些网络中常见的名词&#xff0c;具体如&#xff1a;DNS、ICMP、NAT、代理服务器…

服务器数据恢复—LINUX下各文件系统删除/格式化的数据恢复可行性分析

Linux操作系统是世界上流行的操作系统之一&#xff0c;被广泛用于服务器、个人电脑、移动设备和嵌入式系统。Linux系统下数据被误删除或者误格式化的问题非常普遍。下面北亚企安数据恢复工程师简单聊一下基于linux的文件系统&#xff08;EXT2/EXT3/EXT4/Reiserfs/Xfs&#xff0…

因果推荐CIKM24 | 通过偏好感知因果干预和反事实数据增强来提升序列推荐

论文来源&#xff1a;CIKM 24 论文链接&#xff1a;PACIFIC: Enhancing Sequential Recommendation via Preference-aware Causal Intervention and Counterfactual Data Augmentation | Proceedings of the 33rd ACM International Conference on Information and Knowledge …

如何在 Odoo18 视图中添加关联数据看板按钮 | 免费开源ERP实施诀窍

文 / 开源智造 Odoo亚太金牌服务 引言 关联数据看板按钮乃是 Odoo 当中的一项强效功能&#xff0c;它容许用户顺遂地访问相关记录&#xff0c;或者直接从模型的表单视图施行特定操作。它们为用户给予了对重要信息的疾速访问途径&#xff0c;并简化了工作流程&#xff0c;由此…

提升网站流量的关键:AI在SEO关键词优化中的应用

内容概要 在当今数字时代&#xff0c;提升网站流量已成为每个网站管理员的首要任务。而人工智能的技术进步&#xff0c;为搜索引擎优化&#xff08;SEO&#xff09;提供了强有力的支持&#xff0c;尤其是在关键词优化方面。关键词是连接用户需求与网站内容的桥梁&#xff0c;其…

腾讯图标/百并发

腾讯新图标&#xff0c;识别速度7毫秒&#xff0c; 百并发无压力

python和C++中的逻辑与/或、位与/或

在 Python 和 C 中&#xff0c;“与”和“或”的实现逻辑相似&#xff0c;但符号和使用方式有区别。 1.Python 中的与、或 与&#xff08;AND&#xff09;&#xff1a;and或&#xff08;OR&#xff09;&#xff1a;or 1.1 逻辑与、或&#xff1a; 用于布尔值&#xff08;Tr…

PR基本操作

将剪辑添加到序列 1.在项目面板中选择素材&#xff0c;右击插入或覆盖选项&#xff0c;添加的素材依指针所在位置为起点。 上图画框位置会影响素材插入的轨道。 2.直接拖动素材到对应的时间轴轨道即可 3.拖动素材到节目监视器 在此项前插入&#xff1a;在V1轨道当前指针所…

如何配置Github并在本地提交代码

前提: 可以流畅访问github, 需要一些上网技巧, 这就自行处理了 申请一个github账号 Github官网地址 首先就是邮箱注册啦, github没有对邮箱的限制, 只要是能收邮件的就ok, qq邮箱, 163等都可以使用. 然后和普通注册账号一样, 一路填写需要的信息, 验证邮箱即可. 如何新增代…

nacos服务注册流程

一、客户端自动注册实例流程 1.首先客户端需要引入服务发现包 <groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2.2.6.RELEASE</version>2. NacosServiceRegistryA…

qemu安装arm64架构银河麒麟

qemu虚拟化软件&#xff0c;可以在一个平台上模拟另一个硬件平台&#xff0c;可以支持多种处理器架构。 一、安装 安装教程&#xff1a;https://blog.csdn.net/qq_36035382/article/details/125308044 下载链接&#xff1a;https://qemu.weilnetz.de/w64/2024/ 我下载的是 …

小程序维护外包流程和费用

由于某些原因很多老板想要跟换掉小程序原来合作的开发公司&#xff0c;重新把小程序系统维护外包新的公司。小程序系统外包维护是一个涉及多个方面的过程&#xff0c;需要从需求明确、选择团队到持续优化等多个环节进行细致管理。以下就是小程序系统外包维护主要包括几个关键步…

C—指针初阶(2)

如果看完阁下满意的话&#xff0c;能否一键三连呢&#xff0c;我的动力就是大家的支持与肯定&#xff0c;冲&#xff01; 二级指针 我们先看概念以及作用&#xff1a;用来存放一级指针的地址的指针 先看例子&#xff0c;我们逐一分析 我们先分析上面那个“1” 标注那里&#x…

x64dbg 安装使用教程

x64dbg的安装与配置 x64dbg官网地址&#xff1a;https://x64dbg.com/#start x64dbg界面介绍 1.反汇编窗口 这个位置显示的是需要分析的程序的反汇编代码。在第一个区域的最左侧例如“7712EAA3”这一列就是内存地址区域&#xff0c;接着“E8 07”就是汇编指令的opcode&#xff…