diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/hotake/HotakeSecurityController.java b/vetti-admin/src/main/java/com/vetti/web/controller/hotake/HotakeSecurityController.java new file mode 100644 index 0000000..be29446 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/hotake/HotakeSecurityController.java @@ -0,0 +1,106 @@ +package com.vetti.web.controller.hotake; + +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.R; +import com.vetti.common.utils.MessageUtils; +import com.vetti.hotake.domain.HotakeSecuritySettings; +import com.vetti.hotake.domain.dto.SecurityChangePasswordDto; +import com.vetti.hotake.domain.vo.SecuritySessionVo; +import com.vetti.hotake.service.IHotakeSecurityService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 安全设置控制器 + * + * @author vetti + * @date 2026-02-02 + */ +@Api(tags = "安全设置模块") +@RestController +@RequestMapping("/security") +public class HotakeSecurityController +{ + @Autowired + private IHotakeSecurityService securityService; + + /** + * 获取当前用户的安全设置 + */ + @ApiOperation("获取当前用户的安全设置") + @GetMapping("/settings") + public R getSecuritySettings() + { + HotakeSecuritySettings settings = securityService.getCurrentUserSecuritySettings(); + // 脱敏处理,不返回敏感信息 + settings.setTwoFactorSecret(null); + settings.setBackupCodes(null); + settings.setPasswordResetToken(null); + return R.ok(settings, ""); + } + + /** + * 更新两步验证设置 + */ + @ApiOperation("更新两步验证设置") + @PutMapping("/two-factor") + public AjaxResult updateTwoFactorEnabled( + @ApiParam(value = "是否启用", required = true) + @RequestParam Boolean enabled) + { + securityService.updateTwoFactorEnabled(enabled); + return AjaxResult.success(MessageUtils.messageCustomize("HotakeSecurityController10001")); + } + + /** + * 修改密码 + */ + @ApiOperation("修改密码") + @PostMapping("/change-password") + public AjaxResult changePassword(@Validated @RequestBody SecurityChangePasswordDto dto) + { + securityService.changePassword(dto); + return AjaxResult.success(MessageUtils.messageCustomize("HotakeSecurityController10002")); + } + + /** + * 获取活跃会话列表 + */ + @ApiOperation("获取活跃会话列表") + @GetMapping("/sessions") + public R> getActiveSessions() + { + List sessions = securityService.getActiveSessions(); + return R.ok(sessions, ""); + } + + /** + * 终止指定会话 + */ + @ApiOperation("终止指定会话") + @DeleteMapping("/sessions/{sessionId}") + public AjaxResult terminateSession( + @ApiParam(value = "会话ID", required = true) + @PathVariable Long sessionId) + { + securityService.terminateSession(sessionId); + return AjaxResult.success(MessageUtils.messageCustomize("HotakeSecurityController10003")); + } + + /** + * 终止所有其他会话 + */ + @ApiOperation("终止所有其他会话") + @DeleteMapping("/sessions/terminate-all") + public AjaxResult terminateAllOtherSessions() + { + securityService.terminateAllOtherSessions(); + return AjaxResult.success(MessageUtils.messageCustomize("HotakeSecurityController10004")); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysLoginController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysLoginController.java index 62bd5f6..5b2f6b2 100644 --- a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysLoginController.java +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysLoginController.java @@ -71,6 +71,28 @@ public class SysLoginController LoginDto loginDto = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid()); + // TODO: 安全功能集成 - 记录登录会话到安全表 + // 在用户成功登录后,记录会话信息到hotake_security_login_sessions表 + // 用于支持"Login Sessions"功能,显示用户的所有活跃会话 + try { + String ipAddress = com.vetti.common.utils.ip.IpUtils.getIpAddr(); + String userAgent = com.vetti.common.utils.ServletUtils.getRequest().getHeader("User-Agent"); + // 使用Spring的ApplicationContext来获取bean,避免循环依赖 + try { + Object securityService = com.vetti.common.utils.spring.SpringUtils.getBean("hotakeSecurityServiceImpl"); + if (securityService != null) { + // 使用反射调用方法 + java.lang.reflect.Method method = securityService.getClass().getMethod( + "recordLoginSession", Long.class, String.class, String.class, String.class); + method.invoke(securityService, loginDto.getUserId(), loginDto.getToken(), ipAddress, userAgent); + } + } catch (Exception e) { + // 安全服务不存在或调用失败,不影响登录流程 + } + } catch (Exception e) { + // 记录会话失败不影响登录流程 + } + // 如果是候选者,查询是否有简历 if (loginDto.getUser() != null && "candidate".equals(loginDto.getUser().getSysUserType())) { HotakeCvInfo query = new HotakeCvInfo(); @@ -180,11 +202,7 @@ public class SysLoginController public AjaxResult logout() { LoginUser loginUser = SecurityUtils.getLoginUser(); - if (loginUser != null) - { - // 删除用户缓存记录 - tokenService.delLoginUser(loginUser.getToken()); - } + loginService.logout(loginUser); return AjaxResult.success("退出成功"); } } diff --git a/vetti-admin/src/main/resources/application-druid.yml b/vetti-admin/src/main/resources/application-druid.yml index ba7c81c..561345c 100644 --- a/vetti-admin/src/main/resources/application-druid.yml +++ b/vetti-admin/src/main/resources/application-druid.yml @@ -2,7 +2,7 @@ # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 - port: 8080 + port: 8686 servlet: # 应用的访问路径 context-path: / diff --git a/vetti-admin/src/main/resources/i18n/messages_en_US.properties b/vetti-admin/src/main/resources/i18n/messages_en_US.properties index f49a7cc..7818bc2 100644 --- a/vetti-admin/src/main/resources/i18n/messages_en_US.properties +++ b/vetti-admin/src/main/resources/i18n/messages_en_US.properties @@ -65,4 +65,24 @@ VerificationEmailTiTle = Your verification code VerificationEmailContent = Your verification code is: {0}, valid for {1} minutes. -HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position \ No newline at end of file +HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position + +# Security settings related messages +HotakeSecurityServiceImpl10001 = New password and confirm password do not match +HotakeSecurityServiceImpl10002 = Current password is incorrect +HotakeSecurityServiceImpl10003 = New password cannot be the same as the last 5 passwords used +HotakeSecurityServiceImpl10004 = Session does not exist or no permission to operate +HotakeSecurityServiceImpl10005 = Failed to change password: current password is incorrect +HotakeSecurityServiceImpl10006 = Two-factor authentication enabled +HotakeSecurityServiceImpl10007 = Two-factor authentication disabled +HotakeSecurityServiceImpl10008 = Password changed successfully +HotakeSecurityServiceImpl10009 = Terminate session +HotakeSecurityServiceImpl10010 = Terminate all other sessions + +HotakeSecurityController10001 = Two-factor authentication settings updated +HotakeSecurityController10002 = Password changed successfully +HotakeSecurityController10003 = Session terminated +HotakeSecurityController10004 = All other sessions terminated + +# Logout related +HotakeSecurityServiceImpl10011 = User logged out \ No newline at end of file diff --git a/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties b/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties index 90b799f..c24a4f3 100644 --- a/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties +++ b/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties @@ -62,4 +62,24 @@ HotakeRolesInfoServiceImpl10001 = 岗位信息异常,请稍后再试 VerificationEmailTiTle = 你的验证码 VerificationEmailContent = 你的验证码是: {0},有效期为 {1} 分钟。 -HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位 \ No newline at end of file +HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位 + +# 安全设置相关消息 +HotakeSecurityServiceImpl10001 = 新密码和确认密码不一致 +HotakeSecurityServiceImpl10002 = 当前密码错误 +HotakeSecurityServiceImpl10003 = 新密码不能与最近5次使用过的密码相同 +HotakeSecurityServiceImpl10004 = 会话不存在或无权操作 +HotakeSecurityServiceImpl10005 = 修改密码失败:当前密码错误 +HotakeSecurityServiceImpl10006 = 启用两步验证 +HotakeSecurityServiceImpl10007 = 禁用两步验证 +HotakeSecurityServiceImpl10008 = 修改密码成功 +HotakeSecurityServiceImpl10009 = 终止会话 +HotakeSecurityServiceImpl10010 = 终止所有其他会话 + +HotakeSecurityController10001 = 两步验证设置已更新 +HotakeSecurityController10002 = 密码修改成功 +HotakeSecurityController10003 = 会话已终止 +HotakeSecurityController10004 = 所有其他会话已终止 + +# 退出登录相关 +HotakeSecurityServiceImpl10011 = 用户退出登录 \ No newline at end of file diff --git a/vetti-admin/target/classes/i18n/messages_en_US.properties b/vetti-admin/target/classes/i18n/messages_en_US.properties index f49a7cc..7818bc2 100644 --- a/vetti-admin/target/classes/i18n/messages_en_US.properties +++ b/vetti-admin/target/classes/i18n/messages_en_US.properties @@ -65,4 +65,24 @@ VerificationEmailTiTle = Your verification code VerificationEmailContent = Your verification code is: {0}, valid for {1} minutes. -HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position \ No newline at end of file +HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position + +# Security settings related messages +HotakeSecurityServiceImpl10001 = New password and confirm password do not match +HotakeSecurityServiceImpl10002 = Current password is incorrect +HotakeSecurityServiceImpl10003 = New password cannot be the same as the last 5 passwords used +HotakeSecurityServiceImpl10004 = Session does not exist or no permission to operate +HotakeSecurityServiceImpl10005 = Failed to change password: current password is incorrect +HotakeSecurityServiceImpl10006 = Two-factor authentication enabled +HotakeSecurityServiceImpl10007 = Two-factor authentication disabled +HotakeSecurityServiceImpl10008 = Password changed successfully +HotakeSecurityServiceImpl10009 = Terminate session +HotakeSecurityServiceImpl10010 = Terminate all other sessions + +HotakeSecurityController10001 = Two-factor authentication settings updated +HotakeSecurityController10002 = Password changed successfully +HotakeSecurityController10003 = Session terminated +HotakeSecurityController10004 = All other sessions terminated + +# Logout related +HotakeSecurityServiceImpl10011 = User logged out \ No newline at end of file diff --git a/vetti-admin/target/classes/i18n/messages_zh_CN.properties b/vetti-admin/target/classes/i18n/messages_zh_CN.properties index 90b799f..c24a4f3 100644 --- a/vetti-admin/target/classes/i18n/messages_zh_CN.properties +++ b/vetti-admin/target/classes/i18n/messages_zh_CN.properties @@ -62,4 +62,24 @@ HotakeRolesInfoServiceImpl10001 = 岗位信息异常,请稍后再试 VerificationEmailTiTle = 你的验证码 VerificationEmailContent = 你的验证码是: {0},有效期为 {1} 分钟。 -HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位 \ No newline at end of file +HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位 + +# 安全设置相关消息 +HotakeSecurityServiceImpl10001 = 新密码和确认密码不一致 +HotakeSecurityServiceImpl10002 = 当前密码错误 +HotakeSecurityServiceImpl10003 = 新密码不能与最近5次使用过的密码相同 +HotakeSecurityServiceImpl10004 = 会话不存在或无权操作 +HotakeSecurityServiceImpl10005 = 修改密码失败:当前密码错误 +HotakeSecurityServiceImpl10006 = 启用两步验证 +HotakeSecurityServiceImpl10007 = 禁用两步验证 +HotakeSecurityServiceImpl10008 = 修改密码成功 +HotakeSecurityServiceImpl10009 = 终止会话 +HotakeSecurityServiceImpl10010 = 终止所有其他会话 + +HotakeSecurityController10001 = 两步验证设置已更新 +HotakeSecurityController10002 = 密码修改成功 +HotakeSecurityController10003 = 会话已终止 +HotakeSecurityController10004 = 所有其他会话已终止 + +# 退出登录相关 +HotakeSecurityServiceImpl10011 = 用户退出登录 \ No newline at end of file diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/service/SysLoginService.java b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysLoginService.java index 5f55216..45cf9e0 100644 --- a/vetti-framework/src/main/java/com/vetti/framework/web/service/SysLoginService.java +++ b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysLoginService.java @@ -233,4 +233,35 @@ public class SysLoginService userService.resetUserPwd(sysUser.getUserId(), SecurityUtils.encryptPassword(password)); } + /** + * 退出登录 + * + * @param loginUser 登录用户信息 + */ + public void logout(LoginUser loginUser) + { + if (loginUser != null) + { + String token = loginUser.getToken(); + + // TODO: 安全功能集成 - 更新会话状态为已登出 + // 在用户退出登录时,更新hotake_security_login_sessions表中的会话状态 + // 记录登出时间,并将is_active设置为0 + try { + Object securityService = com.vetti.common.utils.spring.SpringUtils.getBean("hotakeSecurityServiceImpl"); + if (securityService != null) { + // 使用反射调用更新会话状态的方法 + java.lang.reflect.Method method = securityService.getClass().getMethod( + "updateSessionLogout", String.class); + method.invoke(securityService, token); + } + } catch (Exception e) { + // 更新会话状态失败不影响退出流程 + } + + // 删除用户缓存记录 + tokenService.delLoginUser(token); + } + } + } diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityLoginSessions.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityLoginSessions.java new file mode 100644 index 0000000..1453222 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityLoginSessions.java @@ -0,0 +1,115 @@ +package com.vetti.hotake.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * 用户登录会话对象 hotake_security_login_sessions + * + * @author vetti + * @date 2026-02-02 + */ +@Data +@Accessors(chain = true) +@ApiModel("用户登录会话") +public class HotakeSecurityLoginSessions extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @ApiModelProperty("主键ID") + private Long id; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 会话ID */ + @ApiModelProperty("会话ID") + @Excel(name = "会话ID") + private String sessionId; + + /** 会话令牌 */ + @ApiModelProperty("会话令牌") + private String sessionToken; + + /** 设备类型 Desktop/Mobile/Tablet */ + @ApiModelProperty("设备类型") + @Excel(name = "设备类型") + private String deviceType; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + @Excel(name = "设备名称") + private String deviceName; + + /** 浏览器 */ + @ApiModelProperty("浏览器") + @Excel(name = "浏览器") + private String browser; + + /** 浏览器版本 */ + @ApiModelProperty("浏览器版本") + private String browserVersion; + + /** 操作系统 */ + @ApiModelProperty("操作系统") + @Excel(name = "操作系统") + private String os; + + /** 操作系统版本 */ + @ApiModelProperty("操作系统版本") + private String osVersion; + + /** IP地址 */ + @ApiModelProperty("IP地址") + @Excel(name = "IP地址") + private String ipAddress; + + /** 国家 */ + @ApiModelProperty("国家") + @Excel(name = "国家") + private String locationCountry; + + /** 城市 */ + @ApiModelProperty("城市") + @Excel(name = "城市") + private String locationCity; + + /** 是否活跃 0-否 1-是 */ + @ApiModelProperty("是否活跃") + @Excel(name = "是否活跃", readConverterExp = "0=否,1=是") + private Integer isActive; + + /** 最后活动时间 */ + @ApiModelProperty("最后活动时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "最后活动时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date lastActivityTime; + + /** 登录时间 */ + @ApiModelProperty("登录时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date loginTime; + + /** 登出时间 */ + @ApiModelProperty("登出时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "登出时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date logoutTime; + + /** 会话过期时间 */ + @ApiModelProperty("会话过期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "会话过期时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date expiresAt; +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityPasswordHistory.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityPasswordHistory.java new file mode 100644 index 0000000..6929b82 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityPasswordHistory.java @@ -0,0 +1,54 @@ +package com.vetti.hotake.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * 密码历史记录对象 hotake_security_password_history + * + * @author vetti + * @date 2026-02-02 + */ +@Data +@Accessors(chain = true) +@ApiModel("密码历史记录") +public class HotakeSecurityPasswordHistory extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @ApiModelProperty("主键ID") + private Long id; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 密码哈希值 */ + @ApiModelProperty("密码哈希值") + private String passwordHash; + + /** 修改时间 */ + @ApiModelProperty("修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "修改时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date changedAt; + + /** 修改方式 USER/ADMIN/RESET */ + @ApiModelProperty("修改方式") + @Excel(name = "修改方式") + private String changedBy; + + /** IP地址 */ + @ApiModelProperty("IP地址") + @Excel(name = "IP地址") + private String ipAddress; +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecuritySecurityLogs.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecuritySecurityLogs.java new file mode 100644 index 0000000..029f837 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecuritySecurityLogs.java @@ -0,0 +1,67 @@ +package com.vetti.hotake.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * 安全日志对象 hotake_security_security_logs + * + * @author vetti + * @date 2026-02-02 + */ +@Data +@Accessors(chain = true) +@ApiModel("安全日志") +public class HotakeSecuritySecurityLogs extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @ApiModelProperty("主键ID") + private Long id; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 事件类型 */ + @ApiModelProperty("事件类型") + @Excel(name = "事件类型") + private String eventType; + + /** 事件描述 */ + @ApiModelProperty("事件描述") + @Excel(name = "事件描述") + private String eventDescription; + + /** IP地址 */ + @ApiModelProperty("IP地址") + @Excel(name = "IP地址") + private String ipAddress; + + /** 用户代理 */ + @ApiModelProperty("用户代理") + private String userAgent; + + /** 地理位置 */ + @ApiModelProperty("地理位置") + @Excel(name = "地理位置") + private String location; + + /** 状态 SUCCESS/FAILED/BLOCKED */ + @ApiModelProperty("状态") + @Excel(name = "状态") + private String status; + + /** 元数据(JSON格式) */ + @ApiModelProperty("元数据") + private String metadata; +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecuritySettings.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecuritySettings.java new file mode 100644 index 0000000..ea1361e --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecuritySettings.java @@ -0,0 +1,78 @@ +package com.vetti.hotake.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * 用户安全设置对象 hotake_security_settings + * + * @author vetti + * @date 2026-02-02 + */ +@Data +@Accessors(chain = true) +@ApiModel("用户安全设置") +public class HotakeSecuritySettings extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @ApiModelProperty("主键ID") + private Long id; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 是否启用两步验证 0-否 1-是 */ + @ApiModelProperty("是否启用两步验证 0-否 1-是") + @Excel(name = "是否启用两步验证", readConverterExp = "0=否,1=是") + private Integer twoFactorEnabled; + + /** 两步验证密钥 */ + @ApiModelProperty("两步验证密钥") + private String twoFactorSecret; + + /** 两步验证类型 TOTP/SMS/EMAIL */ + @ApiModelProperty("两步验证类型") + @Excel(name = "两步验证类型") + private String twoFactorType; + + /** 备份验证码(JSON格式) */ + @ApiModelProperty("备份验证码") + private String backupCodes; + + /** 密码最后修改时间 */ + @ApiModelProperty("密码最后修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "密码最后修改时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date passwordChangedAt; + + /** 密码重置令牌 */ + @ApiModelProperty("密码重置令牌") + private String passwordResetToken; + + /** 密码重置令牌过期时间 */ + @ApiModelProperty("密码重置令牌过期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date passwordResetExpires; + + /** 失败登录尝试次数 */ + @ApiModelProperty("失败登录尝试次数") + @Excel(name = "失败登录尝试次数") + private Integer failedLoginAttempts; + + /** 账户锁定截止时间 */ + @ApiModelProperty("账户锁定截止时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "账户锁定截止时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date accountLockedUntil; +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityTrustedDevices.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityTrustedDevices.java new file mode 100644 index 0000000..6cbcb38 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeSecurityTrustedDevices.java @@ -0,0 +1,80 @@ +package com.vetti.hotake.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * 可信设备对象 hotake_security_trusted_devices + * + * @author vetti + * @date 2026-02-02 + */ +@Data +@Accessors(chain = true) +@ApiModel("可信设备") +public class HotakeSecurityTrustedDevices extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @ApiModelProperty("主键ID") + private Long id; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 设备唯一标识 */ + @ApiModelProperty("设备唯一标识") + @Excel(name = "设备唯一标识") + private String deviceId; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + @Excel(name = "设备名称") + private String deviceName; + + /** 设备类型 */ + @ApiModelProperty("设备类型") + @Excel(name = "设备类型") + private String deviceType; + + /** 浏览器 */ + @ApiModelProperty("浏览器") + @Excel(name = "浏览器") + private String browser; + + /** 操作系统 */ + @ApiModelProperty("操作系统") + @Excel(name = "操作系统") + private String os; + + /** IP地址 */ + @ApiModelProperty("IP地址") + @Excel(name = "IP地址") + private String ipAddress; + + /** 地理位置 */ + @ApiModelProperty("地理位置") + @Excel(name = "地理位置") + private String location; + + /** 是否可信 0-否 1-是 */ + @ApiModelProperty("是否可信") + @Excel(name = "是否可信", readConverterExp = "0=否,1=是") + private Integer isTrusted; + + /** 信任过期时间 */ + @ApiModelProperty("信任过期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "信任过期时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date trustExpiresAt; +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeSecurityService.java b/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeSecurityService.java new file mode 100644 index 0000000..fa5ce6e --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeSecurityService.java @@ -0,0 +1,100 @@ +package com.vetti.hotake.service; + +import com.vetti.hotake.domain.HotakeSecuritySettings; +import com.vetti.hotake.domain.dto.SecurityChangePasswordDto; +import com.vetti.hotake.domain.vo.SecuritySessionVo; + +import java.util.List; + +/** + * 安全设置Service接口 + * + * @author vetti + * @date 2026-02-02 + */ +public interface IHotakeSecurityService +{ + /** + * 获取当前用户的安全设置 + * 如果不存在则自动创建默认设置 + * + * @return 安全设置 + */ + HotakeSecuritySettings getCurrentUserSecuritySettings(); + + /** + * 更新两步验证设置 + * + * @param enabled 是否启用 + * @return 结果 + */ + int updateTwoFactorEnabled(boolean enabled); + + /** + * 修改密码 + * + * @param dto 修改密码DTO + * @return 结果 + */ + void changePassword(SecurityChangePasswordDto dto); + + /** + * 获取当前用户的活跃会话列表 + * + * @return 会话列表 + */ + List getActiveSessions(); + + /** + * 终止指定会话 + * + * @param sessionId 会话ID + * @return 结果 + */ + int terminateSession(Long sessionId); + + /** + * 终止所有其他会话(除了当前会话) + * + * @return 结果 + */ + int terminateAllOtherSessions(); + + /** + * 记录登录会话 + * + * @param userId 用户ID + * @param sessionToken 会话令牌 + * @param ipAddress IP地址 + * @param userAgent 用户代理 + * @return 结果 + */ + int recordLoginSession(Long userId, String sessionToken, String ipAddress, String userAgent); + + /** + * 更新会话活动时间 + * + * @param sessionToken 会话令牌 + * @return 结果 + */ + int updateSessionActivity(String sessionToken); + + /** + * 记录安全日志 + * + * @param userId 用户ID + * @param eventType 事件类型 + * @param eventDescription 事件描述 + * @param status 状态 + * @return 结果 + */ + int recordSecurityLog(Long userId, String eventType, String eventDescription, String status); + + /** + * 更新会话为已登出状态 + * + * @param sessionToken 会话令牌 + * @return 结果 + */ + int updateSessionLogout(String sessionToken); +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeSecurityServiceImpl.java b/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeSecurityServiceImpl.java new file mode 100644 index 0000000..0683b09 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeSecurityServiceImpl.java @@ -0,0 +1,454 @@ +package com.vetti.hotake.service.impl; + +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.service.BaseServiceImpl; +import com.vetti.common.enums.FillTypeEnum; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.ServletUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.ip.AddressUtils; +import com.vetti.common.utils.ip.IpUtils; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.hotake.domain.HotakeSecurityLoginSessions; +import com.vetti.hotake.domain.HotakeSecurityPasswordHistory; +import com.vetti.hotake.domain.HotakeSecuritySecurityLogs; +import com.vetti.hotake.domain.HotakeSecuritySettings; +import com.vetti.hotake.domain.dto.SecurityChangePasswordDto; +import com.vetti.hotake.domain.vo.SecuritySessionVo; +import com.vetti.hotake.mapper.HotakeSecurityLoginSessionsMapper; +import com.vetti.hotake.mapper.HotakeSecurityPasswordHistoryMapper; +import com.vetti.hotake.mapper.HotakeSecuritySecurityLogsMapper; +import com.vetti.hotake.mapper.HotakeSecuritySettingsMapper; +import com.vetti.hotake.service.IHotakeSecurityService; +import com.vetti.system.service.ISysUserService; +import eu.bitwalker.useragentutils.Browser; +import eu.bitwalker.useragentutils.OperatingSystem; +import eu.bitwalker.useragentutils.UserAgent; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 安全设置Service业务层处理 + * + * @author vetti + * @date 2026-02-02 + */ +@Service +public class HotakeSecurityServiceImpl extends BaseServiceImpl implements IHotakeSecurityService +{ + @Autowired + private HotakeSecuritySettingsMapper securitySettingsMapper; + + @Autowired + private HotakeSecurityLoginSessionsMapper loginSessionsMapper; + + @Autowired + private HotakeSecurityPasswordHistoryMapper passwordHistoryMapper; + + @Autowired + private HotakeSecuritySecurityLogsMapper securityLogsMapper; + + @Autowired + private ISysUserService userService; + + @Autowired + private RedisCache redisCache; + + @Value("${token.expireTime}") + private int expireTime; + + /** + * 获取当前用户的安全设置 + * 如果不存在则自动创建默认设置 + * + * @return 安全设置 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public HotakeSecuritySettings getCurrentUserSecuritySettings() + { + Long userId = SecurityUtils.getUserId(); + + // 查询用户的安全设置 + HotakeSecuritySettings settings = securitySettingsMapper.selectHotakeSecuritySettingsByUserId(userId); + + // 如果不存在,创建默认设置 + if (settings == null) + { + settings = new HotakeSecuritySettings(); + settings.setUserId(userId); + settings.setTwoFactorEnabled(0); + settings.setTwoFactorType("TOTP"); + settings.setFailedLoginAttempts(0); + + fill(FillTypeEnum.INSERT.getCode(), settings); + securitySettingsMapper.insertHotakeSecuritySettings(settings); + } + + return settings; + } + + /** + * 更新两步验证设置 + * + * @param enabled 是否启用 + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int updateTwoFactorEnabled(boolean enabled) + { + Long userId = SecurityUtils.getUserId(); + HotakeSecuritySettings settings = getCurrentUserSecuritySettings(); + + settings.setTwoFactorEnabled(enabled ? 1 : 0); + fill(FillTypeEnum.UPDATE.getCode(), settings); + + // 记录安全日志 + String eventType = enabled ? "2FA_ENABLED" : "2FA_DISABLED"; + String eventDesc = enabled ? + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10006") : + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10007"); + recordSecurityLog(userId, eventType, eventDesc, "SUCCESS"); + + return securitySettingsMapper.updateHotakeSecuritySettings(settings); + } + + /** + * 修改密码 + * + * @param dto 修改密码DTO + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void changePassword(SecurityChangePasswordDto dto) + { + Long userId = SecurityUtils.getUserId(); + String username = SecurityUtils.getUsername(); + + // 验证新密码和确认密码是否一致 + if (!dto.getNewPassword().equals(dto.getConfirmPassword())) + { + throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10001")); + } + + // 验证当前密码是否正确 + if (!SecurityUtils.matchesPassword(dto.getCurrentPassword(), SecurityUtils.getLoginUser().getPassword())) + { + // 记录失败日志 + recordSecurityLog(userId, "PASSWORD_CHANGE", + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10005"), "FAILED"); + throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10002")); + } + + // TODO: 安全功能集成 - 检查新密码是否与历史密码重复 + // 查询最近5次的密码历史 + List historyList = passwordHistoryMapper.selectRecentPasswordHistory(userId, 5); + String newPasswordHash = SecurityUtils.encryptPassword(dto.getNewPassword()); + + for (HotakeSecurityPasswordHistory history : historyList) + { + if (SecurityUtils.matchesPassword(dto.getNewPassword(), history.getPasswordHash())) + { + throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10003")); + } + } + + // 修改密码 + userService.resetUserPwd(userId, newPasswordHash); + + // 记录密码历史 + HotakeSecurityPasswordHistory history = new HotakeSecurityPasswordHistory(); + history.setUserId(userId); + history.setPasswordHash(newPasswordHash); + history.setChangedAt(new Date()); + history.setChangedBy("USER"); + history.setIpAddress(IpUtils.getIpAddr()); + fill(FillTypeEnum.INSERT.getCode(), history); + passwordHistoryMapper.insertHotakeSecurityPasswordHistory(history); + + // 更新安全设置中的密码修改时间 + HotakeSecuritySettings settings = getCurrentUserSecuritySettings(); + settings.setPasswordChangedAt(new Date()); + fill(FillTypeEnum.UPDATE.getCode(), settings); + securitySettingsMapper.updateHotakeSecuritySettings(settings); + + // 记录安全日志 + recordSecurityLog(userId, "PASSWORD_CHANGE", + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10008"), "SUCCESS"); + } + + /** + * 获取当前用户的活跃会话列表 + * + * @return 会话列表 + */ + @Transactional(readOnly = true) + @Override + public List getActiveSessions() + { + Long userId = SecurityUtils.getUserId(); + LoginUser loginUser = SecurityUtils.getLoginUser(); + String currentToken = loginUser.getToken(); + + // 查询用户的活跃会话 + List sessionsList = loginSessionsMapper.selectActiveSessionsByUserId(userId); + + // 转换为VO + List voList = new ArrayList<>(); + for (HotakeSecurityLoginSessions session : sessionsList) + { + SecuritySessionVo vo = new SecuritySessionVo(); + BeanUtils.copyProperties(session, vo); + + // 组合设备名称 + String deviceName = ""; + if (StringUtils.isNotEmpty(session.getBrowser())) + { + deviceName = session.getBrowser(); + } + if (StringUtils.isNotEmpty(session.getOs())) + { + deviceName += (StringUtils.isNotEmpty(deviceName) ? " / " : "") + session.getOs(); + } + vo.setDeviceName(deviceName); + + // 组合地理位置 + String location = ""; + if (StringUtils.isNotEmpty(session.getLocationCity())) + { + location = session.getLocationCity(); + } + if (StringUtils.isNotEmpty(session.getLocationCountry())) + { + location += (StringUtils.isNotEmpty(location) ? ", " : "") + session.getLocationCountry(); + } + vo.setLocation(location); + + // 判断是否为当前会话 + vo.setIsCurrent(session.getSessionToken().equals(currentToken)); + vo.setIsActive(session.getIsActive() == 1); + + voList.add(vo); + } + + return voList; + } + + /** + * 终止指定会话 + * + * @param sessionId 会话ID + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int terminateSession(Long sessionId) + { + Long userId = SecurityUtils.getUserId(); + + // 查询会话信息 + HotakeSecurityLoginSessions session = loginSessionsMapper.selectHotakeSecurityLoginSessionsById(sessionId); + if (session == null || !session.getUserId().equals(userId)) + { + throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10004")); + } + + // TODO: 安全功能集成 - 从Redis中删除对应的token缓存 + // 这样可以立即使该会话失效 + // 使用RedisCache直接删除,避免依赖TokenService + String userKey = CacheConstants.LOGIN_TOKEN_KEY + session.getSessionToken(); + redisCache.deleteObject(userKey); + + // 更新会话状态 + int result = loginSessionsMapper.terminateSession(sessionId, userId); + + // 记录安全日志 + recordSecurityLog(userId, "SESSION_TERMINATE", + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10009") + ":" + session.getDeviceName(), + "SUCCESS"); + + return result; + } + + /** + * 终止所有其他会话(除了当前会话) + * + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int terminateAllOtherSessions() + { + Long userId = SecurityUtils.getUserId(); + LoginUser loginUser = SecurityUtils.getLoginUser(); + String currentToken = loginUser.getToken(); + + // TODO: 安全功能集成 - 从Redis中批量删除其他会话的token缓存 + // 查询所有其他活跃会话 + List otherSessions = loginSessionsMapper.selectActiveSessionsByUserId(userId); + for (HotakeSecurityLoginSessions session : otherSessions) + { + if (!session.getSessionToken().equals(currentToken)) + { + // 使用RedisCache直接删除,避免依赖TokenService + String userKey = CacheConstants.LOGIN_TOKEN_KEY + session.getSessionToken(); + redisCache.deleteObject(userKey); + } + } + + // 更新会话状态 + int result = loginSessionsMapper.terminateOtherSessions(userId, currentToken); + + // 记录安全日志 + recordSecurityLog(userId, "SESSION_TERMINATE_ALL", + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10010"), + "SUCCESS"); + + return result; + } + + /** + * 记录登录会话 + * + * @param userId 用户ID + * @param sessionToken 会话令牌 + * @param ipAddress IP地址 + * @param userAgent 用户代理 + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int recordLoginSession(Long userId, String sessionToken, String ipAddress, String userAgent) + { + // 解析User-Agent + UserAgent ua = UserAgent.parseUserAgentString(userAgent); + Browser browser = ua.getBrowser(); + OperatingSystem os = ua.getOperatingSystem(); + + // 获取地理位置 + String location = AddressUtils.getRealAddressByIP(ipAddress); + String[] locationParts = location.split(" "); + String country = locationParts.length > 0 ? locationParts[0] : ""; + String city = locationParts.length > 1 ? locationParts[1] : ""; + + // 判断设备类型 + String deviceType = "Desktop"; + if (os.getDeviceType() != null) + { + deviceType = os.getDeviceType().getName(); + } + + // 创建会话记录 + HotakeSecurityLoginSessions session = new HotakeSecurityLoginSessions(); + session.setUserId(userId); + session.setSessionId(sessionToken); + session.setSessionToken(sessionToken); + session.setDeviceType(deviceType); + session.setDeviceName(browser.getName() + " / " + os.getName()); + session.setBrowser(browser.getName()); + session.setBrowserVersion(ua.getBrowserVersion() != null ? ua.getBrowserVersion().getVersion() : ""); + session.setOs(os.getName()); + session.setOsVersion(""); + session.setIpAddress(ipAddress); + session.setLocationCountry(country); + session.setLocationCity(city); + session.setIsActive(1); + session.setLoginTime(new Date()); + session.setLastActivityTime(new Date()); + + // 计算过期时间 + Date expiresAt = new Date(System.currentTimeMillis() + expireTime * 60 * 1000L); + session.setExpiresAt(expiresAt); + + fill(FillTypeEnum.INSERT.getCode(), session); + return loginSessionsMapper.insertHotakeSecurityLoginSessions(session); + } + + /** + * 更新会话活动时间 + * + * @param sessionToken 会话令牌 + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int updateSessionActivity(String sessionToken) + { + HotakeSecurityLoginSessions session = loginSessionsMapper.selectHotakeSecurityLoginSessionsByToken(sessionToken); + if (session != null) + { + session.setLastActivityTime(new Date()); + fill(FillTypeEnum.UPDATE.getCode(), session); + return loginSessionsMapper.updateHotakeSecurityLoginSessions(session); + } + return 0; + } + + /** + * 记录安全日志 + * + * @param userId 用户ID + * @param eventType 事件类型 + * @param eventDescription 事件描述 + * @param status 状态 + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int recordSecurityLog(Long userId, String eventType, String eventDescription, String status) + { + HotakeSecuritySecurityLogs log = new HotakeSecuritySecurityLogs(); + log.setUserId(userId); + log.setEventType(eventType); + log.setEventDescription(eventDescription); + log.setIpAddress(IpUtils.getIpAddr()); + log.setUserAgent(StringUtils.substring(ServletUtils.getRequest().getHeader("User-Agent"), 0, 500)); + log.setLocation(AddressUtils.getRealAddressByIP(IpUtils.getIpAddr())); + log.setStatus(status); + + fill(FillTypeEnum.INSERT.getCode(), log); + return securityLogsMapper.insertHotakeSecuritySecurityLogs(log); + } + + /** + * 更新会话为已登出状态 + * + * @param sessionToken 会话令牌 + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int updateSessionLogout(String sessionToken) + { + // 查询会话信息 + HotakeSecurityLoginSessions session = loginSessionsMapper.selectHotakeSecurityLoginSessionsByToken(sessionToken); + if (session != null && session.getIsActive() == 1) + { + // 更新会话状态 + session.setIsActive(0); + session.setLogoutTime(new Date()); + fill(FillTypeEnum.UPDATE.getCode(), session); + + // 记录安全日志 + recordSecurityLog(session.getUserId(), "LOGOUT", + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10011"), "SUCCESS"); + + return loginSessionsMapper.updateHotakeSecurityLoginSessions(session); + } + return 0; + } +} diff --git a/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityLoginSessionsMapper.xml b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityLoginSessionsMapper.xml new file mode 100644 index 0000000..1af0f1f --- /dev/null +++ b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityLoginSessionsMapper.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, user_id, session_id, session_token, device_type, device_name, + browser, browser_version, os, os_version, ip_address, + location_country, location_city, is_active, last_activity_time, + login_time, logout_time, expires_at, + del_flag, create_by, create_time, update_by, update_time, remark + from hotake_security_login_sessions + + + + + + + + + + + + insert into hotake_security_login_sessions + + user_id, + session_id, + session_token, + device_type, + device_name, + browser, + browser_version, + os, + os_version, + ip_address, + location_country, + location_city, + is_active, + last_activity_time, + login_time, + logout_time, + expires_at, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{userId}, + #{sessionId}, + #{sessionToken}, + #{deviceType}, + #{deviceName}, + #{browser}, + #{browserVersion}, + #{os}, + #{osVersion}, + #{ipAddress}, + #{locationCountry}, + #{locationCity}, + #{isActive}, + #{lastActivityTime}, + #{loginTime}, + #{logoutTime}, + #{expiresAt}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update hotake_security_login_sessions + + user_id = #{userId}, + session_id = #{sessionId}, + session_token = #{sessionToken}, + device_type = #{deviceType}, + device_name = #{deviceName}, + browser = #{browser}, + browser_version = #{browserVersion}, + os = #{os}, + os_version = #{osVersion}, + ip_address = #{ipAddress}, + location_country = #{locationCountry}, + location_city = #{locationCity}, + is_active = #{isActive}, + last_activity_time = #{lastActivityTime}, + login_time = #{loginTime}, + logout_time = #{logoutTime}, + expires_at = #{expiresAt}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + update hotake_security_login_sessions + set is_active = 0, + logout_time = now(), + update_time = now() + where user_id = #{userId} + and session_token != #{currentSessionToken} + and is_active = 1 + and del_flag = '0' + + + + update hotake_security_login_sessions + set is_active = 0, + logout_time = now(), + update_time = now() + where id = #{id} + and user_id = #{userId} + and del_flag = '0' + + + + update hotake_security_login_sessions set del_flag = '2' where id = #{id} + + + + update hotake_security_login_sessions set del_flag = '2' where id in + + #{id} + + + diff --git a/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityPasswordHistoryMapper.xml b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityPasswordHistoryMapper.xml new file mode 100644 index 0000000..786908d --- /dev/null +++ b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityPasswordHistoryMapper.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + select id, user_id, password_hash, changed_at, changed_by, ip_address, + del_flag, create_by, create_time, update_by, update_time, remark + from hotake_security_password_history + + + + + + + + + + insert into hotake_security_password_history + + user_id, + password_hash, + changed_at, + changed_by, + ip_address, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{userId}, + #{passwordHash}, + #{changedAt}, + #{changedBy}, + #{ipAddress}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update hotake_security_password_history + + user_id = #{userId}, + password_hash = #{passwordHash}, + changed_at = #{changedAt}, + changed_by = #{changedBy}, + ip_address = #{ipAddress}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + update hotake_security_password_history set del_flag = '2' where id = #{id} + + + + update hotake_security_password_history set del_flag = '2' where id in + + #{id} + + + diff --git a/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecuritySecurityLogsMapper.xml b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecuritySecurityLogsMapper.xml new file mode 100644 index 0000000..6369620 --- /dev/null +++ b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecuritySecurityLogsMapper.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + select id, user_id, event_type, event_description, ip_address, user_agent, + location, status, metadata, + del_flag, create_by, create_time, update_by, update_time, remark + from hotake_security_security_logs + + + + + + + + insert into hotake_security_security_logs + + user_id, + event_type, + event_description, + ip_address, + user_agent, + location, + status, + metadata, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{userId}, + #{eventType}, + #{eventDescription}, + #{ipAddress}, + #{userAgent}, + #{location}, + #{status}, + #{metadata}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update hotake_security_security_logs + + user_id = #{userId}, + event_type = #{eventType}, + event_description = #{eventDescription}, + ip_address = #{ipAddress}, + user_agent = #{userAgent}, + location = #{location}, + status = #{status}, + metadata = #{metadata}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + update hotake_security_security_logs set del_flag = '2' where id = #{id} + + + + update hotake_security_security_logs set del_flag = '2' where id in + + #{id} + + + diff --git a/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecuritySettingsMapper.xml b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecuritySettingsMapper.xml new file mode 100644 index 0000000..98571a5 --- /dev/null +++ b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecuritySettingsMapper.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select id, user_id, two_factor_enabled, two_factor_secret, two_factor_type, backup_codes, + password_changed_at, password_reset_token, password_reset_expires, + failed_login_attempts, account_locked_until, + del_flag, create_by, create_time, update_by, update_time, remark + from hotake_security_settings + + + + + + + + + + insert into hotake_security_settings + + user_id, + two_factor_enabled, + two_factor_secret, + two_factor_type, + backup_codes, + password_changed_at, + password_reset_token, + password_reset_expires, + failed_login_attempts, + account_locked_until, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{userId}, + #{twoFactorEnabled}, + #{twoFactorSecret}, + #{twoFactorType}, + #{backupCodes}, + #{passwordChangedAt}, + #{passwordResetToken}, + #{passwordResetExpires}, + #{failedLoginAttempts}, + #{accountLockedUntil}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update hotake_security_settings + + user_id = #{userId}, + two_factor_enabled = #{twoFactorEnabled}, + two_factor_secret = #{twoFactorSecret}, + two_factor_type = #{twoFactorType}, + backup_codes = #{backupCodes}, + password_changed_at = #{passwordChangedAt}, + password_reset_token = #{passwordResetToken}, + password_reset_expires = #{passwordResetExpires}, + failed_login_attempts = #{failedLoginAttempts}, + account_locked_until = #{accountLockedUntil}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + update hotake_security_settings set del_flag = '2' where id = #{id} + + + + update hotake_security_settings set del_flag = '2' where id in + + #{id} + + +