From 94bee0be900c79617419641328a36c5179062ae3 Mon Sep 17 00:00:00 2001 From: ID <304930518@qq.com> Date: Tue, 24 Feb 2026 21:01:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-druid.yml | 12 +- .../resources/i18n/messages_en_US.properties | 9 +- .../resources/i18n/messages_zh_CN.properties | 9 +- .../HotakeSecurityTrustedDevicesMapper.java | 96 +++++++++ .../service/IHotakeSecurityService.java | 34 +++ .../impl/HotakeSecurityServiceImpl.java | 204 ++++++++++++++++++ .../HotakeSecurityTrustedDevicesMapper.xml | 136 ++++++++++++ 7 files changed, 492 insertions(+), 8 deletions(-) create mode 100644 vetti-hotakes/src/main/java/com/vetti/hotake/mapper/HotakeSecurityTrustedDevicesMapper.java create mode 100644 vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityTrustedDevicesMapper.xml diff --git a/vetti-admin/src/main/resources/application-druid.yml b/vetti-admin/src/main/resources/application-druid.yml index 3cca37a..1c57de4 100644 --- a/vetti-admin/src/main/resources/application-druid.yml +++ b/vetti-admin/src/main/resources/application-druid.yml @@ -40,9 +40,9 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://13.211.168.80:3306/vetti_service?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: vetti_service - password: Hotake@2025 + url: jdbc:mysql://ddns.hotake.cn:13306/vetti_service?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: Hamkke@2021 # 从库数据源 slave: # 从数据源开关/默认关闭 @@ -207,7 +207,7 @@ oauth2: google: client-id: 398978985110-ve0usu381mmdio12ff01iqvv1g087qvi.apps.googleusercontent.com # Google Cloud Console获取 client-secret: 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 # Google Cloud Console获取 - redirect-uri: https://vetti.hotake.cn/oauth2/callback/google + redirect-uri: https://vetti.com.au/oauth2/callback/google scope: openid email profile # Google标准scope(空格分隔) auth-uri: https://accounts.google.com/o/oauth2/v2/auth # Google授权地址 token-uri: https://oauth2.googleapis.com/token # Google令牌地址 @@ -220,7 +220,7 @@ oauth2: microsoft: client-id: 608cbc4f-3ee2-4f51-a72c-b2e3133fc5b2 # Azure Portal获取 client-secret: gvw8Q~dwG8Sv7HN3R3W3R7TQtcZyvrh88ZiJubPa # Azure Portal获取 - redirect-uri: https://vetti.hotake.cn/oauth2/callback/microsoft + redirect-uri: https://vetti.com.au/oauth2/callback/microsoft scope: openid,email,profile,User.Read # 需要额外的User.Read权限才能读取用户信息 auth-uri: https://login.microsoftonline.com/common/oauth2/v2.0/authorize # 微软授权地址 token-uri: https://login.microsoftonline.com/common/oauth2/v2.0/token # 微软令牌地址 @@ -233,7 +233,7 @@ oauth2: linkedin: client-id: 86uq3opzshd3bq # LinkedIn Developer Portal获取 client-secret: WPL_AP1.mipgyxfgfBoN12Th.1TXeFg== # LinkedIn Developer Portal获取 - redirect-uri: https://vetti.hotake.cn/oauth2/callback/linkedin + redirect-uri: https://vetti.com.au/oauth2/callback/linkedin scope: openid profile email # LinkedIn使用OpenID Connect,scope顺序和命名略有不同 auth-uri: https://www.linkedin.com/oauth/v2/authorization # LinkedIn授权地址 token-uri: https://www.linkedin.com/oauth/v2/accessToken # LinkedIn令牌地址 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 7818bc2..307b9dd 100644 --- a/vetti-admin/src/main/resources/i18n/messages_en_US.properties +++ b/vetti-admin/src/main/resources/i18n/messages_en_US.properties @@ -85,4 +85,11 @@ HotakeSecurityController10003 = Session terminated HotakeSecurityController10004 = All other sessions terminated # Logout related -HotakeSecurityServiceImpl10011 = User logged out \ No newline at end of file +HotakeSecurityServiceImpl10011 = User logged out + + +# Trusted device related messages +HotakeSecurityServiceImpl10012 = Updated trusted device +HotakeSecurityServiceImpl10013 = Added trusted device +HotakeSecurityServiceImpl10014 = Device does not exist or no permission to operate +HotakeSecurityServiceImpl10015 = Removed trusted device 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 c24a4f3..c499081 100644 --- a/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties +++ b/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties @@ -82,4 +82,11 @@ HotakeSecurityController10003 = 会话已终止 HotakeSecurityController10004 = 所有其他会话已终止 # 退出登录相关 -HotakeSecurityServiceImpl10011 = 用户退出登录 \ No newline at end of file +HotakeSecurityServiceImpl10011 = 用户退出登录 + + +# 可信设备相关消息 +HotakeSecurityServiceImpl10012 = 更新可信设备 +HotakeSecurityServiceImpl10013 = 添加可信设备 +HotakeSecurityServiceImpl10014 = 设备不存在或无权操作 +HotakeSecurityServiceImpl10015 = 移除可信设备 diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/mapper/HotakeSecurityTrustedDevicesMapper.java b/vetti-hotakes/src/main/java/com/vetti/hotake/mapper/HotakeSecurityTrustedDevicesMapper.java new file mode 100644 index 0000000..ed2da63 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/mapper/HotakeSecurityTrustedDevicesMapper.java @@ -0,0 +1,96 @@ +package com.vetti.hotake.mapper; + +import com.vetti.hotake.domain.HotakeSecurityTrustedDevices; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 可信设备Mapper接口 + * + * @author vetti + * @date 2026-02-02 + */ +public interface HotakeSecurityTrustedDevicesMapper +{ + /** + * 查询可信设备 + * + * @param id 可信设备主键 + * @return 可信设备 + */ + public HotakeSecurityTrustedDevices selectHotakeSecurityTrustedDevicesById(Long id); + + /** + * 根据用户ID和设备ID查询可信设备 + * + * @param userId 用户ID + * @param deviceId 设备ID + * @return 可信设备 + */ + public HotakeSecurityTrustedDevices selectByUserIdAndDeviceId(@Param("userId") Long userId, @Param("deviceId") String deviceId); + + /** + * 查询可信设备列表 + * + * @param hotakeSecurityTrustedDevices 可信设备 + * @return 可信设备集合 + */ + public List selectHotakeSecurityTrustedDevicesList(HotakeSecurityTrustedDevices hotakeSecurityTrustedDevices); + + /** + * 查询用户的可信设备列表 + * + * @param userId 用户ID + * @return 可信设备集合 + */ + public List selectTrustedDevicesByUserId(Long userId); + + /** + * 新增可信设备 + * + * @param hotakeSecurityTrustedDevices 可信设备 + * @return 结果 + */ + public int insertHotakeSecurityTrustedDevices(HotakeSecurityTrustedDevices hotakeSecurityTrustedDevices); + + /** + * 修改可信设备 + * + * @param hotakeSecurityTrustedDevices 可信设备 + * @return 结果 + */ + public int updateHotakeSecurityTrustedDevices(HotakeSecurityTrustedDevices hotakeSecurityTrustedDevices); + + /** + * 删除可信设备 + * + * @param id 可信设备主键 + * @return 结果 + */ + public int deleteHotakeSecurityTrustedDevicesById(Long id); + + /** + * 批量删除可信设备 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteHotakeSecurityTrustedDevicesByIds(Long[] ids); + + /** + * 移除用户的可信设备 + * + * @param id 设备记录ID + * @param userId 用户ID + * @return 结果 + */ + public int removeTrustedDevice(@Param("id") Long id, @Param("userId") Long userId); + + /** + * 清理过期的可信设备 + * + * @return 结果 + */ + public int cleanExpiredTrustedDevices(); +} 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 index fa5ce6e..8d51adf 100644 --- a/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeSecurityService.java +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeSecurityService.java @@ -3,6 +3,7 @@ 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 com.vetti.hotake.domain.vo.SecurityTrustedDeviceVo; import java.util.List; @@ -97,4 +98,37 @@ public interface IHotakeSecurityService * @return 结果 */ int updateSessionLogout(String sessionToken); + + /** + * 获取当前用户的可信设备列表 + * + * @return 可信设备列表 + */ + List getTrustedDevices(); + + /** + * 添加可信设备 + * + * @param deviceId 设备唯一标识 + * @param deviceName 设备名称 + * @return 结果 + */ + int addTrustedDevice(String deviceId, String deviceName); + + /** + * 移除可信设备 + * + * @param id 设备记录ID + * @return 结果 + */ + int removeTrustedDevice(Long id); + + /** + * 检查设备是否为可信设备 + * + * @param userId 用户ID + * @param deviceId 设备唯一标识 + * @return 是否可信 + */ + boolean isTrustedDevice(Long userId, String deviceId); } 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 index 0683b09..99b9b8b 100644 --- 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 @@ -16,12 +16,15 @@ 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.HotakeSecurityTrustedDevices; import com.vetti.hotake.domain.dto.SecurityChangePasswordDto; import com.vetti.hotake.domain.vo.SecuritySessionVo; +import com.vetti.hotake.domain.vo.SecurityTrustedDeviceVo; 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.mapper.HotakeSecurityTrustedDevicesMapper; import com.vetti.hotake.service.IHotakeSecurityService; import com.vetti.system.service.ISysUserService; import eu.bitwalker.useragentutils.Browser; @@ -60,6 +63,9 @@ public class HotakeSecurityServiceImpl extends BaseServiceImpl implements IHotak @Autowired private HotakeSecuritySecurityLogsMapper securityLogsMapper; + @Autowired + private HotakeSecurityTrustedDevicesMapper trustedDevicesMapper; + @Autowired private ISysUserService userService; @@ -451,4 +457,202 @@ public class HotakeSecurityServiceImpl extends BaseServiceImpl implements IHotak } return 0; } + + /** + * 获取当前用户的可信设备列表 + * + * @return 可信设备列表 + */ + @Transactional(readOnly = true) + @Override + public List getTrustedDevices() + { + Long userId = SecurityUtils.getUserId(); + + // 查询用户的可信设备 + List devicesList = trustedDevicesMapper.selectTrustedDevicesByUserId(userId); + + // 获取当前设备标识(可以从请求头或Cookie中获取) + String currentDeviceId = getCurrentDeviceId(); + + // 转换为VO + List voList = new ArrayList<>(); + for (HotakeSecurityTrustedDevices device : devicesList) + { + SecurityTrustedDeviceVo vo = new SecurityTrustedDeviceVo(); + vo.setId(device.getId()); + vo.setDeviceId(device.getDeviceId()); + vo.setDeviceName(device.getDeviceName()); + vo.setDeviceType(device.getDeviceType()); + vo.setBrowser(device.getBrowser()); + vo.setOs(device.getOs()); + vo.setIpAddress(device.getIpAddress()); + vo.setLocation(device.getLocation()); + vo.setTrustExpiresAt(device.getTrustExpiresAt()); + vo.setCreateTime(device.getCreateTime()); + vo.setIsCurrent(device.getDeviceId().equals(currentDeviceId)); + + voList.add(vo); + } + + return voList; + } + + /** + * 添加可信设备 + * + * @param deviceId 设备唯一标识 + * @param deviceName 设备名称 + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int addTrustedDevice(String deviceId, String deviceName) + { + Long userId = SecurityUtils.getUserId(); + + // 检查是否已存在 + HotakeSecurityTrustedDevices existDevice = trustedDevicesMapper.selectByUserIdAndDeviceId(userId, deviceId); + if (existDevice != null) + { + // 已存在,更新信任状态 + existDevice.setIsTrusted(1); + existDevice.setDeviceName(deviceName); + // 设置信任过期时间(30天) + existDevice.setTrustExpiresAt(new Date(System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000)); + fill(FillTypeEnum.UPDATE.getCode(), existDevice); + + recordSecurityLog(userId, "TRUSTED_DEVICE_UPDATE", + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10012") + ":" + deviceName, "SUCCESS"); + + return trustedDevicesMapper.updateHotakeSecurityTrustedDevices(existDevice); + } + + // 解析User-Agent + String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); + UserAgent ua = UserAgent.parseUserAgentString(userAgent); + Browser browser = ua.getBrowser(); + OperatingSystem os = ua.getOperatingSystem(); + + // 获取IP和位置 + String ipAddress = IpUtils.getIpAddr(); + String location = AddressUtils.getRealAddressByIP(ipAddress); + + // 判断设备类型 + String deviceType = "Desktop"; + if (os.getDeviceType() != null) + { + deviceType = os.getDeviceType().getName(); + } + + // 创建新的可信设备记录 + HotakeSecurityTrustedDevices device = new HotakeSecurityTrustedDevices(); + device.setUserId(userId); + device.setDeviceId(deviceId); + device.setDeviceName(StringUtils.isNotEmpty(deviceName) ? deviceName : browser.getName() + " / " + os.getName()); + device.setDeviceType(deviceType); + device.setBrowser(browser.getName()); + device.setOs(os.getName()); + device.setIpAddress(ipAddress); + device.setLocation(location); + device.setIsTrusted(1); + // 设置信任过期时间(30天) + device.setTrustExpiresAt(new Date(System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000)); + + fill(FillTypeEnum.INSERT.getCode(), device); + + // 记录安全日志 + recordSecurityLog(userId, "TRUSTED_DEVICE_ADD", + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10013") + ":" + device.getDeviceName(), "SUCCESS"); + + return trustedDevicesMapper.insertHotakeSecurityTrustedDevices(device); + } + + /** + * 移除可信设备 + * + * @param id 设备记录ID + * @return 结果 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int removeTrustedDevice(Long id) + { + Long userId = SecurityUtils.getUserId(); + + // 查询设备信息 + HotakeSecurityTrustedDevices device = trustedDevicesMapper.selectHotakeSecurityTrustedDevicesById(id); + if (device == null || !device.getUserId().equals(userId)) + { + throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10014")); + } + + // 删除设备 + int result = trustedDevicesMapper.removeTrustedDevice(id, userId); + + // 记录安全日志 + recordSecurityLog(userId, "TRUSTED_DEVICE_REMOVE", + MessageUtils.messageCustomize("HotakeSecurityServiceImpl10015") + ":" + device.getDeviceName(), "SUCCESS"); + + return result; + } + + /** + * 检查设备是否为可信设备 + * + * @param userId 用户ID + * @param deviceId 设备唯一标识 + * @return 是否可信 + */ + @Transactional(readOnly = true) + @Override + public boolean isTrustedDevice(Long userId, String deviceId) + { + if (StringUtils.isEmpty(deviceId)) + { + return false; + } + + HotakeSecurityTrustedDevices device = trustedDevicesMapper.selectByUserIdAndDeviceId(userId, deviceId); + if (device == null || device.getIsTrusted() != 1) + { + return false; + } + + // 检查是否过期 + if (device.getTrustExpiresAt() != null && device.getTrustExpiresAt().before(new Date())) + { + return false; + } + + return true; + } + + /** + * 获取当前设备标识 + * 可以从请求头或Cookie中获取,这里简单实现 + */ + private String getCurrentDeviceId() + { + HttpServletRequest request = ServletUtils.getRequest(); + // 优先从请求头获取 + String deviceId = request.getHeader("X-Device-Id"); + if (StringUtils.isEmpty(deviceId)) + { + // 从Cookie获取 + javax.servlet.http.Cookie[] cookies = request.getCookies(); + if (cookies != null) + { + for (javax.servlet.http.Cookie cookie : cookies) + { + if ("device_id".equals(cookie.getName())) + { + deviceId = cookie.getValue(); + break; + } + } + } + } + return deviceId; + } } diff --git a/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityTrustedDevicesMapper.xml b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityTrustedDevicesMapper.xml new file mode 100644 index 0000000..a4f8c05 --- /dev/null +++ b/vetti-hotakes/src/main/resources/mapper/hotake/HotakeSecurityTrustedDevicesMapper.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + select id, user_id, device_id, device_name, device_type, browser, os, ip_address, location, is_trusted, trust_expires_at, create_by, create_time, update_by, update_time, remark from hotake_security_trusted_devices + + + + + + + + + + + + insert into hotake_security_trusted_devices + + user_id, + device_id, + device_name, + device_type, + browser, + os, + ip_address, + location, + is_trusted, + trust_expires_at, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{userId}, + #{deviceId}, + #{deviceName}, + #{deviceType}, + #{browser}, + #{os}, + #{ipAddress}, + #{location}, + #{isTrusted}, + #{trustExpiresAt}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update hotake_security_trusted_devices + + user_id = #{userId}, + device_id = #{deviceId}, + device_name = #{deviceName}, + device_type = #{deviceType}, + browser = #{browser}, + os = #{os}, + ip_address = #{ipAddress}, + location = #{location}, + is_trusted = #{isTrusted}, + trust_expires_at = #{trustExpiresAt}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from hotake_security_trusted_devices where id = #{id} + + + + delete from hotake_security_trusted_devices where id in + + #{id} + + + + + delete from hotake_security_trusted_devices where id = #{id} and user_id = #{userId} + + + + delete from hotake_security_trusted_devices where trust_expires_at is not null and trust_expires_at < now() + +