登录成功
This commit is contained in:
@@ -90,7 +90,6 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT 依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.xyzh.common.core.domain.LoginDomain;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @description AuthController.java文件描述 认证控制器
|
||||
* @filename AuthController.java
|
||||
@@ -25,13 +27,13 @@ public class AuthController {
|
||||
/**
|
||||
* @description 用户登录
|
||||
* @param loginParam 登录参数
|
||||
* @return ResultDomain<String> 登录结果
|
||||
* @return ResultDomain<LoginDomain> 登录结果
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ResultDomain<String> login(@RequestBody LoginParam loginParam) {
|
||||
return loginService.login(loginParam);
|
||||
public ResultDomain<LoginDomain> login(@RequestBody LoginParam loginParam, HttpServletRequest request) {
|
||||
return loginService.login(loginParam, request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.xyzh.auth.security;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xyzh.auth.util.JwtTokenUtil;
|
||||
import org.xyzh.common.core.security.TokenParser;
|
||||
|
||||
/**
|
||||
* @description JwtTokenParser.java文件描述 JWT令牌解析器实现类
|
||||
* @filename JwtTokenParser.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
@Component
|
||||
public class JwtTokenParser implements TokenParser {
|
||||
|
||||
private final JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
/**
|
||||
* Spring会自动注入,无需@Autowired注解
|
||||
*/
|
||||
public JwtTokenParser(JwtTokenUtil jwtTokenUtil) {
|
||||
this.jwtTokenUtil = jwtTokenUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserIdFromToken(String token) {
|
||||
return jwtTokenUtil.getUserIdFromToken(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Claims getAllClaimsFromToken(String token) {
|
||||
return jwtTokenUtil.getAllClaimsFromToken(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateToken(String token, String userId) {
|
||||
return jwtTokenUtil.validateToken(token, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTokenExpired(String token) {
|
||||
return jwtTokenUtil.isTokenExpired(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,15 +11,25 @@ import org.xyzh.common.core.domain.LoginParam;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.enums.UserStatus;
|
||||
import org.xyzh.common.dto.permission.TbSysPermission;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.dto.log.TbSysLoginLog;
|
||||
import org.xyzh.common.dto.menu.TbSysMenu;
|
||||
import org.xyzh.common.exception.auth.AuthException;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.xyzh.api.system.user.UserService;
|
||||
import org.xyzh.api.system.role.RoleService;
|
||||
import org.xyzh.api.system.permission.PermissionService;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.xyzh.api.system.menu.MenuService;
|
||||
import org.xyzh.common.vo.DeptRoleVO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
@@ -38,12 +48,18 @@ public class LoginServiceImpl implements LoginService {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired(required = false)
|
||||
@Autowired
|
||||
private RoleService roleService;
|
||||
|
||||
@Autowired(required = false)
|
||||
@Autowired
|
||||
private PermissionService permissionService;
|
||||
|
||||
@Autowired
|
||||
private MenuService menuService;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@@ -51,9 +67,9 @@ public class LoginServiceImpl implements LoginService {
|
||||
private LoginLogService loginLogService;
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> login(LoginParam loginParam) {
|
||||
ResultDomain<String> result = new ResultDomain<>();
|
||||
|
||||
public ResultDomain<LoginDomain> login(LoginParam loginParam, HttpServletRequest request) {
|
||||
ResultDomain<LoginDomain> result = new ResultDomain<>();
|
||||
String ipAddress = request.getRemoteAddr();
|
||||
try {
|
||||
// 自动检测登录类型
|
||||
String loginType = detectLoginType(loginParam);
|
||||
@@ -84,24 +100,40 @@ public class LoginServiceImpl implements LoginService {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if (!strategy.verifyPassword(loginParam.getPassword(), user.getPassword())) {
|
||||
result.fail("密码错误");
|
||||
logLoginAttempt(loginParam, user, false, "密码错误");
|
||||
return result;
|
||||
if (loginType.equals("password")) {
|
||||
// 验证凭据(密码或验证码)
|
||||
if (!strategy.verifyCredential(loginParam.getPassword(), user.getPassword())) {
|
||||
result.fail("密码错误");
|
||||
logLoginAttempt(loginParam, user, false, "密码错误");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 验证凭据(验证码)
|
||||
String storedCaptcha = (String) redisService.get("captcha:" + loginParam.getPhone());
|
||||
if (!strategy.verifyCredential(loginParam.getCaptcha(), storedCaptcha)) {
|
||||
result.fail("验证码错误");
|
||||
logLoginAttempt(loginParam, user, false, "验证码错误");
|
||||
return result;
|
||||
}
|
||||
// 验证码使用后删除
|
||||
redisService.delete("captcha:" + loginParam.getPhone());
|
||||
}
|
||||
|
||||
// 构建登录域对象
|
||||
LoginDomain loginDomain = buildLoginDomain(user, loginType);
|
||||
|
||||
LoginDomain loginDomain = buildLoginDomain(user, loginType, ipAddress);
|
||||
// 生成JWT令牌
|
||||
String token = jwtTokenUtil.generateToken(loginDomain);
|
||||
loginDomain.setToken(jwtTokenUtil.generateToken(loginDomain));
|
||||
|
||||
// 将LoginDomain存储到Redis中
|
||||
String redisKey = "login:token:" + user.getID();
|
||||
redisService.set(redisKey, loginDomain, 24 * 60 * 60, TimeUnit.SECONDS);
|
||||
|
||||
// 记录成功登录日志
|
||||
logLoginAttempt(loginParam, user, true, "登录成功");
|
||||
|
||||
result.success("登录成功", (String)null);
|
||||
result.setData(token);
|
||||
result.success("登录成功", loginDomain);
|
||||
result.setData(loginDomain);
|
||||
|
||||
} catch (AuthException e) {
|
||||
result.fail(e.getMessage());
|
||||
@@ -139,7 +171,9 @@ public class LoginServiceImpl implements LoginService {
|
||||
if (StringUtils.hasText(loginParam.getLoginType())) {
|
||||
return loginParam.getLoginType();
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(loginParam.getPassword())) {
|
||||
return "password";
|
||||
}
|
||||
if (StringUtils.hasText(loginParam.getEmail())) {
|
||||
return "email";
|
||||
}
|
||||
@@ -164,36 +198,48 @@ public class LoginServiceImpl implements LoginService {
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
private LoginDomain buildLoginDomain(TbSysUser user, String loginType) {
|
||||
private LoginDomain buildLoginDomain(TbSysUser user, String loginType, String ipAddress) {
|
||||
LoginDomain loginDomain = new LoginDomain();
|
||||
loginDomain.setUser(user);
|
||||
loginDomain.setLoginType(loginType);
|
||||
loginDomain.setLoginTime(new Date());
|
||||
|
||||
loginDomain.setIpAddress(ipAddress);
|
||||
// 获取用户角色和权限(如果服务可用)
|
||||
if (roleService != null) {
|
||||
try {
|
||||
// TODO: 需要在RoleService中实现findRolesByUserId方法
|
||||
// loginDomain.setRoles(roleService.findRolesByUserId(user.getID()));
|
||||
loginDomain.setRoles(new ArrayList<>());
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
ResultDomain<DeptRoleVO> resultDomain = roleService.getDeptRolesByUserId(user.getID());
|
||||
if (resultDomain.isSuccess()) {
|
||||
List<DeptRoleVO> roles = resultDomain.getDataList();
|
||||
loginDomain.setRoles(roles);
|
||||
} else {
|
||||
loginDomain.setRoles(new ArrayList<>());
|
||||
}
|
||||
} else {
|
||||
} catch (Exception e) {
|
||||
loginDomain.setRoles(new ArrayList<>());
|
||||
}
|
||||
|
||||
if (permissionService != null) {
|
||||
try {
|
||||
// TODO: 需要在PermissionService中实现findPermissionsByUserId方法
|
||||
// loginDomain.setPermissions(permissionService.findPermissionsByUserId(user.getID()));
|
||||
loginDomain.setPermissions(new ArrayList<>());
|
||||
} catch (Exception e) {
|
||||
|
||||
try {
|
||||
ResultDomain<TbSysPermission> resultDomain = permissionService.getPermissionsByUserId(user.getID());
|
||||
if (resultDomain.isSuccess()) {
|
||||
List<TbSysPermission> permissions = resultDomain.getDataList();
|
||||
loginDomain.setPermissions(permissions);
|
||||
} else {
|
||||
loginDomain.setPermissions(new ArrayList<>());
|
||||
}
|
||||
} else {
|
||||
} catch (Exception e) {
|
||||
loginDomain.setPermissions(new ArrayList<>());
|
||||
}
|
||||
|
||||
try {
|
||||
ResultDomain<TbSysMenu> resultDomain = menuService.getMenusByUserId(user.getID());
|
||||
if (resultDomain.isSuccess()) {
|
||||
List<TbSysMenu> menus = resultDomain.getDataList();
|
||||
loginDomain.setMenus(menus);
|
||||
} else {
|
||||
loginDomain.setMenus(new ArrayList<>());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
loginDomain.setMenus(new ArrayList<>());
|
||||
}
|
||||
|
||||
return loginDomain;
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@ public interface LoginStrategy {
|
||||
TbSysUser findUser(LoginParam loginParam);
|
||||
|
||||
/**
|
||||
* @description 验证密码
|
||||
* @param inputPassword 输入密码
|
||||
* @param storedPassword 存储密码
|
||||
* @description 验证凭据(密码或验证码)
|
||||
* @param inputCredential 输入凭据(密码或验证码)
|
||||
* @param storedCredential 存储凭据(密码或验证码)
|
||||
* @return boolean 是否匹配
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
boolean verifyPassword(String inputPassword, String storedPassword);
|
||||
boolean verifyCredential(String inputCredential, String storedCredential);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class EmailLoginStrategy implements LoginStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyPassword(String inputPassword, String storedPassword) {
|
||||
return passwordEncoder.matches(inputPassword, storedPassword);
|
||||
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||
return passwordEncoder.matches(inputCredential, storedCredential);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.xyzh.auth.strategy.impl;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.xyzh.auth.strategy.LoginStrategy;
|
||||
import org.xyzh.common.core.domain.LoginParam;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.utils.NonUtils;
|
||||
import org.xyzh.api.system.user.UserService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @description PasswordLoginStrategy.java文件描述 密码登录策略
|
||||
* @filename PasswordLoginStrategy.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
@Component
|
||||
public class PasswordLoginStrategy implements LoginStrategy {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PasswordLoginStrategy.class);
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public String getLoginType() {
|
||||
return "password";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(LoginParam loginParam) {
|
||||
if (NonUtils.isEmpty(loginParam.getPassword())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NonUtils.isEmpty(loginParam.getUsername()) && NonUtils.isEmpty(loginParam.getEmail()) && NonUtils.isEmpty(loginParam.getPhone())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbSysUser findUser(LoginParam loginParam) {
|
||||
TbSysUser filter = new TbSysUser();
|
||||
if (NonUtils.isNotEmpty(loginParam.getUsername())) {
|
||||
filter.setUsername(loginParam.getUsername());
|
||||
}
|
||||
if (NonUtils.isNotEmpty(loginParam.getEmail())) {
|
||||
filter.setEmail(loginParam.getEmail());
|
||||
}
|
||||
if (NonUtils.isNotEmpty(loginParam.getPhone())) {
|
||||
filter.setPhone(loginParam.getPhone());
|
||||
}
|
||||
return userService.getUserByFilter(filter).getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||
logger.info(passwordEncoder.encode(inputCredential));
|
||||
return passwordEncoder.matches(inputCredential, storedCredential);
|
||||
}
|
||||
}
|
||||
@@ -43,11 +43,11 @@ public class PhoneLoginStrategy implements LoginStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyPassword(String inputPassword, String storedPassword) {
|
||||
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||
// 手机号登录可能使用验证码,如果有验证码则跳过密码验证
|
||||
if (inputPassword == null) {
|
||||
if (inputCredential == null) {
|
||||
return true; // 假设验证码已经在其他地方验证过了
|
||||
}
|
||||
return passwordEncoder.matches(inputPassword, storedPassword);
|
||||
return passwordEncoder.matches(inputCredential, storedCredential);
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class UsernameLoginStrategy implements LoginStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyPassword(String inputPassword, String storedPassword) {
|
||||
return passwordEncoder.matches(inputPassword, storedPassword);
|
||||
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||
return passwordEncoder.matches(inputCredential, storedCredential);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class WechatLoginStrategy implements LoginStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyPassword(String inputPassword, String storedPassword) {
|
||||
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||
// 微信登录通常不需要密码验证,通过微信授权码验证
|
||||
// 这里可以添加微信授权验证逻辑
|
||||
return true;
|
||||
|
||||
@@ -5,6 +5,21 @@ spring:
|
||||
application:
|
||||
name: school-news-auth
|
||||
|
||||
# Redis配置
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password:
|
||||
database: 0
|
||||
timeout: 5000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-wait: -1ms
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/school_news?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
|
||||
@@ -12,27 +12,29 @@
|
||||
<!-- 定义日志存储的路径 -->
|
||||
<property name="FILE_PATH" value="./logs" />
|
||||
<property name="FILE_NAME" value="school-news-auth" />
|
||||
<property name="file.encoding" value="UTF-8" />
|
||||
<property name="console.encoding" value="UTF-8" />
|
||||
</Properties>
|
||||
|
||||
<appenders>
|
||||
|
||||
<console name="Console" target="SYSTEM_OUT">
|
||||
<!--输出日志的格式-->
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
|
||||
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</console>
|
||||
|
||||
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
|
||||
<File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||
</File>
|
||||
|
||||
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
|
||||
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
|
||||
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||
<Policies>
|
||||
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
|
||||
<TimeBasedTriggeringPolicy interval="1"/>
|
||||
@@ -43,10 +45,10 @@
|
||||
</RollingFile>
|
||||
|
||||
<!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
|
||||
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
|
||||
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||
<Policies>
|
||||
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
|
||||
<TimeBasedTriggeringPolicy interval="1"/>
|
||||
@@ -57,10 +59,10 @@
|
||||
</RollingFile>
|
||||
|
||||
<!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
|
||||
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
|
||||
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||
<Policies>
|
||||
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
|
||||
<TimeBasedTriggeringPolicy interval="1"/>
|
||||
|
||||
Reference in New Issue
Block a user