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

@@ -6,7 +6,7 @@
package com.qf.backend.service;
import com.qf.backend.common.Result;
import com.qf.backend.dto.LoginUser;
import com.qf.backend.dto.LoginResponse;
/**
* 用户登录服务接口
@@ -16,7 +16,7 @@ public interface UserLoginService {
* 用户登录
* @param username 用户名
* @param password 密码
* @return 登录结果
* @return 登录结果包含登录状态、token等信息
*/
Result<LoginUser> login(String username, String password);
Result<LoginResponse> login(String username, String password);
}

View File

@@ -1,11 +1,11 @@
package com.qf.backend.service;
import java.util.List;
import com.baomidou.mybatisplus.extension.service.IService;
import com.qf.backend.common.Result;
import com.qf.backend.entity.UserRoles;
import java.util.List;
/**
* 用户角色关联服务接口
*/

View File

@@ -1,158 +1,87 @@
package com.qf.backend.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qf.backend.common.Result;
import com.qf.backend.common.ResultUtils;
import com.qf.backend.entity.UserDetails;
import com.qf.backend.exception.BusinessException;
import com.qf.backend.exception.ErrorCode;
import com.qf.backend.mapper.UserDetailsMapper;
import com.qf.backend.service.UserDetailsService;
import com.qf.backend.entity.Roles;
import com.qf.backend.entity.UserRoles;
import com.qf.backend.entity.Users;
import com.qf.backend.service.RolesService;
import com.qf.backend.service.UserRolesService;
import com.qf.backend.service.UsersService;
/**
* 用户详情服务实现类
* UserDetailsService实现类用于从数据库中加载用户信息
* 该类实现了Spring Security的UserDetailsService接口用于根据用户名加载用户信息
*/
@Service
public class UserDetailsServiceImpl extends ServiceImpl<UserDetailsMapper, UserDetails> implements UserDetailsService {
public class UserDetailsServiceImpl implements UserDetailsService {
/**
* 注入用户服务,用于查询用户信息
*/
@Autowired
private UserDetailsMapper userDetailsMapper;
private UsersService usersService;
@Autowired
private UserRolesService userRolesService;
@Autowired
private RolesService RolesService;
/**
* 根据用户名加载用户信息
* @param username 用户名
* @return UserDetails 用户详情对象,包含用户名、密码、权限等信息
* @throws UsernameNotFoundException 如果用户名不存在
*/
@Override
public Result<UserDetails> getUserDetailsByUserId(Long userId) {
if (userId == null) {
throw new BusinessException(ErrorCode.MISSING_PARAM, "用户ID不能为空");
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 从数据库中查询用户
Result<Users> result = usersService.getUserByUsername(username);
if (result == null || result.getData() == null) {
throw new UsernameNotFoundException("用户名不存在: " + username);
}
try {
UserDetails userDetails = userDetailsMapper.selectOne(
new QueryWrapper<UserDetails>().eq("user_id", userId));
return ResultUtils.success(userDetails);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
throw new BusinessException(ErrorCode.DATABASE_ERROR, "查询用户详情失败", e);
Users user = result.getData();
// 2. 构建权限列表(这里简化处理,实际应从数据库中查询用户的角色和权限)
List<GrantedAuthority> authorities = new ArrayList<>();
// 查询用户角色关联
Result<List<UserRoles>> userRoleResultList = userRolesService.getUserRolesByUserId(user.getId());
if (userRoleResultList == null || userRoleResultList.getData() == null) {
throw new UsernameNotFoundException("用户角色不存在: " + user.getId());
}
}
@Override
public Result<Boolean> createUserDetails(UserDetails userDetails) {
if (userDetails == null) {
throw new BusinessException(ErrorCode.MISSING_PARAM, "用户详情信息不能为空");
}
if (userDetails.getUserId() == null) {
throw new BusinessException(ErrorCode.MISSING_PARAM, "用户ID不能为空");
}
try {
// 检查是否已存在该用户的详情
UserDetails existing = userDetailsMapper.selectOne(
new QueryWrapper<UserDetails>().eq("user_id", userDetails.getUserId()));
if (existing != null) {
throw new BusinessException(ErrorCode.BUSINESS_ERROR, "该用户已存在详情信息");
// 3. 查询角色权限
for (UserRoles userRole : userRoleResultList.getData()) {
Result<Roles> roleResult = RolesService.getRoleById(userRole.getRoleId());
if (roleResult == null || roleResult.getData() == null) {
throw new UsernameNotFoundException("权限不存在: " + userRole.getRoleId());
}
int result = userDetailsMapper.insert(userDetails);
return ResultUtils.success(result > 0);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
throw new BusinessException(ErrorCode.DATABASE_ERROR, "创建用户详情失败", e);
Roles role = roleResult.getData();
// 4. 转换为Spring Security的GrantedAuthority对象
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName().toUpperCase()));
}
// 3. 返回UserDetails对象
// 注意:在实际应用中,密码应该加密存储,这里直接使用明文密码(仅用于演示)
return User.builder()
.username(user.getUsername()) // 用户名
.password(user.getPassword()) // 密码需要加密存储,这里直接使用明文密码(仅用于演示)
.authorities(authorities) // 假设用户默认拥有USER权限
.accountExpired(false) // 假设账号永不过期
.accountLocked(false) // 假设账号永不过期
.credentialsExpired(false) // 假设密码永不过期
.disabled(user.getStatus() == 0) // 假设status为0表示禁用
.build();
}
@Override
public Result<Boolean> updateUserDetails(UserDetails userDetails) {
if (userDetails == null) {
throw new BusinessException(ErrorCode.MISSING_PARAM, "用户详情信息不能为空");
}
if (userDetails.getId() == null) {
throw new BusinessException(ErrorCode.MISSING_PARAM, "用户详情ID不能为空");
}
try {
// 检查用户详情是否存在
UserDetails existing = userDetailsMapper.selectById(userDetails.getId());
if (existing == null) {
throw new BusinessException(ErrorCode.NOT_FOUND, "用户详情不存在");
}
int result = userDetailsMapper.updateById(userDetails);
return ResultUtils.success(result > 0);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
throw new BusinessException(ErrorCode.DATABASE_ERROR, "更新用户详情失败", e);
}
}
@Override
public Result<Boolean> deleteUserDetailsByUserId(Long userId) {
if (userId == null) {
throw new BusinessException(ErrorCode.MISSING_PARAM, "用户ID不能为空");
}
try {
// 检查用户详情是否存在
UserDetails existing = userDetailsMapper.selectOne(
new QueryWrapper<UserDetails>().eq("user_id", userId));
if (existing == null) {
throw new BusinessException(ErrorCode.NOT_FOUND, "用户详情不存在");
}
int result = userDetailsMapper.delete(
new QueryWrapper<UserDetails>().eq("user_id", userId));
return ResultUtils.success(result > 0);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
throw new BusinessException(ErrorCode.DATABASE_ERROR, "删除用户详情失败", e);
}
}
@Override
public Result<UserDetails> getUserDetailsById(Long id) {
if (id == null) {
throw new BusinessException(ErrorCode.MISSING_PARAM, "用户详情ID不能为空");
}
try {
UserDetails userDetails = userDetailsMapper.selectById(id);
if (userDetails == null) {
throw new BusinessException(ErrorCode.NOT_FOUND, "用户详情不存在");
}
return ResultUtils.success(userDetails);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
throw new BusinessException(ErrorCode.DATABASE_ERROR, "查询用户详情失败", e);
}
}
// @Override
// public Result<Boolean> updateContactInfo(Long userId, String phone, String email) {
// if (userId == null) {
// throw new BusinessException(ErrorCode.MISSING_PARAM, "用户ID不能为空");
// }
// if (ValidateUtil.isEmpty(phone) && ValidateUtil.isEmpty(email)) {
// throw new BusinessException(ErrorCode.MISSING_PARAM, "手机号和邮箱不能同时为空");
// }
// try {
// // 检查用户详情是否存在
// UserDetails existing = userDetailsMapper.selectOne(
// new QueryWrapper<UserDetails>().eq("user_id", userId));
// if (existing == null) {
// throw new BusinessException(ErrorCode.NOT_FOUND, "用户详情不存在");
// }
// // 更新联系方式
// if (phone != null) {
// existing.setPhone(phone);
// }
// if (email != null) {
// existing.setEmail(email);
// }
// int result = userDetailsMapper.updateById(existing);
// return ResultUtils.success(result > 0);
// } catch (BusinessException e) {
// throw e;
// } catch (Exception e) {
// throw new BusinessException(ErrorCode.DATABASE_ERROR, "更新用户联系方式失败", e);
// }
// }
}
}

View File

@@ -17,7 +17,7 @@ import org.springframework.stereotype.Service;
import com.qf.backend.common.Result;
import com.qf.backend.common.ResultUtils;
import com.qf.backend.dto.LoginUser;
import com.qf.backend.dto.LoginResponse;
import com.qf.backend.entity.Permissions;
import com.qf.backend.entity.Roles;
import com.qf.backend.entity.UserRoles;
@@ -57,17 +57,15 @@ public class UserLoginServiceImpl implements UserLoginService {
* @return 登录结果
*/
@Override
public Result<LoginUser> login(String username, String password) {
public Result<LoginResponse> login(String username, String password) {
logger.info("用户登录,用户名:{}", username);
// 1. 校验用户名和密码是否为空
try{
if (ValidateUtil.isEmpty(username) || ValidateUtil.isEmpty(password)) {
throw new IllegalArgumentException("用户名或密码不能为空");
}
// 加密密码
String encryptedPassword = ValidateUtil.encryptPassword(password);
// 2. 登录
Result<Users> result = usersServiceImpl.login(username, encryptedPassword);
Result<Users> result = usersServiceImpl.login(username, password);
if (result == null || result.getData() == null) {
throw new IllegalArgumentException("用户名不存在或密码错误");
}
@@ -87,7 +85,7 @@ public class UserLoginServiceImpl implements UserLoginService {
for (UserRoles ur : userRoles) {
Roles role = rolesServiceImpl.getById(ur.getRoleId());
if (role != null) {
roleNames.add(role.getRoleName());
roleNames.add(String.valueOf(role.getRoleType()));
roleIds.add(role.getId());
}
}
@@ -104,14 +102,12 @@ public class UserLoginServiceImpl implements UserLoginService {
}
}
// 6. 构建LoginUser对象
LoginUser loginUser = new LoginUser();
loginUser.setId(user.getId());
loginUser.setUsername(user.getUsername());
loginUser.setRoles(new ArrayList<>(roleNames));
loginUser.setPermissions(new ArrayList<>(permissionCodes));
return ResultUtils.success(loginUser);
// 6. 构建LoginResponse对象
LoginResponse loginResponse = new LoginResponse();
loginResponse.setUsername(user.getUsername());
loginResponse.setRoles(new ArrayList<>(roleNames));
loginResponse.setPermissions(new ArrayList<>(permissionCodes));
return ResultUtils.success(loginResponse);
} catch (Exception e) {
logger.error("用户登录失败,用户名:{}", username, e);
return ResultUtils.fail(ErrorCode.SYSTEM_ERROR, e.getMessage());

View File

@@ -88,12 +88,18 @@ public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements
throw new BusinessException(ErrorCode.INVALID_PARAM, "用户名或密码不能为空");
}
// 校验用户名是否存在
Users user = usersMapper.login(username, password);
// 根据用户名查询用户
Users user = usersMapper.selectByUsername(username);
if (user == null) {
throw new BusinessException(ErrorCode.USER_NOT_FOUND, "用户名不存在或密码错误");
}
// 使用BCryptPasswordEncoder验证密码
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BusinessException(ErrorCode.USER_NOT_FOUND, "用户名不存在或密码错误");
}
return ResultUtils.success(user);
} catch (BusinessException e) {
throw e;