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.mzph.cn/pingmian/63576.shtml

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

相关文章

PowerShell install 一键部署postgres17

postgres 前言 PostgreSQL 是一个功能强大的开源对象关系数据库系统,拥有超过 35 年的积极开发经验 这为其赢得了可靠性、功能稳健性和性能的良好声誉。 通过官方文档可以找到大量描述如何安装和使用 PostgreSQL 的信息。 开源社区提供了许多有用的地方来熟悉PostgreSQL, 了…

Redis——内存淘汰策略

Redis的淘汰策略是指当Redis内存使用达到上限时&#xff0c;为了腾出空间给新的数据&#xff0c;Redis会根据预设的策略来删除一些现有的数据。以下是Redis的几种主要淘汰策略&#xff1a; 1. noeviction&#xff08;默认&#xff09; 描述&#xff1a;不淘汰任何key&#xf…

如何在 IntelliJ IDEA 中为 Spring Boot 应用实现热部署

文章目录 1. 引言2. 准备工作3. 添加必要的依赖4. 配置 IntelliJ IDEA4.1 启用自动编译4.2 开启热部署策略 5. 测试热部署6. 高级技巧7. 注意事项8. 总结 随着现代开发工具的进步&#xff0c;开发者们越来越重视提高生产力的特性。对于 Java 开发者来说&#xff0c;能够在不重启…

Spring Boot中实现JPA多数据源配置指南

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;本文详细介绍了在Spring Boot项目中配置和使用JPA进行多数据源管理的步骤。从引入依赖开始&#xff0c;到配置数据源、创建DataSource bean、定义实体和Repository&#xff0c;最后到配置事务管理器和使用多数据…

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

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

详细聊聊 Spring Cloud for Amazon Web Services

Spring Cloud for Amazon Web Services&#xff08;简称 Spring Cloud AWS&#xff09;是 Spring Cloud 生态系统中用于与亚马逊网络服务&#xff08;AWS&#xff09;集成的一个重要模块。 Spring Cloud AWS 为在 AWS 上构建和部署微服务应用提供了一系列便利的功能和集成点。…

OpenAI 12Days 第二天 强化微调(RFT):推动语言模型在科学研究中的应用

OpenAI 12Days 第二天 强化微调&#xff08;RFT&#xff09;&#xff1a;推动语言模型在科学研究中的应用 文章目录 OpenAI 12Days 第二天 强化微调&#xff08;RFT&#xff09;&#xff1a;推动语言模型在科学研究中的应用RFT的工作原理与应用领域案例研究&#xff1a;基因突变…

21个Python脚本自动执行日常任务(2)

引言 作为编程领域摸爬滚打超过十年的老手&#xff0c;我深刻体会到&#xff0c;自动化那些重复性工作能大大节省我们的时间和精力。 Python以其简洁的语法和功能强大的库支持&#xff0c;成为了编写自动化脚本的首选语言。无论你是专业的程序员&#xff0c;还是希望简化日常工…

关于信号隔离转换器

isolate converter是隔离转换器‌。它是一种在电子电路中用于实现电路隔离、电压转换或信号隔离的设备‌。隔离转换器能在很多场合发挥关键作用&#xff0c;比如可以保护电路、提高安全性&#xff0c;还能帮助不同电压或信号之间的转换与传递‌。 ‌一、产品概述‌ ‌简介‌&a…

2.linux中调度kettle

一.准备转换&#xff0c;等会在linux中用 1.添加excel输入组件&#xff0c;并添加对应的文件 2.添加列拆分为多行组件 3.添加文本文件输出组件 4.保存转换 二.linux安装java 1.把jdk-8u144-linux-x64.tar.gz上传到linux的/lx目录下 2. 解压jdk包&#xff0c;然后配置环境变量…

图的割点、割边(Tarjan算法)

深度优先搜索的利用。 在一个无向连通图中&#xff0c;如果删掉某个顶点后&#xff0c;图不再连通&#xff08;即任意两点之间不能互相到达&#xff09;&#xff0c;我们称这样的顶点为割点。 在一个无向连通图中&#xff0c;如果删掉某条边后&#xff0c;图不在连通&#xff0…

【毕业设计选题】深度学习类毕业设计选题参考 开题指导

目录 前言 毕设选题 开题指导建议 更多精选选题 选题帮助 最后 前言 大家好,这里是海浪学长毕设专题! 大四是整个大学期间最忙碌的时光&#xff0c;一边要忙着准备考研、考公、考教资或者实习为毕业后面临的升学就业做准备,一边要为毕业设计耗费大量精力。学长给大家整…

Java 在Json对象字符串中查找和提取特定的数据

1、在处理JSON数据时&#xff0c;需要提出个别字段的值&#xff0c;通过正则表达式提取特定的数据 public static void main(String[] args) {//定义多个JSON对象字符串类型&#xff0c;假设每个对象有a,b,c 字段String strJson "{\"a\":1.23,\"b\"…

HTTP 持久连接(长连接)

HTTP 持久连接&#xff08;长连接&#xff09; HTTP 持久连接&#xff08;HTTP Persistent Connections&#xff09;&#xff0c;也常被称作 HTTP 长连接&#xff0c;是 HTTP 协议中的一种重要特性&#xff0c;以下是关于它的详细介绍&#xff1a; 一、基本概念 在传统的 HTT…

杨振宁大学物理视频中黄色的字,c#写程序去掉

先看一下效果&#xff1a;&#xff08;还有改进的余地&#xff09; 我的方法是笨方法&#xff0c;也比较刻板。 1&#xff0c;首先想到&#xff0c;把屏幕打印下来。c#提供了这样一个函数&#xff1a; Bitmap bmp new Bitmap(640, 480, PixelFormat.Format32bppArgb); // 创…

数字图像处理(15):图像平移

&#xff08;1&#xff09;图像平移的基本原理&#xff1a;计算每个像素点的移动向量&#xff0c;并将这些像素按照指定的方向和距离进行移动。 &#xff08;2&#xff09;平移向量包括水平和垂直分量&#xff0c;可以表示为&#xff08;dx&#xff0c;dy&#xff09;&#xff…

基于springboot+vue实现的剧本杀管理系统(源码+L文+ppt)4-114

摘 要 剧本杀管理系统是一个综合性平台&#xff0c;为剧本杀游戏爱好者、创作者及商家提供多方位服务。系统具备用户账号管理、剧本分类、预约、评价和论坛交流等核心功能。通过这个平台&#xff0c;用户可以便捷地浏览各类剧本信息&#xff0c;根据兴趣和时间安排进行预约&a…

Android开发-----Could not install Gradle distribution from- gradle

Could not install Gradle distribution from - gradle 这个通常是因为网络原因导致的&#xff0c;即使科学上网了&#xff0c;但是仍然不行。所以需要另辟蹊径。 打开gradle-wrapper.properties 原地址&#xff1a;distributionUrlhttps\://services.gradle.org/distributio…

FPGA工作原理、架构及底层资源

FPGA工作原理、架构及底层资源 文章目录 FPGA工作原理、架构及底层资源前言一、FPGA工作原理二、FPGA架构及底层资源 1.FPGA架构2.FPGA底层资源 2.1可编程输入/输出单元简称&#xff08;IOB&#xff09;2.2可配置逻辑块2.3丰富的布线资源2.4数字时钟管理模块(DCM)2.5嵌入式块 …

MATLAB中drawnow命令的作用和使用方法

MATLAB 中&#xff0c;drawnow 是一个非常有用的命令&#xff0c;它的主要功能是在图形绘制过程中强制 MATLAB 更新当前图形窗口。本文具体说明其作用和使用方法 文章目录 功能说明使用场景使用方法示例代码运行结果 总结 功能说明 更新图形&#xff1a; drawnow 会立即绘制所有…