在 Spring Boot 中使用 Spring Security + JWT + MySQL 实现基于 Token 的身份认证

文章目录

  • 在 Spring Boot 中使用 Spring Security + JWT + MySQL 实现基于 Token 的身份认证
    • 一、引言
    • 二、环境搭建
      • 1、第一步:引入依赖
      • 2、第二步:配置MySQL数据库
    • 三、实现身份认证
    • 三、实现身份认证
      • 1、定义实体和数据访问层
        • 1.1、实体类定义
        • 1.2、数据访问层
      • 2、JWT工具类
        • 2.1、JwtTokenProvider类
      • 3、Spring Security配置
        • 3.1、配置WebSecurityConfigurerAdapter
        • 3.2、用户详情服务
      • 4、登录和认证接口
        • 4.1、登录接口
        • 4.2、AuthService类
        • 4.3、过滤器
    • 四、总结

在 Spring Boot 中使用 Spring Security + JWT + MySQL 实现基于 Token 的身份认证

一、引言

在现代Web应用中,安全是一个核心考虑因素。随着微服务架构和前后端分离模式的流行,传统的会话管理(基于Cookie和Session)已不再满足需求。基于Token的身份认证机制因其无状态、可扩展性高和适用于分布式系统等优点,逐渐成为主流。本文将介绍如何在Spring Boot应用中整合Spring Security、JWT和MySQL,实现基于Token的身份认证机制。

二、环境搭建

1、第一步:引入依赖

在Spring Boot项目中,首先需要引入必要的依赖。以下是pom.xml中需要添加的依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

2、第二步:配置MySQL数据库

创建一个名为login_system的数据库,并在application.properties中配置数据库连接信息:

spring.datasource.url=jdbc:mysql://localhost:3306/login_system
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update

三、实现身份认证

三、实现身份认证

1、定义实体和数据访问层

1.1、实体类定义

首先,定义UserRole实体类,并在它们之间建立多对多关系。使用JPA注解来标注这些类和字段。

User实体:

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;import java.util.Set;@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;@Column(nullable = false, unique = true)private String username;@Column(nullable = false, unique = true)private String email;@Column(nullable = false)private String password;@ManyToMany(fetch = FetchType.EAGER)@JoinTable(name = "users_roles",joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))private Set<Role> roles;
}

Role实体:

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;@Entity
@Table(name = "roles")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Role {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;
}
1.2、数据访问层

UserRole创建相应的JPA仓库接口。

UserRepository接口:

import org.springframework.data.jpa.repository.JpaRepository;import java.util.Optional;public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUsername(String username);Optional<User> findByEmail(String email);Boolean existsByEmail(String email);Boolean existsByUsername(String username);
}

RoleRepository接口:

import org.springframework.data.jpa.repository.JpaRepository;import java.util.Optional;public interface RoleRepository extends JpaRepository<Role, Long> {Optional<Role> findByName(String name);
}

2、JWT工具类

2.1、JwtTokenProvider类

创建JwtTokenProvider类,用于生成和解析JWT Token。使用java-jwt库来实现JWT的创建和验证。

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.function.Function;@Component
public class JwtTokenProvider {@Value("${app.jwt-secret}")private String jwtSecret;@Value("${app.jwt-expiration-milliseconds}")private Long jwtExpirationInMs;public String generateToken(String username) {return generateToken(username, null);}public String generateToken(String username, Date expiration) {return Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(expiration != null ? expiration : expirationDate()).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();}public String extractUsername(String token) {return extractClaim(token, Claims::getSubject);}public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {final Claims claims = extractAllClaims(token).getBody();return claimsResolver.apply(claims);}private Claims extractAllClaims(String token) {try {return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();} catch (JwtException | IllegalArgumentException e) {throw new IllegalArgumentException("Expired or invalid JWT token");}}public Boolean validateToken(String token) {try {extractAllClaims(token);return true;} catch (Exception e) {return false;}}private Date expirationDate() {return new Date(System.currentTimeMillis() + jwtExpirationInMs);}
}

3、Spring Security配置

3.1、配置WebSecurityConfigurerAdapter

创建一个配置类,继承WebSecurityConfigurerAdapter,重写方法以配置Spring Security。

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/api/auth/**").permitAll().anyRequest().authenticated().and().addFilter(new JwtAuthenticationFilter(authenticationManager())).addFilter(new JwtAuthorizationFilter(authenticationManager())).exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint());}
}
3.2、用户详情服务

实现UserDetailsService接口,用于从数据库加载用户信息。

import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.stream.Collectors;@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()));}
}

4、登录和认证接口

4.1、登录接口

创建一个登录接口,用户可以通过提供用户名和密码来获取JWT Token。

@RestController
@RequestMapping("/api/auth")
public class AuthController {@Autowiredprivate AuthService authService;@PostMapping("/login")public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthRequest authenticationRequest) {final String jwt = authService.login(authenticationRequest.getUsername(), authenticationRequest.getPassword());return ResponseEntity.ok(new AuthResponse(jwt));}
}
4.2、AuthService类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;@Service
public class AuthService {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtTokenProvider jwtTokenProvider;public String login(String username, String password) {Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));return jwtTokenProvider.generateToken(authentication.getName());}
}
4.3、过滤器

实现两个过滤器,JwtAuthenticationFilter用于处理登录请求,生成JWT;JwtAuthorizationFilter用于验证后续请求中的JWT。

JwtAuthenticationFilter:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate JwtTokenProvider tokenProvider;@Autowiredprivate CustomUserDetailsService userDetailsService;@Autowiredprivate AuthenticationManager authenticationManager;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {final String authorizationHeader = request.getHeader("Authorization");String username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {jwt = authorizationHeader.substring(7);username = tokenProvider.extractUsername(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (tokenProvider.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authToken);}}chain.doFilter(request, response);}
}

JwtAuthorizationFilter:

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class JwtAuthorizationFilter extends OncePerRequestFilter {@Autowiredprivate JwtTokenProvider tokenProvider;@Autowiredprivate CustomUserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {final String authorizationHeader = request.getHeader("Authorization");String username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {jwt = authorizationHeader.substring(7);username = tokenProvider.extractUsername(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (token
Provider.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authToken);}}chain.doFilter(request, response);}
}

这样,我们就完成了基于Token的身份认证机制的实现。用户登录时,系统会生成一个JWT,用户需在随后的请求中携带此JWT进行身份验证。通过这种方式,我们可以确保应用的安全性和可扩展性。

四、总结

通过上述步骤,我们成功在Spring Boot应用中整合了Spring Security、JWT和MySQL,实现了基于Token的身份认证机制。这种机制不仅提高了应用的安全性,还增强了其可扩展性和维护性。在微服务和分布式系统中,基于Token的身份认证是推荐的做法。


版权声明:本博客内容为原创,转载请保留原文链接及作者信息。

参考文章

  • Springdoc - Spring Boot, Spring Security, JWT and MySQL
  • CSDN - 【全网最细致】SpringBoot整合Spring Security + JWT实现用户认证

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

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

相关文章

华为OD机试 - 端口合并(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

Web后端服务平台解析漏洞与修复、文件包含漏洞详解

免责申明 本文仅是用于学习检测自己搭建的Web后端服务平台解析漏洞、文件包含漏洞的相关原理,请勿用在非法途径上,若将其用于非法目的,所造成的一切后果由您自行承担,产生的一切风险和后果与笔者无关;本文开始前请认真详细学习《‌中华人民共和国网络安全法》‌及其所在国…

mysql怎样优化count(*) from 表名 where …… or ……这种慢sql

一 问题描述 线上发现一条类似这样的慢sql&#xff08;查询时长8s&#xff09;&#xff1a; select id,name,(select count(*) from t14 where t14.idt15.id or t14.id2t15.id) as cnt from t15 ; t14的id和id2字段上都有索引&#xff0c;但是因为条件里有or&#xff0c;导致…

电路设计学习(一)

FUSB302BUCX 可编程 USB Type-C 控制器&#xff0c;带 PD&#xff08;默认 SNK&#xff09; FUSB302BUCX 是一款由 ON Semiconductor 生产的 USB Type-C 控制器&#xff0c;用于实现 USB Type-C 和 USB Power Delivery (PD) 协议。它主要负责 USB Type-C 端口的检测、CC 引脚…

GESP C++二级样题卷

一、单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 1.目前主流的计算机储存数据最终都是转换成&#xff08; &#xff09;数据进行储存。 ​ A&#xff0e;二进制 ​ B&#xff0e;十进制 ​ C&#xff0e; 八进制 ​ D&#xff0e;十六进制 2.已知大写字…

结构开发笔记(八):solidworks软件(七):装配图中让摄像头绕轴旋转起来

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/142176639 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

量化交易backtrader实践(二)_基础加强篇(3)_策略类实践进阶

通过前面几节的实践&#xff0c;我们已经对股票数据的获取&#xff0c;pandasData数据的格式处理&#xff0c;到bactrader的DATAS结构以及里面的data的数据结构&#xff0c;以及在init和next函数中如何读取和计算都有了比较清晰的认知。我们通过一个最简的回测系统&#xff0c;…

Java 读取特定目录下子文件夹的 json格式文件并解析

一、需求   有一个目录结构&#xff0c;包含多个子文件夹&#xff0c;每个子文件夹中都有一个名为goods.txt的文件&#xff0c;文件内容以 JSON 格式存储。现在需要将所有的goods.txt文件内容读取出来&#xff0c;放在一个List集合中&#xff0c;以便进行后续的处理。 二、使…

Pre-training、Post-training、Continue training的区别

在训练大语言模型时&#xff0c;大家可能会搞混以下几个概念&#xff0c;下面做一个详细的对比区分&#xff1a; 概念Pre-training&#xff08;预训练&#xff09;Post-training&#xff08;后训练&#xff09;Continue Training&#xff08;持续训练&#xff09;定义预训练是…

C# 结合 Javascript 测试获取天气信息

目录 测试效果 范例运行环境 关键代码 C#获取网页数据 前端代码 JavaScript 实现 总结 测试效果 获取一些简单的天气信息&#xff0c;可以丰富我们的应用系统&#xff0c;比如开发一个小桌面&#xff0c;小组件&#xff0c;增加一些实用性的系统功能&#xff0c;本文将…

pip清华源地址

一、pip清华源地址 https://pypi.tuna.tsinghua.edu.cn/simple 二、清华源使用方法 pip install package_name -i https://pypi.tuna.tsinghua.edu.cn/simple 三、将清华源设置为默认源&#xff1a; pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/si…

neo4j安装为服务+配置环境变量

目录 neo4j安装为服务 windows services 参照JDK&#xff0c;将neo4j加入到环境变量 neo4j安装为服务 windows services 我的上一篇文章详细写明了如何安装启动neo4j《neo4j安装启动教程对应的jdk配置》&#xff0c;文末的启动neo4j是通过cmd命令行访问bin目录&#xff0c;这…

Git+Jenkins 基本使用(Basic Usage of Git+Jenkins)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

Java企业面试题2

1.语言的分代&#xff1a; 第1代&#xff1a;机器语言 机器语言是最底层的计算机编程语言&#xff0c;它是由二进制数构成的一系列指令&#xff0c;直接与计算机硬件交互。每个二进制位模式代表一条特定的指令或数据地址。因为它是直接在硬件上执行的&#xff0c;所以运行效率…

大尺寸卷积核的优缺点

大尺寸卷积核在卷积神经网络中具有一些独特的优缺点&#xff0c;具体如下&#xff1a; 优点&#xff1a; 更大的感受野&#xff1a; 大尺寸卷积核能够捕获更大的上下文信息&#xff0c;拥有更广的感受野。它可以一次性处理更大区域的特征信息&#xff0c;尤其在处理复杂的视觉…

诚邀见证2024九章云极DataCanvas算力包产品发布会!

算力&#xff0c;是驱动全球智能化升级的关键力量&#xff0c;也是智算经济腾飞的主要燃料。 在智算经济腾起之际&#xff0c;我们洞察未来:算力不仅是生产力还将作为社会性普惠AI资源。我们思考未来:算力产品和模式需要何种创新才能够加速算力普惠的进程?我们定义未来:用单位…

react学习笔记一:react介绍

将view规划成一个个的组件&#xff0c;是一个响应式的声明式的设计。 虚拟dom&#xff0c;减少dom操作。vue的虚拟dom是在react的基础上拓展来的。 单向数据流&#xff1a;是一种数据流动的模式。数据流的方向是有上到下的&#xff0c;在react中主要是从父组件流向子组件。 …

Python基础(九)——正则表达式

6.正则表达式 &#xff08;1&#xff09;基础匹配 使用re模块 match方法&#xff1a; re.match(匹配规则&#xff0c;被匹配字符串) 从被匹配字符串开头进行匹配&#xff0c;成功则返回匹配对象包含的信息&#xff0c;失败则返回None。 s "today is a beautiful da…

计算机毕业设计 基于SpringBoot框架的网上蛋糕销售系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【微处理器系统原理与应用设计第十三讲】通用同/异步收发器USART轮询模式应用设计

USART提供两设备之间的串行双工通信&#xff0c;并支持中断和DMA工作。采用轮询、中断和DMA三种方式进行数据收发。 一、功能需求 实现远程串行通信数据的回传确认。微处理器系统构成的测控设备通过USART&#xff08;串口&#xff09;与用户设备&#xff08;上位机&#xff0…