refactor: 优化登录策略

This commit is contained in:
Bunny 2025-02-17 19:43:01 +08:00
parent da7157e0c4
commit 08aeca4834
34 changed files with 252 additions and 82 deletions

View File

@ -25,9 +25,39 @@ public class Knife4jConfig {
return new OpenAPI().info(info).externalDocs(new ExternalDocumentation());
}
// 管理员相关分类接口
@Bean
public GroupedOpenApi groupedOpenAdminApi() {
return GroupedOpenApi.builder().group("默认请求接口").pathsToMatch("/admin/**").build();
public GroupedOpenApi all() {
return GroupedOpenApi.builder().group("全部请求接口").pathsToMatch("/api/**").build();
}
@Bean
public GroupedOpenApi i18n() {
return GroupedOpenApi.builder().group("多语言").pathsToMatch("/api/i18n/**", "/api/i18nType/**").build();
}
@Bean
public GroupedOpenApi config() {
return GroupedOpenApi.builder().group("配置")
.pathsToMatch("/api/config/**", "/api/emailTemplate/**", "/api/emailUsers/**",
"/api/message/**", "/api/messageReceived/**", "/api/messageType/**",
"/api/menuIcon/**", "/api/schedulers/**", "/api/schedulersGroup/**"
)
.build();
}
@Bean
public GroupedOpenApi system() {
return GroupedOpenApi.builder().group("系统")
.pathsToMatch("/api/dept/**", "/api/files/**", "/api/power/**",
"/api/rolePower/**", "/api/role/**", "/api/router/**",
"/api/routerRole/**", "/api/user/**", "/api/userRole/**"
).build();
}
@Bean
public GroupedOpenApi log() {
return GroupedOpenApi.builder().group("日志")
.pathsToMatch("/api/userLoginLog/**", "/api/quartzExecuteLog/**"
).build();
}
}

View File

@ -11,13 +11,13 @@ import java.io.IOException;
public class ResponseUtil {
public static void out(HttpServletResponse response, Result<Object> result) {
try {
ObjectMapper mapper = new ObjectMapper();
// 注册JavaTimeModule模块
mapper.registerModule(new JavaTimeModule());
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
try {
mapper.writeValue(response.getWriter(), result);
} catch (IOException e) {
e.printStackTrace();

View File

@ -37,7 +37,7 @@ public class LoginDto {
@Schema(name = "type", title = "登录类型")
@NotBlank(message = "登录类型不能为空")
@NotNull(message = "登录类型能为空")
private String type;
private String type = "default";
@Schema(name = "readMeDay", title = "记住我的天数")
private Long readMeDay = 1L;

View File

@ -0,0 +1,21 @@
package cn.bunny.dao.enums;
import lombok.Getter;
@Getter
public enum LoginEnums {
// 邮箱登录请求
EMAIL_STRATEGY("email"),
// 默认登录请求
default_STRATEGY("default"),
// 登良路请求API
LOGIN_REQUEST_API("/api/login"),
;
private final String value;
LoginEnums(String value) {
this.value = value;
}
}

View File

@ -24,7 +24,7 @@ public enum ResultCodeEnum {
EMAIL_CODE_NOT_EMPTY(201, "邮箱验证码不能为空"),
SEND_EMAIL_CODE_NOT_EMPTY(201, "请先发送邮箱验证码"),
EMAIL_CODE_NOT_MATCHING(201, "邮箱验证码不匹配"),
LOGIN_ERROR(201, "账号或密码错误"),
LOGIN_ERROR(500, "账号或密码错误"),
LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY(201, "登录信息不能为空"),
GET_BUCKET_EXCEPTION(201, "获取文件信息失败"),
SEND_MAIL_CODE_ERROR(201, "邮件发送失败"),

View File

@ -14,7 +14,7 @@ import reactor.core.publisher.Mono;
@Tag(name = "系统配置", description = "系统配置相关接口")
@RestController
@RequestMapping("/admin/config")
@RequestMapping("/api/config")
public class ConfigurationController {
@Autowired

View File

@ -30,7 +30,7 @@ import java.util.List;
*/
@Tag(name = "系统部门", description = "部门相关接口")
@RestController
@RequestMapping("/admin/dept")
@RequestMapping("/api/dept")
public class DeptController {
@Autowired

View File

@ -31,7 +31,7 @@ import java.util.Map;
*/
@Tag(name = "邮件模板", description = "邮件模板相关接口")
@RestController
@RequestMapping("admin/emailTemplate")
@RequestMapping("api/emailTemplate")
public class EmailTemplateController {
@Autowired

View File

@ -32,7 +32,7 @@ import java.util.Map;
*/
@Tag(name = "邮箱用户发送配置", description = "邮箱用户发送配置相关接口")
@RestController
@RequestMapping("admin/emailUsers")
@RequestMapping("api/emailUsers")
public class EmailUsersController {
@Autowired

View File

@ -36,7 +36,7 @@ import java.util.Set;
*/
@Tag(name = "系统文件表", description = "系统文件相关接口")
@RestController
@RequestMapping("admin/files")
@RequestMapping("api/files")
public class FilesController {
@Autowired

View File

@ -31,7 +31,7 @@ import java.util.Map;
*/
@Tag(name = "多语言", description = "多语言相关接口")
@RestController
@RequestMapping("admin/i18n")
@RequestMapping("api/i18n")
public class I18nController {
@Autowired

View File

@ -26,7 +26,7 @@ import java.util.List;
*/
@Tag(name = "多语言类型", description = "多语言类型相关接口")
@RestController
@RequestMapping("admin/i18nType")
@RequestMapping("api/i18nType")
public class I18nTypeController {
@Autowired

View File

@ -30,7 +30,7 @@ import java.util.List;
*/
@Tag(name = "系统菜单图标", description = "系统菜单图标相关接口")
@RestController
@RequestMapping("admin/menuIcon")
@RequestMapping("api/menuIcon")
public class MenuIconController {
@Autowired

View File

@ -32,7 +32,7 @@ import java.util.List;
*/
@Tag(name = "系统消息", description = "系统消息相关接口")
@RestController
@RequestMapping("admin/message")
@RequestMapping("api/message")
public class MessageController {
@Autowired

View File

@ -31,7 +31,7 @@ import java.util.List;
*/
@Tag(name = "消息接收(用户消息)", description = "消息接收(用户消息)相关接口")
@RestController
@RequestMapping("/admin/messageReceived")
@RequestMapping("/api/messageReceived")
public class MessageReceivedController {
@Autowired

View File

@ -30,7 +30,7 @@ import java.util.List;
*/
@Tag(name = "系统消息类型", description = "系统消息类型相关接口")
@RestController
@RequestMapping("admin/messageType")
@RequestMapping("api/messageType")
public class MessageTypeController {
@Autowired

View File

@ -31,7 +31,7 @@ import java.util.List;
*/
@Tag(name = "权限", description = "权限相关接口")
@RestController
@RequestMapping("admin/power")
@RequestMapping("api/power")
public class PowerController {
@Autowired

View File

@ -30,7 +30,7 @@ import java.util.List;
*/
@Tag(name = "角色", description = "角色相关接口")
@RestController
@RequestMapping("admin/role")
@RequestMapping("api/role")
public class RoleController {
@Autowired

View File

@ -22,7 +22,7 @@ import java.util.List;
*/
@Tag(name = "角色和权限", description = "角色和权限相关接口")
@RestController
@RequestMapping("admin/rolePower")
@RequestMapping("api/rolePower")
public class RolePowerController {
@Autowired

View File

@ -32,7 +32,7 @@ import java.util.List;
*/
@Tag(name = "系统路由", description = "系统路由相关接口")
@RestController
@RequestMapping("admin/router")
@RequestMapping("api/router")
public class RouterController {
@Autowired

View File

@ -21,7 +21,7 @@ import java.util.List;
*/
@Tag(name = "路由和角色", description = "路由和角色相关接口")
@RestController
@RequestMapping("admin/routerRole")
@RequestMapping("api/routerRole")
public class RouterRoleController {
@Autowired

View File

@ -27,7 +27,7 @@ import java.util.List;
*/
@Tag(name = "调度任务执行日志", description = "调度任务执行日志相关接口")
@RestController
@RequestMapping("admin/quartzExecuteLog")
@RequestMapping("api/quartzExecuteLog")
public class ScheduleExecuteLogController {
@Autowired

View File

@ -32,7 +32,7 @@ import java.util.Map;
*/
@Tag(name = "调度任务", description = "调度任务相关接口")
@RestController
@RequestMapping("admin/schedulers")
@RequestMapping("api/schedulers")
public class SchedulersController {
@Autowired

View File

@ -30,7 +30,7 @@ import java.util.List;
*/
@Tag(name = "任务调度分组", description = "任务调度分组相关接口")
@RestController
@RequestMapping("admin/schedulersGroup")
@RequestMapping("api/schedulersGroup")
public class SchedulersGroupController {
@Autowired

View File

@ -21,7 +21,7 @@ import java.util.List;
@Tag(name = "用户信息", description = "用户信息相关接口")
@RestController
@RequestMapping("/admin/user")
@RequestMapping("/api/user")
public class UserController {
@Autowired

View File

@ -28,7 +28,7 @@ import java.util.List;
*/
@Tag(name = "用户登录日志", description = "用户登录日志相关接口")
@RestController
@RequestMapping("admin/userLoginLog")
@RequestMapping("api/userLoginLog")
public class UserLoginLogController {
@Autowired

View File

@ -21,7 +21,7 @@ import java.util.List;
*/
@Tag(name = "用户和角色", description = "用户和角色相关接口")
@RestController
@RequestMapping("admin/userRole")
@RequestMapping("api/userRole")
public class UserRoleController {
@Autowired

View File

@ -73,7 +73,7 @@ public class WebSecurityConfig {
exception.accessDeniedHandler(new SecurityAccessDeniedHandler());
})
// 登录验证过滤器
.addFilterBefore(new TokenLoginFilterService(authenticationConfiguration, redisTemplate, customUserDetailsService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new TokenLoginFilterService(authenticationConfiguration, customUserDetailsService), UsernamePasswordAuthenticationFilter.class)
// 自定义密码加密器和用户登录
.passwordManagement(customPasswordEncoder).userDetailsService(customUserDetailsService);

View File

@ -1,8 +1,8 @@
package cn.bunny.services.security.filter;
import cn.bunny.dao.constant.RedisUserConstant;
import cn.bunny.dao.dto.system.user.LoginDto;
import cn.bunny.dao.enums.LoginEnums;
import cn.bunny.dao.vo.result.Result;
import cn.bunny.dao.vo.result.ResultCodeEnum;
import cn.bunny.dao.vo.system.user.LoginVo;
@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@ -34,18 +33,15 @@ import static cn.bunny.common.service.utils.ResponseUtil.out;
* 再去设置到 UsernamePasswordAuthenticationToken 来改变请求传参方式参数名等 或者也可以在登录的时候加入其他参数等等
*/
public class TokenLoginFilterService extends UsernamePasswordAuthenticationFilter {
private final RedisTemplate<String, Object> redisTemplate;
private final CustomUserDetailsService customUserDetailsService;
private LoginDto loginDto;
public TokenLoginFilterService(AuthenticationConfiguration authenticationConfiguration, RedisTemplate<String, Object> redisTemplate, CustomUserDetailsService customUserDetailsService) throws Exception {
public TokenLoginFilterService(AuthenticationConfiguration authenticationConfiguration, CustomUserDetailsService customUserDetailsService) throws Exception {
this.setAuthenticationSuccessHandler(new SecurityAuthenticationSuccessHandler());
this.setAuthenticationFailureHandler(new SecurityAuthenticationFailureHandler());
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/login", HttpMethod.POST.name()));
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(LoginEnums.LOGIN_REQUEST_API.getValue(), HttpMethod.POST.name()));
this.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
this.redisTemplate = redisTemplate;
this.customUserDetailsService = customUserDetailsService;
}
@ -58,39 +54,8 @@ public class TokenLoginFilterService extends UsernamePasswordAuthenticationFilte
ObjectMapper objectMapper = new ObjectMapper();
try {
loginDto = objectMapper.readValue(request.getInputStream(), LoginDto.class);
// type不能为空
String type = loginDto.getType();
if (!StringUtils.hasText(type)) {
out(response, Result.error(ResultCodeEnum.REQUEST_IS_EMPTY));
return null;
}
String emailCode = loginDto.getEmailCode();
String username = loginDto.getUsername();
String password = loginDto.getPassword();
// 如果有邮箱验证码表示是邮箱登录
if (type.equals("email")) {
emailCode = emailCode.toLowerCase();
Object redisEmailCode = redisTemplate.opsForValue().get(RedisUserConstant.getAdminUserEmailCodePrefix(username));
// --------TODO 线上取消注释这个
// ----测试
// --------线上取消注释这个
// if (redisEmailCode == null) {
// out(response, Result.error(ResultCodeEnum.EMAIL_CODE_EMPTY));
// return null;
// }
//
// // 判断用户邮箱验证码是否和Redis中发送的验证码
// if (!emailCode.equals(redisEmailCode.toString().toLowerCase())) {
// out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_MATCHING));
// return null;
// }
}
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
return getAuthenticationManager().authenticate(authenticationToken);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
return getAuthenticationManager().authenticate(authentication);
} catch (IOException e) {
out(response, Result.error(ResultCodeEnum.ILLEGAL_DATA_REQUEST));
return null;

View File

@ -3,26 +3,38 @@ package cn.bunny.services.security.service.impl;
import cn.bunny.common.service.exception.AuthCustomerException;
import cn.bunny.dao.dto.system.user.LoginDto;
import cn.bunny.dao.entity.system.AdminUser;
import cn.bunny.dao.enums.LoginEnums;
import cn.bunny.dao.vo.result.Result;
import cn.bunny.dao.vo.result.ResultCodeEnum;
import cn.bunny.dao.vo.system.user.LoginVo;
import cn.bunny.services.mapper.UserMapper;
import cn.bunny.services.security.custom.CustomUser;
import cn.bunny.services.utils.UserUtil;
import cn.bunny.services.utils.login.DefaultLoginStrategy;
import cn.bunny.services.utils.login.EmailLoginStrategy;
import cn.bunny.services.utils.login.LoginContext;
import cn.bunny.services.utils.login.LoginStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import static cn.bunny.common.service.utils.ResponseUtil.out;
@Component
public class CustomUserDetailsServiceImpl implements cn.bunny.services.security.service.CustomUserDetailsService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
@ -57,18 +69,24 @@ public class CustomUserDetailsServiceImpl implements cn.bunny.services.security.
*/
@Override
public LoginVo login(LoginDto loginDto, HttpServletResponse response) {
String username = loginDto.getUsername();
String password = loginDto.getPassword();
Long readMeDay = loginDto.getReadMeDay();
// 查询用户相关内容
LambdaQueryWrapper<AdminUser> queryWrapper = new LambdaQueryWrapper<>();
if (loginDto.getType().equals("email")) {
queryWrapper.eq(AdminUser::getEmail, username);
} else {
queryWrapper.eq(AdminUser::getUsername, username);
// type不能为空
String type = loginDto.getType();
if (!StringUtils.hasText(type)) {
out(response, Result.error(ResultCodeEnum.REQUEST_IS_EMPTY));
return null;
}
AdminUser user = userMapper.selectOne(queryWrapper);
// 初始化登录策略如果有需要添加策略放在这里
HashMap<String, LoginStrategy> loginStrategyHashMap = new HashMap<>();
loginStrategyHashMap.put(LoginEnums.EMAIL_STRATEGY.getValue(), new EmailLoginStrategy(redisTemplate, userMapper));
loginStrategyHashMap.put(LoginEnums.default_STRATEGY.getValue(), new DefaultLoginStrategy(userMapper));
// 使用登录上下文调用登录策略
LoginContext loginContext = new LoginContext(loginStrategyHashMap);
AdminUser user = loginContext.executeStrategy(type, loginDto, response);
// 判断用户是否为空
if (user == null) {

View File

@ -0,0 +1,35 @@
package cn.bunny.services.utils.login;
import cn.bunny.dao.dto.system.user.LoginDto;
import cn.bunny.dao.entity.system.AdminUser;
import cn.bunny.services.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.servlet.http.HttpServletResponse;
/**
* 使用用户名登录
*/
public class DefaultLoginStrategy implements LoginStrategy {
private final UserMapper userMapper;
public DefaultLoginStrategy(UserMapper userMapper) {
this.userMapper = userMapper;
}
/**
* 登录鉴定方法
*
* @param response 返回的响应
* @param loginDto 登录参数
* @return 鉴定身份验证
*/
@Override
public AdminUser authenticate(HttpServletResponse response, LoginDto loginDto) {
String username = loginDto.getUsername();
String password = loginDto.getPassword();
// 查询用户相关内容
LambdaQueryWrapper<AdminUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AdminUser::getUsername, username);
return userMapper.selectOne(queryWrapper);
}
}

View File

@ -0,0 +1,55 @@
package cn.bunny.services.utils.login;
import cn.bunny.common.service.utils.ResponseUtil;
import cn.bunny.dao.constant.RedisUserConstant;
import cn.bunny.dao.dto.system.user.LoginDto;
import cn.bunny.dao.entity.system.AdminUser;
import cn.bunny.dao.vo.result.Result;
import cn.bunny.dao.vo.result.ResultCodeEnum;
import cn.bunny.services.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
/**
* 邮箱登录策略
*/
public class EmailLoginStrategy implements LoginStrategy {
private final RedisTemplate<String, Object> redisTemplate;
private final UserMapper userMapper;
public EmailLoginStrategy(RedisTemplate<String, Object> redisTemplate, UserMapper userMapper) {
this.redisTemplate = redisTemplate;
this.userMapper = userMapper;
}
/**
* 登录鉴定方法
*
* @param response 返回的响应
* @param loginDto 登录参数
* @return 鉴定身份验证
*/
@Override
public AdminUser authenticate(HttpServletResponse response, LoginDto loginDto) {
String username = loginDto.getUsername();
String emailCode = loginDto.getEmailCode().toLowerCase();
Object redisEmailCode = redisTemplate.opsForValue().get(RedisUserConstant.getAdminUserEmailCodePrefix(username));
if (redisEmailCode == null) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.EMAIL_CODE_EMPTY));
return null;
}
// 判断用户邮箱验证码是否和Redis中发送的验证码
if (!emailCode.equals(redisEmailCode.toString().toLowerCase())) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_MATCHING));
return null;
}
// 查询用户相关内容
LambdaQueryWrapper<AdminUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AdminUser::getEmail, username);
return userMapper.selectOne(queryWrapper);
}
}

View File

@ -0,0 +1,26 @@
package cn.bunny.services.utils.login;
import cn.bunny.dao.dto.system.user.LoginDto;
import cn.bunny.dao.entity.system.AdminUser;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 登录策略上下文
*/
public class LoginContext {
private final Map<String, LoginStrategy> strategies;
public LoginContext(Map<String, LoginStrategy> strategies) {
this.strategies = strategies;
}
public AdminUser executeStrategy(String type, LoginDto loginDto, HttpServletResponse response) {
LoginStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持登录类型: " + type);
}
return strategy.authenticate(response, loginDto);
}
}

View File

@ -0,0 +1,20 @@
package cn.bunny.services.utils.login;
import cn.bunny.dao.dto.system.user.LoginDto;
import cn.bunny.dao.entity.system.AdminUser;
import jakarta.servlet.http.HttpServletResponse;
/**
* 登录策略
*/
public interface LoginStrategy {
/**
* 登录鉴定方法
*
* @param response 返回的响应
* @param loginDto 登录参数
* @return 鉴定身份验证
*/
AdminUser authenticate(HttpServletResponse response, LoginDto loginDto);
}