You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Java WebSocket实现用户密码过期提醒的技术求助

问题与解决方案

核心问题

当前实现存在两个关键问题:

  1. WebSocket握手时使用随机UUID作为用户标识,与系统真实用户ID不匹配,无法精准推送消息给指定用户。
  2. 定时任务全量遍历用户,未筛选出密码即将过期的目标用户,效率低下且无意义。

分步解决方案

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 18:44:57