feat(security): 实现JWT认证与授权功能

重构用户登录服务,引入Spring Security和JWT认证机制
- 新增JwtUtils工具类处理JWT生成与验证
- 添加JwtAuthenticationFilter拦截请求验证token
- 实现UserDetailsService从数据库加载用户信息
- 创建AuthController处理登录请求返回JWT
- 重构用户角色权限相关接口,支持基于角色的访问控制
- 移除旧的安全配置,启用新的SecurityConfig
- 新增LoginResponse DTO替代旧的LoginUser
- 优化用户密码加密存储,使用BCryptPasswordEncoder
This commit is contained in:
qingfeng1121
2025-12-04 14:03:29 +08:00
parent d99580f0c9
commit 20f8a9d132
20 changed files with 970 additions and 320 deletions

View File

@@ -0,0 +1,80 @@
package com.qf.backend.config;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 com.qf.backend.util.JwtUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* JWT认证过滤器用于解析和验证JWT
* 拦截所有请求检查Authorization头中的Bearer token
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
/**
* 过滤请求检查JWT并设置认证信息
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
// 1. 从请求头中获取Authorization
String authorizationHeader = request.getHeader("Authorization");
String token = null;
String username = null;
// 2. 检查Authorization头格式
if (authorizationHeader != null && authorizationHeader.startsWith(jwtUtils.getTokenPrefix() + " ")) {
token = authorizationHeader.substring(jwtUtils.getTokenPrefix().length() + 1);
username = jwtUtils.getUsernameFromToken(token);
}
// 3. 如果token有效且用户未认证设置认证信息
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 4. 验证token
if (jwtUtils.validateToken(token, userDetails)) {
// 5. 创建认证对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
// 6. 设置认证细节
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 7. 设置认证信息到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
} catch (Exception e) {
logger.error("JWT认证失败: {}", e.getMessage());
}
// 8. 继续过滤链
filterChain.doFilter(request, response);
}
}

View File

@@ -9,8 +9,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qf.backend.entity.Roles;
import com.qf.backend.service.RolesService;
import jakarta.annotation.PostConstruct;
/**
* 角色初始化配置类,用于在系统启动时创建内置角色
* @author 30803
@@ -21,19 +19,18 @@ public class RoleInitializer {
@Autowired
private RolesService rolesService;
/**
* 系统启动时初始化内置角色
*/
@PostConstruct
// @PostConstruct
public void initRoles() {
logger.info("开始初始化内置角色...");
// 定义内置角色信息
String[][] roleInfos = {
{"用户", "默认用户角色", "0"}, // roleType: 0-默认用户
{"店主", "店铺管理员角色", "1"}, // roleType: 1-店主
{"管理员", "系统管理员角色", "2"} // roleType: 2-管理员
{"User", "默认用户角色", "0"}, // roleType: 0-默认用户
{"Shopkeeper", "店铺管理员角色", "1"}, // roleType: 1-店主
{"Admin", "系统管理员角色", "2"} // roleType: 2-管理员
};
for (String[] roleInfo : roleInfos) {

View File

@@ -1,57 +1,114 @@
// /*
// * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
// * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
// */
// package com.qf.backend.config;
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package com.qf.backend.config;
// import org.springframework.context.annotation.Bean;
// import org.springframework.context.annotation.Configuration;
// import org.springframework.security.config.annotation.web.builders.HttpSecurity;
// import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
// import org.springframework.security.provisioning.InMemoryUserDetailsManager;
// import org.springframework.security.web.SecurityFilterChain;
import org.springframework.beans.factory.annotation.Autowired;
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;
// /**
// * 安全配置类(仅开发 禁用安全认证)
// *
// * @author 30803
// */
// @Configuration
// @EnableWebSecurity
// public class SecurityConfig {
/**
* 安全配置类使用JWT认证
* 该类是Spring Security的核心配置类负责配置安全策略、认证机制和授权规则
* 与AuthController.java的关系
* 1. AuthController处理登录请求调用AuthenticationManager进行认证
* 2. SecurityConfig配置AuthenticationManager和相关组件
* 3. SecurityConfig配置JWT过滤器用于拦截后续请求并验证JWT
* 4. 两者协同工作,完成完整的认证授权流程
*/
@Configuration
@EnableWebSecurity // 启用Spring Security
public class SecurityConfig {
// @Bean
// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// http
// .authorizeHttpRequests(auth -> auth
// .requestMatchers("/users/**").permitAll() // 公开路径
// .requestMatchers("/admin/**").hasRole("ADMIN") // 需要 ADMIN 角色
// .anyRequest().authenticated() // 其他请求需登录
// )
// .formLogin(form -> form
// .loginPage("/login") // 自定义登录页(可选)
// .permitAll()
// )
// .logout(logout -> logout
// .permitAll()
// );
// return http.build();
// }
/**
* 注入JWT认证过滤器
* 该过滤器会拦截所有请求检查Authorization头中的Bearer token
*/
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
/**
* 配置SecurityFilterChain
* SecurityFilterChain是Spring Security 3.x的新特性替代了旧版的WebSecurityConfigurerAdapter
* 该方法配置了安全策略,包括:
* 1. 禁用CSRF保护适合API服务因为API通常使用JWT而不是Session
* 2. 配置会话管理为无状态适合RESTful API不使用Session
* 3. 配置请求授权规则
* 4. 禁用默认的登录和注销功能使用自定义的AuthController
* 5. 添加JWT过滤器
*
* @param http HttpSecurity对象用于配置安全策略
* @return SecurityFilterChain对象
* @throws Exception 配置过程中可能抛出的异常
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 1. 禁用CSRF保护
// CSRF跨站请求伪造保护主要用于Web应用防止第三方网站伪造请求
// API服务通常使用JWT认证不需要CSRF保护
.csrf(csrf -> csrf.disable())
// 2. 配置会话管理:无状态
// 无状态意味着服务器不存储用户会话信息,每个请求都需要携带完整的认证信息
// 这是RESTful API的最佳实践提高了系统的可扩展性和安全性
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 3. 配置请求授权规则
// 使用Lambda DSL配置请求匹配规则和授权要求
.authorizeHttpRequests(auth -> auth
// 登录接口公开访问,不需要认证
.requestMatchers("/api/auth/login").permitAll()
// 其他所有请求都需要认证
.anyRequest().authenticated()
)
// 4. 禁用默认的登录和注销功能
// 因为我们使用自定义的AuthController处理登录请求
.formLogin(form -> form.disable())
.logout(logout -> logout.disable());
// 5. 添加JWT过滤器
// 在UsernamePasswordAuthenticationFilter之前添加JWT过滤器
// 这样所有请求都会先经过JWT过滤器验证token的有效性
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
// @Bean
// public UserDetailsService userDetailsService() {
// UserDetails user = User.withDefaultPasswordEncoder()
// .username("user")
// .password("123456")
// .roles("USER")
// .build();
// UserDetails admin = User.withDefaultPasswordEncoder()
// .username("admin")
// .password("admin123")
// .roles("USER", "ADMIN")
// .build();
// return new InMemoryUserDetailsManager(user, admin);
// }
// }
/**
* 配置PasswordEncoder
* PasswordEncoder用于加密和验证密码
* BCryptPasswordEncoder是Spring Security推荐的密码编码器使用随机盐值
*
* @return PasswordEncoder对象
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置AuthenticationManager
* AuthenticationManager是Spring Security的核心组件负责处理认证请求
* 该方法从AuthenticationConfiguration中获取AuthenticationManager实例
*
* @param authenticationConfiguration AuthenticationConfiguration对象
* @return AuthenticationManager对象
* @throws Exception 配置过程中可能抛出的异常
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}

View File

@@ -0,0 +1,69 @@
package com.qf.backend.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qf.backend.entity.Users;
import com.qf.backend.service.UsersService;
import com.qf.backend.util.ValidateUtil;
import jakarta.annotation.PostConstruct;
/**
* 用户初始化配置类,用于在系统启动时创建内置用户
* @author 30803
*/
@Component
public class UserInitializer {
private static final Logger logger = LoggerFactory.getLogger(UserInitializer.class);
@Autowired
private UsersService usersService;
/**
* 系统启动时初始化内置用户
*/
// @PostConstruct
public void initUsers() {
logger.info("开始初始化内置用户...");
// 定义内置用户信息
String[][] userInfos = {
// 用户名,密码,手机号,邮箱,状态
{"admin", "admin123", "13800000000", "admin@qq.com", "1"}, // 管理员用户
{"shopkeeper", "123456", "13800000001", "shopkeeper@qq.com", "1"}, // 店主用户
{"user", "123456", "13800000002", "user@qq.com", "1"} // 普通用户
};
for (String[] userInfo : userInfos) {
String username = userInfo[0];
String password = userInfo[1];
String phone = userInfo[2];
String email = userInfo[3];
Integer status = Integer.parseInt(userInfo[4]);
// 检查用户是否已存在
Users existingUser = usersService.getOne(new QueryWrapper<Users>().eq("username", username));
if (existingUser == null) {
// 创建新用户
Users user = new Users();
user.setUsername(username);
user.setPassword(password);
user.setPhone(phone);
user.setEmail(email);
user.setStatus(status);
// 注意不设置last_login_time字段因为数据库中可能不存在该字段
usersService.createUser(user);
logger.info("成功创建内置用户: {}", username);
} else {
logger.info("内置用户 {} 已存在,跳过创建", username);
}
}
logger.info("内置用户初始化完成");
}
}

View File

@@ -1,43 +0,0 @@
// /*
// * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
// * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
// */
// package com.qf.backend.config;
// import org.springframework.beans.factory.annotation.Autowired;
// import org.springframework.security.core.userdetails.UserDetailsService;
// import org.springframework.stereotype.Component;
// import org.springframework.stereotype.Service;
// import org.springframework.security.core.userdetails.UserDetails;
// import org.springframework.security.core.userdetails.UsernameNotFoundException;
// import org.springframework.security.core.authority.AuthorityUtils;
// import com.qf.backend.mapper.UsersMapper;
// import com.qf.backend.entity.Users;
// import com.qf.backend.mapper.RolesMapper;
// /**
// * 自定义UserDetailsService
// * 用于从数据库加载用户信息进行认证
// * @author 30803
// */
// @Component
// public class UsersDetailsServiceConfig implements UserDetailsService {
// @Autowired
// private UsersMapper usersMapper;
// @Autowired
// private RolesMapper rolesMapper;
// @Override
// public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Users users = usersMapper.selectByUsername(username);
// if (users == null) {
// throw new UsernameNotFoundException("用户不存在"+username);
// }
// int roleType = rolesMapper.selectById(users.getId()).getRoleType();
// }
// }