feat(security): 实现JWT认证与授权功能
重构用户登录服务,引入Spring Security和JWT认证机制 - 新增JwtUtils工具类处理JWT生成与验证 - 添加JwtAuthenticationFilter拦截请求验证token - 实现UserDetailsService从数据库加载用户信息 - 创建AuthController处理登录请求返回JWT - 重构用户角色权限相关接口,支持基于角色的访问控制 - 移除旧的安全配置,启用新的SecurityConfig - 新增LoginResponse DTO替代旧的LoginUser - 优化用户密码加密存储,使用BCryptPasswordEncoder
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
69
src/main/java/com/qf/backend/config/UserInitializer.java
Normal file
69
src/main/java/com/qf/backend/config/UserInitializer.java
Normal 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("内置用户初始化完成");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
// }
|
||||
|
||||
// }
|
||||
Reference in New Issue
Block a user