基于Java WebSocket实现用户密码过期提醒的技术求助
问题与解决方案
核心问题
当前实现存在两个关键问题:
- WebSocket握手时使用随机UUID作为用户标识,与系统真实用户ID不匹配,无法精准推送消息给指定用户。
- 定时任务全量遍历用户,未筛选出密码即将过期的目标用户,效率低下且无意义。
分步解决方案
1. 修改握手处理器,绑定真实用户ID
WebSocket连接时必须关联系统真实用户ID,才能实现精准推送。根据系统认证方式选择以下方案:
方案A:基于Spring Security认证用户
@Component public class UserHandshakeHandler extends DefaultHandshakeHandler { private final Logger LOG = LoggerFactory.getLogger(UserHandshakeHandler.class); @Override protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) { // 从Spring Security上下文获取已认证用户ID Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { // 假设用户名存储的是用户ID,若不是可从UserDetails中获取真实ID String userId = authentication.getName(); LOG.info("User {} connected via WebSocket", userId); return new UserPrincipal(userId); } throw new AccessDeniedException("未认证用户无法连接WebSocket"); } }
方案B:基于请求头Token解析用户ID
@Component public class UserHandshakeHandler extends DefaultHandshakeHandler { private final Logger LOG = LoggerFactory.getLogger(UserHandshakeHandler.class); private final JwtUtil jwtUtil; // 自定义JWT解析工具类 @Autowired public UserHandshakeHandler(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; } @Override protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) { HttpHeaders headers = request.getHeaders(); String token = headers.getFirst("Authorization"); if (token != null && token.startsWith("Bearer ")) { String userId = jwtUtil.getUserIdFromToken(token.substring(7)); LOG.info("User {} connected via WebSocket", userId); return new UserPrincipal(userId); } throw new AccessDeniedException("缺少有效认证Token"); } }
2. 更新WebSocket配置,注入自定义握手处理器
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { private final UserHandshakeHandler userHandshakeHandler; @Autowired public WebSocketConfig(UserHandshakeHandler userHandshakeHandler) { this.userHandshakeHandler = userHandshakeHandler; } @Override public void configureMessageBroker(final MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/ws"); } @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { registry.addEndpoint("/our-websocket") .setHandshakeHandler(userHandshakeHandler) .withSockJS(); } }
3. 优化定时任务,精准推送密码过期通知
@RestController public class WSController { @Autowired private WSService service; @Autowired private UsersRepository usersRepository; // 每日执行一次(86400000毫秒),可根据需求调整频率 @Scheduled(fixedDelay = 86400000) public void sendPasswordExpiryNotifications() { int warningThreshold = 7; // 提前7天通知 LocalDate now = LocalDate.now(); LocalDate warningDeadline = now.plusDays(warningThreshold); // 只查询密码即将过期的用户 List<Users> targetUsers = usersRepository.findByPasswordExpireTimeBetween(now, warningDeadline); for (Users user : targetUsers) { long remainingDays = ChronoUnit.DAYS.between(now, user.getPasswordExpireTime()); String notification = String.format("您的密码将在%d天后过期,请及时修改!", remainingDays); service.notifyUser(user.getId().toString(), notification); } } // 其他原有方法保留... }
4. 补充Repository查询方法
在UsersRepository中添加筛选条件:
@Repository public interface UsersRepository extends JpaRepository<Users, Long> { List<Users> findByPasswordExpireTimeBetween(LocalDate startDate, LocalDate endDate); }
5. 前端连接时携带认证信息(可选)
若采用Token认证,前端连接WebSocket时需携带Token:
function connect() { var socket = new SockJS('/our-websocket'); stompClient = Stomp.over(socket); var authToken = localStorage.getItem("authToken"); var connectHeaders = authToken ? { "Authorization": "Bearer " + authToken } : {}; stompClient.connect(connectHeaders, function (frame) { console.log('Connected: ' + frame); // 原有订阅逻辑保留... }, function(error) { console.error("WebSocket连接失败:", error); alert("请先登录再使用系统功能"); }); }
关键注意事项
- 必须确保WebSocket连接时的用户ID与系统用户ID完全一致,这是精准推送的核心前提。
- 定时任务频率不宜过高,密码过期通知每日执行一次即可,避免不必要的数据库查询和推送。
- 未认证用户应拒绝WebSocket连接,防止非法推送和资源浪费。
内容的提问来源于stack exchange,提问作者Yevgeniy
相关产品推荐
相关产品推荐

