Skip to content

fix(websocket): add heartbeat scheduling and transport limits#313

Merged
balazs-szucs merged 2 commits intogrimmory-tools:developfrom
balazs-szucs:websocket-springs
Mar 31, 2026
Merged

fix(websocket): add heartbeat scheduling and transport limits#313
balazs-szucs merged 2 commits intogrimmory-tools:developfrom
balazs-szucs:websocket-springs

Conversation

@balazs-szucs
Copy link
Copy Markdown
Member

@balazs-szucs balazs-szucs commented Mar 31, 2026

Description

Linked Issue: Fixes #

Changes

  • SUBSCRIBE whitelisting: only /topic/** (broadcast) and /user/queue/** (user-scoped) destinations are permitted. All other subscription attempts are rejected.
  • SEND blocking: this application is server-push only. Clients cannot send STOMP SEND frames.
  • Heartbeats: 10s server/client heartbeat with a dedicated scheduler to detect and drop dead connections.
  • Transport limits: caps on message size (128KB), send buffer (512KB), send time (15s), and time-to-first-message (30s) to prevent resource exhaustion.
  • Auth cleanup: MessagingException instead of IllegalArgumentException for proper STOMP ERROR frame propagation. Empty List.of() instead of null for authorities.

Summary by CodeRabbit

  • Improvements
    • Enhanced WebSocket connection stability with periodic heartbeat monitoring to maintain persistent real-time connections
    • Strengthened real-time communication security with improved authentication and authorization validation for all message operations
    • Implemented enhanced destination validation with protection against unauthorized message routing
    • Added safeguards to prevent unauthorized messaging operations

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

WebSocket configuration now includes heartbeat messaging and transport size/time constraints via a dedicated task scheduler bean. The authentication interceptor adds multi-command STOMP handling with specific logic for CONNECT authentication, SUBSCRIBE destination validation, and unconditional SEND rejection.

Changes

Cohort / File(s) Summary
WebSocket Broker Configuration
booklore-api/src/main/java/org/booklore/config/WebSocketConfig.java
Added wsHeartbeatScheduler() bean providing a single-threaded TaskScheduler, enabled heartbeat messaging on simple broker, and configured WebSocket transport constraints for send time, buffer size, and message size limits.
WebSocket Authentication & Command Handling
booklore-api/src/main/java/org/booklore/config/security/interceptor/WebSocketAuthInterceptor.java
Refactored preSend() to handle multiple STOMP commands via switch statement: CONNECT enforces Bearer token authentication, SUBSCRIBE validates destinations against path traversal and allowed prefixes (/topic/, /user/queue/), SEND is unconditionally rejected, and DISCONNECT is logged. Changed exceptions from IllegalArgumentException to MessagingException and updated token validation to return UsernamePasswordAuthenticationToken with empty authorities.

Sequence Diagram

sequenceDiagram
    participant Client as WebSocket Client
    participant Interceptor as WebSocketAuthInterceptor
    participant AuthService as Authentication Service
    participant Broker as Message Broker

    Client->>Interceptor: CONNECT (Bearer token)
    activate Interceptor
    Interceptor->>AuthService: Validate token & extract claims
    AuthService-->>Interceptor: UsernamePasswordAuthenticationToken
    Interceptor->>Broker: Allow connection
    deactivate Interceptor

    Client->>Interceptor: SUBSCRIBE /topic/books
    activate Interceptor
    Interceptor->>Interceptor: Check destination path<br/>(no .., allowed prefix?)
    Interceptor->>Broker: Route subscription
    deactivate Interceptor

    Client->>Interceptor: SEND /app/message
    activate Interceptor
    Interceptor->>Interceptor: Reject SEND operation
    Interceptor-->>Client: MessagingException
    deactivate Interceptor

    Client->>Interceptor: DISCONNECT
    activate Interceptor
    Interceptor->>Interceptor: Log disconnection
    deactivate Interceptor
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

backend, enhancement

Poem

🐰 Heartbeats now flutter through broker's domain,
STOMP commands sorted—each plays its own game,
Tokens are checked with a Bearer's fine care,
Paths defended from traversal snares,
Security whispers through WebSocket air!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commit format with 'fix' prefix and clearly describes the main changes: heartbeat scheduling and transport limits.
Description check ✅ Passed The description includes the required template structure with a description section and a comprehensive list of changes detailing all modifications made.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
booklore-api/src/main/java/org/booklore/config/security/interceptor/WebSocketAuthInterceptor.java (1)

87-90: Confirm /app destination prefix is intentionally unused.

With all SEND frames rejected, the /app application destination prefix configured in WebSocketConfig.java (line 37) becomes unreachable—clients cannot invoke @MessageMapping handlers. Per the PR description this is intentional ("server-push only"), but consider removing the /app prefix configuration to avoid confusion.

If /app is truly unused, clean up the broker config:

// In WebSocketConfig.configureMessageBroker()
// registry.setApplicationDestinationPrefixes("/app"); // Remove if not needed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@booklore-api/src/main/java/org/booklore/config/security/interceptor/WebSocketAuthInterceptor.java`
around lines 87 - 90, The interceptor method rejectSend currently rejects all
SEND frames, making the configured application destination prefix
(setApplicationDestinationPrefixes("/app") in
WebSocketConfig.configureMessageBroker) unreachable; if server-push only
behavior is intended, remove the unused application destination prefix from
WebSocketConfig.configureMessageBroker (i.e. delete or comment out the
setApplicationDestinationPrefixes call) and add a short comment in
WebSocketConfig noting that SEND frames are blocked by
WebSocketAuthInterceptor.rejectSend and that /app is intentionally disabled to
avoid confusion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@booklore-api/src/main/java/org/booklore/config/WebSocketConfig.java`:
- Around line 41-48: There are two unqualified TaskScheduler beans
(taskScheduler() in TaskExecutorConfig and wsHeartbeatScheduler() in
WebSocketConfig) causing NoUniqueBeanDefinitionException when TaskService
autowires TaskScheduler; fix by either removing the `@Bean` from
wsHeartbeatScheduler() so it becomes a private/internal scheduler used only
inside WebSocketConfig, or explicitly qualify one of them—e.g., add
`@Qualifier`("wsHeartbeatScheduler") to the wsHeartbeatScheduler() bean and update
any injection points that should use it, or mark taskScheduler() as `@Primary`;
alternatively inject the scheduler into WebSocketConfig via constructor and stop
exposing wsHeartbeatScheduler() as a bean.

---

Nitpick comments:
In
`@booklore-api/src/main/java/org/booklore/config/security/interceptor/WebSocketAuthInterceptor.java`:
- Around line 87-90: The interceptor method rejectSend currently rejects all
SEND frames, making the configured application destination prefix
(setApplicationDestinationPrefixes("/app") in
WebSocketConfig.configureMessageBroker) unreachable; if server-push only
behavior is intended, remove the unused application destination prefix from
WebSocketConfig.configureMessageBroker (i.e. delete or comment out the
setApplicationDestinationPrefixes call) and add a short comment in
WebSocketConfig noting that SEND frames are blocked by
WebSocketAuthInterceptor.rejectSend and that /app is intentionally disabled to
avoid confusion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c0e3c58-0176-4365-b975-91e614879490

📥 Commits

Reviewing files that changed from the base of the PR and between fb2bde3 and d9b7667.

📒 Files selected for processing (2)
  • booklore-api/src/main/java/org/booklore/config/WebSocketConfig.java
  • booklore-api/src/main/java/org/booklore/config/security/interceptor/WebSocketAuthInterceptor.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Packaging Smoke Test
🧰 Additional context used
📓 Path-based instructions (1)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/main/java/org/booklore/config/WebSocketConfig.java
  • booklore-api/src/main/java/org/booklore/config/security/interceptor/WebSocketAuthInterceptor.java
🔇 Additional comments (5)
booklore-api/src/main/java/org/booklore/config/WebSocketConfig.java (1)

69-75: Transport limits configuration looks good.

The limits are well-chosen for mitigating resource exhaustion:

  • 15s send time limit prevents slow clients from holding resources
  • 512KB send buffer and 128KB message size are reasonable caps
  • 30s time-to-first-message helps detect abandoned connections
booklore-api/src/main/java/org/booklore/config/security/interceptor/WebSocketAuthInterceptor.java (4)

36-48: Switch-based command handling is clean and readable.

Good use of the modern switch expression syntax for routing STOMP commands. The explicit handling of CONNECT, SUBSCRIBE, SEND, and DISCONNECT with a default pass-through is well-structured.


51-70: CONNECT authentication looks solid.

The method correctly validates the Authorization header format, extracts the Bearer token, and delegates to authenticateToken. Using MessagingException ensures proper STOMP ERROR frame propagation to the client.


72-85: Subscription validation is secure.

The whitelist approach with ALLOWED_DEST_PREFIXES is a good security practice. The .. check provides basic path traversal protection. The combination of requireAuthenticated and destination validation ensures only authenticated users can subscribe to permitted destinations.


98-113: Token authentication improvements are good.

Using isBlank() for validation, List.of() for empty authorities (instead of null), and debug-level logging for failures are all solid improvements.

@balazs-szucs balazs-szucs merged commit 12193b2 into grimmory-tools:develop Mar 31, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant