SpringSecurity + JWT实现登录认证

前置基础请参考:SpringSecurity入门-CSDN博客

配置:

pom.xml

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.5</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.23</version></dependency><!--fastjson依赖--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.33</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--jwt依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency><!--mysql + mybatis--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.5</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.0.9.RELEASE</version></dependency><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.1.2.RELEASE</version></dependency></dependencies>

application.yaml

server:port: 8080
spring:data:redis:host: 你的redisurlport: 6379password: 你的密码datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: 你的数据库urlusername: 你的用户名password: 你的密码#配置security的默认账号和密码
#  security:
#    user:
#      name: admin
#      password: admin
mybatis-plus:mapper-locations: classpath:/mapper/*.xml

一、自定义登录

1、实现UserDetailsService

@Service
public class UserDetailsServiceImpl implements UserDetailsService {private UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>().eq(User::getName, username);User user = userMapper.selectOne(queryWrapper);if(null == user){throw new RuntimeException("用户名或密码错误");}LoginUser loginUser = new LoginUser(user);return loginUser;}
}

我这里使用的是根据用户名查询,一般情况下是使用userid,这个时候可能会报异常,因为此时的密码校验方式是以明文的形式,所以我们需要将加密器注入到

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic PasswordEncoder PasswordEncoder(){return new BCryptPasswordEncoder();}
}

2、自定义统一结果返回对象

@Data
@AllArgsConstructor
public class ResponseResult {private Integer Code; //状态码private String message; //信息private Object data; //数据
}

3、自定义登录接口

@RestController
@RequestMapping("/user")
public class HelloController {@Autowiredprivate LoginService loginService;@PostMapping("/login")public ResponseResult login(@RequestBody User user){ResponseResult result = loginService.login(user);return result;}@RequestMapping("/success")public ResponseResult success(){Authentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();ResponseResult responseResult = new ResponseResult(200, "success", loginUser);return responseResult;}@RequestMapping("/fail")public String fail(){return "fail";}
}

二、使用jwt令牌

1、自定义登录方式及其实现

在这里我们需要提取出用户信息,将用户信息存入redis缓存之中,并且使用用户id生成jwt令牌,将jwt令牌封装进返回体中,调用其他接口时就可以从jwt令牌中提取出用户id,然后去redis中提取用户信息。

public interface LoginService {public ResponseResult login(User user);
}@Service
public class LoginServiceImpl implements LoginService {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate RedisTemplate<String,String> redisTemplate;@Overridepublic ResponseResult login(User user) {//获取认证方法进行用户认证AuthenticationUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getName(), user.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);//认证未通过if(ObjectUtils.isNull()){throw new RuntimeException("用户名或者密码错误");}//提取用户信息LoginUser loginuser = (LoginUser) authenticate.getPrincipal();String id = loginuser.getUser().getId().toString();//生成jwtString jwt = JwtUtils.createJWT(id);//将用户信息存入redisString jsonString = JSON.toJSONString(loginuser);redisTemplate.opsForValue().set("userId:" + id,jsonString,3, TimeUnit.MINUTES);//将jwt存入tokenHashMap<String, String> map = new HashMap<>();map.put("token",jwt);ResponseResult responseResult = new ResponseResult(200, "success", map);//返回return responseResult;}
}

jwtutils:

public class JwtUtils {//有效期为public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时//设置秘钥明文public static final String JWT_KEY = "sangeng";public static String getUUID(){String token = UUID.randomUUID().toString().replaceAll("-", "");return token;}/*** 生成jtw* @param subject token中要存放的数据(json格式)* @return*/public static String createJWT(String subject) {JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间return builder.compact();}/*** 生成jtw* @param subject token中要存放的数据(json格式)* @param ttlMillis token超时时间* @return*/public static String createJWT(String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间return builder.compact();}private 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=JwtUtils.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);return Jwts.builder().setId(uuid)              //唯一的ID.setSubject(subject)   // 主题  可以是JSON数据.setIssuer("sg")     // 签发者.setIssuedAt(now)      // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate);}/*** 创建token* @param id* @param subject* @param ttlMillis* @return*/public static String createJWT(String id, String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间return builder.compact();}public static void main(String[] args) throws Exception {
//        String jwt = createJWT("2123");
//        Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0");
//        String subject = claims.getSubject();
//        System.out.println(subject);
//        System.out.println(claims);//String jwt = createJWT("1234");Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwNjc3ZTE4NDJhMTg0MDNjYmE5MDM3ZjUwYzUwYWFlMyIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTcxNDkxMzE0NiwiZXhwIjoxNzE0OTE2NzQ2fQ.7IsMNeWewvNqoZB5Wys0cagCqv4m014DZNrNGZRjB_E");String subject = claims.getSubject();System.out.println("subject = " + subject);}/*** 生成加密后的秘钥 secretKey* @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getDecoder().decode(JwtUtils.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}}

2、webmvc和security配置

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
}@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic PasswordEncoder PasswordEncoder(){return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 禁用basic明文验证//.httpBasic().disable()// 前后端分离架构不需要csrf保护.csrf().disable()// 禁用默认登录页//.formLogin().disable()// 禁用默认登出页//.logout().disable()// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint//.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))// 前后端分离是无状态的,不需要session了,直接禁用。.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests// 允许所有OPTIONS请求//.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 允许直接访问授权登录接口.requestMatchers(HttpMethod.POST, "/user/login").permitAll()// 允许 SpringMVC 的默认错误地址匿名访问//.requestMatchers("/error").permitAll()// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")// 允许任意请求被已登录用户访问,不检查Authority.anyRequest().authenticated());//.authenticationProvider(authenticationProvider())// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter//.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}
}

3、用户信息存入SecurityContextHolder

为了简化操作流程,方便我们在编写其他接口时可以很方便的提取出用户信息,我们可以自定义过滤器,提取出用户信息,并且存入SecurityContextHodler里

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisTemplate<String,String> redisTemplate;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//从请求头中提取出用户idString jwt = request.getHeader("token");//id不存在if(!StringUtils.hasText(jwt)){filterChain.doFilter(request,response);return;}//解析jwtString id = null;try {Claims claims = JwtUtils.parseJWT(jwt);id = claims.getSubject();} catch (Exception e) {e.printStackTrace();filterChain.doFilter(request,response);}//使用用户id从redis里面提取出用户信息String jsonstring = redisTemplate.opsForValue().get("userId:" + id);LoginUser loginUser = JSON.parseObject(jsonstring, LoginUser.class);//将用户信息存入securityContextHolderUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request,response);}
}

需要添加我们自定义的过滤器,使其生效,添加.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

    @Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 禁用basic明文验证//.httpBasic().disable()// 前后端分离架构不需要csrf保护.csrf().disable()// 禁用默认登录页//.formLogin().disable()// 禁用默认登出页//.logout().disable()// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint//.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))// 前后端分离是无状态的,不需要session了,直接禁用。.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests// 允许所有OPTIONS请求//.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 允许直接访问授权登录接口.requestMatchers(HttpMethod.POST, "/user/login").permitAll()// 允许 SpringMVC 的默认错误地址匿名访问//.requestMatchers("/error").permitAll()// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")// 允许任意请求被已登录用户访问,不检查Authority.anyRequest().authenticated())//.authenticationProvider(authenticationProvider())// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}
}

三、测试

因为登录需要使用post请求,所以我们使用postman进行测试

需求:

       1、登录

localhost:8080/user/login 登陆成功返回信息里携带jwt令牌

        2、测试jwt令牌

localhost:8080/user/success携带jwt令牌提取用户信息

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle到PostgreSQL的不停机数据库迁移

1970 年&#xff0c;数据库之父 Edgar Frank Codd 发表了“数据的关系模型”论文&#xff0c;该论文为往后的关系型数据库的发展奠定了基础。1979 年&#xff0c;基于关系模型理论的数据库产品 Oracle 2 首次亮相&#xff0c;并在过去的三四十年时间里&#xff0c;横扫全球数据…

制作跳动的爱心网页效果

html <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>跳动的爱心</title> <link rel&q…

Java入门基础学习笔记7——Intellij IDEA开发工具概述、安装

之前的开发工具存在一些问题&#xff1a; 文本编辑工具&#xff1a;记事本、NotePad、EditPlus、Sublime...编写代码的时候没有错误提醒、没有智能代码提示、需要自己进行编译、执行、功能不够强大。 集成开发环境&#xff08;IDE&#xff1a;Integrated Development Environm…

U盘文件剪切丢失怎么办?揭秘原因并给出恢复方法

在日常生活和工作中&#xff0c;U盘已成为我们不可或缺的数据存储和传输工具。但有时候&#xff0c;我们在对U盘中的文件进行剪切操作时&#xff0c;会遇到文件丢失的情况。这种突如其来的数据消失往往会让人感到惊慌和困惑。那么&#xff0c;为什么U盘剪切时文件会丢失呢&…

【声呐仿真】学习记录2.5-DAVE项目部分文档大纲

【声呐仿真】学习记录2.5-DAVE项目 一、Dave Models 模型Vehicle Models 航行器模型New Underwater Vehicle 新型水下航行器Dave ROV ModelsDave Glider ModelsManipulator Models 机械臂模型UUV Simulator Examplesrexrovrexrov2desistek saga roveca_a9Light Autonomous Unde…

互动科技如何强化法治教育基地体验?

近年来&#xff0c;多媒体互动技术正日益融入我们生活的各个角落&#xff0c;法治教育领域亦不例外。步入法治教育基地&#xff0c;我们不难发现&#xff0c;众多创新的多媒体互动装置如雨后春笋般涌现&#xff0c;这些装置凭借前沿的科技手段&#xff0c;不仅极大地丰富了法制…

排序1——直接插入排序,希尔排序,选择排序,堆排序

1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录…

【MySQL】——课程平台的创建设计

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

Three.js基础练习——渲染一个立方体

1.学习内容参考了 three.js入门教程--零基础也能学会_threejs菜鸟教程-CSDN博客 本章内容包含渲染立方体&#xff0c;并配合ui工具食用~ 2.效果图 import * as THREE from three import * as dat from dat.gui import { OrbitControls } from three/addons/controls/OrbitC…

在Tiled中制作动画瓦片图

什么是瓦片图&#xff1f;瓦片图是指用图块把游戏场景评出来 工具安装链接&#xff1a;Tiled | Flexible level editor 资源下载教程 资源下载&#xff1a;Mystic Woods - 16x16 Pixel Art Asset Pack by Game Endeavor 解压后得到一些资源 新建图块集合 Tiled的安装就不介绍…

H3C DHCP快速配置指南

1 配置DHCP服务器动态分配IPv4地址 1.1 简介 本案例介绍配置接口工作在DHCP服务器模式&#xff0c;实现动态分配IPv4地址的方法。 1.2 组网需求 如1.2 图1所示&#xff0c;公司将交换机做为核心交换机&#xff0c;现在需要在核心交换机上划分3个VLAN网段&#xff0c;Ho…

从零开始写 Docker(十四)---重构:实现容器间 rootfs 隔离

本文为从零开始写 Docker 系列第十四篇&#xff0c;实现容器间的 rootfs 隔离&#xff0c;使得多个容器间互不影响。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; 核心原理&#xff1a;…

FTA、ETA和FMA相互融合——FMEA软件

免费试用FMEA软件-免费版-SunFMEA 在实际工作中&#xff0c;故障树分析&#xff08;FTA&#xff09;、事件树分析&#xff08;ETA&#xff09;和故障模式、影响及危害性分析&#xff08;FMECA&#xff09;是相互补充、相互关联的分析工具&#xff0c;它们在保障系统安全性和可…

Docker常用镜像安装

1. mysql 1.1 安装 获取镜像 docker pull mysql:8.0.30创建文件挂载目录 创建容器并运行 docker run -p 3306:3306 --name mysql8 \ -v /home/docker/mysql8/log:/var/log/mysql \ -v /home/docker/mysql8/data:/var/lib/mysql \ -v /home/docker/mysql8/mysql-files:/va…

三国杀背后的图形化编程 变量跟踪与吐槽的故事

在周末的公司里&#xff0c;卧龙凤雏等几位员工终于结束了加班任务&#xff0c;他们每个人都显现出些许疲惫之态&#xff0c;但心情还算较为轻松愉悦。突然&#xff0c;有人提议玩上几局三国杀&#xff0c;以此来让大家放松一下身心。于是乎&#xff0c;几人纷纷掏出手机&#…

QML配合VTK基本实现

采用 QT5.15 VTK9.2.0 建立QT QUICK项目 部分方法来源于 QML加载VTK main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine>#include <QQuickVTKRenderWindow.h> #include <QQuickVTKRenderItem.h> #include <vtkPolyDataMapp…

geotrust dv通配符证书800

Geotrust是成立时间较久的正规CA认证机构&#xff0c;在过去的几十年间颁发了无数的SSL证书&#xff0c;这些SSL证书被各个开发者使用&#xff0c;受到大多数浏览器的信任。而Geotrust旗下的DV通配符证书因其广泛的应用范围受到了用户的青睐。今天就随SSL盾小编了解Geotrust旗下…

R2S+ZeroTier+Trilium

软路由使用ZeroTier搭建远程笔记 软路由使用ZeroTier搭建远程笔记 环境部署 安装ZeroTier安装trilium 环境 软路由硬件&#xff1a;友善 Nanopo R2S软路由系统&#xff1a;OpenWrt&#xff0c;使用第三方固件nanopi-openwrt。内网穿透&#xff1a;ZeroTier。远程笔记&…

人脸识别技术在访客管理中的应用

访客办理体系&#xff0c;能够使用于政府、戎行、企业、医院、写字楼等众多场所。在办理时&#xff0c;需求对来访人员身份进行精确认证&#xff0c;才能保证来访人员的进入对被访单位不被外来风险入侵。在核实身份时&#xff0c;比较好的方法就是选用人脸辨认技能&#xff0c;…

苹果电脑怎么清内存?2024有哪些好用的工具?

在使用苹果电脑的过程中&#xff0c;我们可能会遇到系统运行缓慢、程序响应迟缓或频繁出现应用程序崩溃的情况&#xff0c;这些问题很可能是由于内存占用过高所导致。内存&#xff0c;或称为RAM&#xff08;RandomAccessMemory&#xff09;&#xff0c;是计算机的临时存储区&am…