当前位置: 首页 > news >正文

SpringBoot3整合“Spring Security+JWT”快速实现demo示例与Apifox测试

目录

一、pom文件中引入核心依赖。

二、工具类准备。(JWT生成、BCrypt加密)

三、配置文件application.yml。(spring、mybatis基础配置)

四、编写SecurityConfig核心配置类。

五、编写JWT认证过滤器。

六、相关实体类。

(1)请求与响应的封装实体类。

(2)用户实体类。(对应数据库表user)

(3)spring security的用户信息包装类。(重要)

七、相关Controller。

(1)登录接口。(设置放行,并由security自动校验用户名与密码)

(2)测试接口。(受保护。必须携带合法且未过期的JWT才能访问)

八、相关Service。

(1)User相关接口与实现类。

(2)Spring Security专用的用户加载服务类。(重要)

九、相关Mapper。

十、springboot启动类。

十一、使用Apifox模拟测试。

(1)登录相关测试。

(2)访问受保护的接口。


一、pom文件中引入核心依赖。

<!--构建springboot项目时根据选择的版本自动生成的起步start依赖--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.7</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.hyl</groupId><artifactId>springSecurityDemo</artifactId><version>0.0.1-SNAPSHOT</version><name>springSecurityDemo</name><description>springSecurityDemo</description><properties><java.version>21</java.version></properties>

<dependencies><!--spring web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.4</version></dependency><!--mysql driver--><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!--spring security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --><!-- JWT 处理的接口和抽象类--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --><!--JWT 的创建、解析和验证--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson --><!--JWT 的 JSON 序列化和反序列化--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope></dependency></dependencies>

二、工具类准备。(JWT生成、BCrypt加密)

  • JWT生成工具类。
package com.hyl.security.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;/*** JWT工具类:生成令牌、解析令牌、验证令牌*/
@Component
public class JwtUtil {// 密钥(实际项目中不要硬编码,可放在配置文件中)//HS256 算法强制要求密钥长度≥256 位private static final String SECRET_KEY = "suisuipingansuisuipingansuisuipingansuisuipingansuisuipinganFIVE";// 令牌有效期(10小时,根据需求调整)private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 10;// 从令牌中提取用户名public String extractUsername(String token) {return extractClaim(token, Claims::getSubject);}// 从令牌中提取过期时间public Date extractExpiration(String token) {return extractClaim(token, Claims::getExpiration);}// 通用方法:从令牌中提取特定声明public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {final Claims claims = extractAllClaims(token);return claimsResolver.apply(claims);}// 解析令牌获取所有声明private Claims extractAllClaims(String token) {return Jwts.parser().setSigningKey(SECRET_KEY) // 使用密钥验证签名.parseClaimsJws(token).getBody();}// 检查令牌是否已过期private Boolean isTokenExpired(String token) {return extractExpiration(token).before(new Date());}// 生成令牌(传入用户信息)public String generateToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>(); // 可添加自定义声明(如角色)return createToken(claims, userDetails.getUsername());}// 构建JWT令牌(设置声明、主题、过期时间并签名)private String createToken(Map<String, Object> claims, String subject) {return Jwts.builder().setClaims(claims) // 设置自定义声明.setSubject(subject) // 设置主题(通常是用户名).setIssuedAt(new Date(System.currentTimeMillis())) // 设置签发时间.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 设置过期时间.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 使用HS256算法和密钥签名.compact(); // 生成紧凑的URL安全字符串}// 验证令牌有效性(用户名匹配且未过期)public Boolean validateToken(String token, UserDetails userDetails) {final String username = extractUsername(token);return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));}
}

  • BCrypt加密测试工具类。(将生成密文存储到数据库中方便测试)
package com.hyl.security.utils;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;/*** 密码加密工具类 - 用于生成BCrypt加密的密码* 运行main方法可以获取指定明文的加密结果*/
public class PasswordEncoderUtil {public static void main(String[] args) {// 需要加密的明文密码(修改这里)String rawPassword = "123";// 创建BCrypt编码器BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();// 生成加密后的密码String encodedPassword = encoder.encode(rawPassword);// 输出结果(复制这个结果到数据库中)System.out.println("明文密码: " + rawPassword);System.out.println("BCrypt加密后的密码: " + encodedPassword);}
}
  • 比如测试明文:123。得到BCrypt加密的字符串。

三、配置文件application.yml。(spring、mybatis基础配置)

#启动端口号
server:port: 9090#spring相关基础配置
spring:application:name: springSecurityDemodatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8&allowPublicKeyRetrieval=trueusername: rootpassword: root123#mybatis相关基础配置
mybatis:mapper-locations: classpath:mapper/*.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true

四、编写SecurityConfig核心配置类。

package com.hyl.security.config;import com.hyl.security.filter.JwtAuthFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration // 配置类
@EnableWebSecurity // 启用Spring Security安全机制
public class SecurityConfig {// 注入JWT过滤器(构造器注入)private final JwtAuthFilter jwtAuthFilter;public SecurityConfig(JwtAuthFilter jwtAuthFilter) {this.jwtAuthFilter = jwtAuthFilter;}// 配置安全过滤链(核心配置)@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// 禁用CSRF:JWT是无状态的,不需要CSRF保护.csrf(csrf -> csrf.disable())// 配置接口访问权限.authorizeHttpRequests(auth -> auth.requestMatchers("/api/auth/**").permitAll() // 登录接口允许匿名访问.anyRequest().authenticated() // 其他接口必须认证(如:/api/test))// 配置会话管理:JWT不需要会话,用无状态模式.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 添加JWT过滤器:在用户名密码验证前执行.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}// 密码加密器:存储密码时用BCrypt加密@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 认证管理器:处理登录时的用户名密码验证@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}
}

五、编写JWT认证过滤器。

package com.hyl.security.filter;import com.hyl.security.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;/*** JWT认证过滤器:拦截请求并验证JWT令牌* 执行时机:每个请求都会经过该过滤器(除了配置的放行接口)*/
@Component
public class JwtAuthFilter extends OncePerRequestFilter {@Autowiredprivate JwtUtil jwtUtil; // JWT工具类@Autowiredprivate UserDetailsService userDetailsService; // 加载用户信息的服务@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {// 1. 从请求头获取令牌(格式:Authorization: Bearer <令牌>)String authHeader = request.getHeader("Authorization");String jwt = null;String username = null;// 检查请求头是否包含Bearer令牌if (authHeader != null && authHeader.startsWith("Bearer ")) {// 截取令牌(去掉"Bearer "前缀,注意有个空格)jwt = authHeader.substring(7);try {// 从令牌中解析出用户名username = jwtUtil.extractUsername(jwt);} catch (Exception e) {// 令牌无效时(如过期、篡改),这里会抛出异常logger.error("JWT令牌验证失败:" + e.getMessage());}}// 2. 验证令牌并设置用户认证信息// 如果用户名不为空,且当前上下文没有认证信息(未登录)if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {// 从数据库加载用户信息UserDetails userDetails = userDetailsService.loadUserByUsername(username);// 验证令牌是否有效(签名正确+未过期)if (jwtUtil.validateToken(jwt, userDetails)) {// 创建认证对象(Spring Security需要的格式)UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());// 设置请求详情(如IP地址等)authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 将认证信息存入上下文:表示用户已登录SecurityContextHolder.getContext().setAuthentication(authToken);}}// 3. 继续执行后续过滤器(让请求到达目标接口)filterChain.doFilter(request, response);}
}

六、相关实体类。

(1)请求与响应的封装实体类。
  • AuthenticationRequest:最大作用是接收登录时传递的账号、密码的数据值。
package com.hyl.security.entity;public class AuthenticationRequest {private String username;private String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "AuthenticationRequest{" +"username='" + username + '\'' +", password='" + password + '\'' +'}';}
}

  • AuthenticationResponse:最大作用是将生成的JWT令牌响应成功回去。
package com.hyl.security.entity;public class AuthenticationResponse {private String jwt;public AuthenticationResponse(String jwt) {this.jwt = jwt;}public String getJwt() {return jwt;}
}
(2)用户实体类。(对应数据库表user)
package com.hyl.security.entity;public class User {private Long id;private String username;private String password;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +'}';}
}
(3)spring security的用户信息包装类。(重要)
package com.hyl.security.entity;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;/*** Spring Security需要的用户信息包装类* 作用:将数据库的User转换为Spring Security能识别的用户对象*/
public class CustomUserDetails implements UserDetails {private final User user; // 数据库用户实体public CustomUserDetails(User user) {this.user = user;}// 获取用户权限(这里简化为无权限,实际项目可添加角色)@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return Collections.emptyList(); // 暂时返回空列表}// 获取密码(从数据库用户对象中获取)@Overridepublic String getPassword() {return user.getPassword();}// 获取用户名(从数据库用户对象中获取)@Overridepublic String getUsername() {return user.getUsername();}// 以下方法都返回true,表示用户状态正常(新手默认即可)@Overridepublic boolean isAccountNonExpired() { return true; } // 账号未过期@Overridepublic boolean isAccountNonLocked() { return true; }  // 账号未锁定@Overridepublic boolean isCredentialsNonExpired() { return true; } // 密码未过期@Overridepublic boolean isEnabled() { return true; } // 账号启用
}

七、相关Controller。

(1)登录接口。(设置放行,并由security自动校验用户名与密码)
  • AuthController。处理登录请求。成功后返回JWT令牌。
package com.hyl.security.controller;import com.hyl.security.entity.AuthenticationRequest;
import com.hyl.security.entity.AuthenticationResponse;
import com.hyl.security.service.impl.CustomUserDetailsService;
import com.hyl.security.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 认证控制器:处理登录请求*/
@RestController
@RequestMapping("/api/auth")
public class AuthController {// 认证管理器(由Spring自动配置)@Autowiredprivate AuthenticationManager authenticationManager;// 加载用户信息的服务@Autowiredprivate CustomUserDetailsService customUserDetailsService;// JWT工具类@Autowiredprivate JwtUtil jwtUtil;/*** 登录接口:验证用户名密码并返回JWT令牌*/@PostMapping("/login")public ResponseEntity<?> login(@RequestBody AuthenticationRequest request) {try {// 1. 验证用户名密码(Spring Security自动调用CustomUserDetailsService查询用户)authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getUsername(),request.getPassword()));} catch (BadCredentialsException e) {// 密码错误时返回401return ResponseEntity.status(401).body("登录失败:用户名或密码错误");}// 2. 验证通过,生成JWT令牌UserDetails userDetails = customUserDetailsService.loadUserByUsername(request.getUsername());String jwt = jwtUtil.generateToken(userDetails);// 3. 返回令牌return ResponseEntity.ok(new AuthenticationResponse(jwt));}
}
(2)测试接口。(受保护。必须携带合法且未过期的JWT才能访问)
  • TestController。受保护的接口。
  • 必须携带JWT令牌,且key=Authorization,value=Bearer <生成的jwt字符串>
package com.hyl.security.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 测试受保护的接口:需要携带JWT令牌才能访问*/
@RestController
@RequestMapping("/api/test")
public class TestController {// 访问该接口需要在请求头添加:Authorization: Bearer <你的令牌>@GetMappingpublic String test() {return "访问成功!这是受保护的接口内容";}
}

八、相关Service。

(1)User相关接口与实现类。
  • UserService接口。
package com.hyl.security.service;import com.hyl.security.entity.User;public interface UserService {/*** 根据用户名查询用户* @param username* @return*/User selectByUsername(String username);
}

  • UserServiceImpl实现类。
package com.hyl.security.service.impl;import com.hyl.security.entity.User;
import com.hyl.security.mapper.UserMapper;
import com.hyl.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User selectByUsername(String username) {return userMapper.selectByUsername(username);}
}
(2)Spring Security专用的用户加载服务类。(重要)
package com.hyl.security.service.impl;import com.hyl.security.entity.CustomUserDetails;
import com.hyl.security.entity.User;
import com.hyl.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;/*** Spring Security专用的用户加载服务* 作用:当需要验证用户时,Spring Security会自动调用该类查询用户*/
@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserService userService; // 注入用户服务// 根据用户名查询用户(Spring Security会自动调用该方法)@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1. 从数据库查询用户User user = userService.selectByUsername(username);// 2. 如果用户不存在,抛出异常(Spring Security会自动处理为登录失败)if (user == null) {throw new UsernameNotFoundException("用户名不存在:" + username);}// 3. 将数据库用户转换为Spring Security需要的用户对象return new CustomUserDetails(user);}
}

九、相关Mapper。

  • UserMapper。
package com.hyl.security.mapper;import com.hyl.security.entity.User;public interface UserMapper {User selectByUsername(String username);
}

  • .UserMapper.xml。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hyl.security.mapper.UserMapper"><select id="selectByUsername" resultType="com.hyl.security.entity.User" parameterType="java.lang.String">select * from `user` where username = #{username}</select></mapper>

十、springboot启动类。

  • 启动springboot服务。开启mapper扫描。
package com.hyl.security;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.hyl.security.mapper")
public class SpringSecurityDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringSecurityDemoApplication.class, args);}}

十一、使用Apifox模拟测试。

  • 提前准备好数据库。

(1)登录相关测试。
  • 登录失败。


  • 登录成功。获取到对应的JWT令牌。

(2)访问受保护的接口。
  • 直接访问接口:http://localhost:9090/api/test。


  • 使用Apifox并携带对应的登录成功后返回的令牌一起请求受保护的接口。


  • 另外一种便捷的请求方式。





http://www.dtcms.com/a/279210.html

相关文章:

  • 在 Azure Linux 上安装 RustFS
  • 【Echarts】 电影票房汇总实时数据横向柱状图比图
  • Swift 解 LeetCode 326:两种方法判断是否是 3 的幂,含循环与数学技巧
  • ELK部署与使用详解
  • BGP基本配置
  • QGC 环境搭建踩坑记录
  • XML vs JSON:核心区别与最佳选择
  • IDEA实现纯java项目并打包jar(不使用Maven,Spring)
  • C++--priority_queue的模拟实现
  • 幻想读 通过多版本并发控制(MVCC)和间隙锁(Gap Lock)的组合也能防止幻读具体说下
  • Transformer江湖录 第五章:江湖争锋 - BERT vs GPT
  • 如何保护文件传输安全?文件传输加密
  • 企业数字化资产管理安全、成本、协作困局难解?
  • Java行为型模式(命令模式)应用场景分析
  • 数字化红头文件生成工具:提升群聊与团队管理效率的创新方案
  • Ubuntu安装 Redis
  • vscode/cursor怎么自定义文字、行高、颜色
  • Mysql数据库学习--多表查询
  • Eureka
  • Linux:3_基础开发⼯具
  • JAVA面试宝典 - 《MyBatis 进阶:插件开发与二级缓存》
  • 理解:进程、线程、协程
  • muduo面试准备
  • 文本预处理(四)
  • 2025-7-14-C++ 学习 排序(2)
  • 【LeetCode 热题 100】94. 二叉树的中序遍历——DFS
  • 死锁!哲学家进餐问题(操作系统os)
  • 光电融合新范式:长春光机所孙晓娟/李大冰团队《Light》发表铁电量子阱相纯度调控策略
  • 系统分析师第五课:进程通信-死锁-存储管理-固定分页分段
  • SpringMVC注解:@RequestParam 与 @PathVariable