Skip to content

feat: [channel auth]#189

Open
chenqwwq wants to merge 5 commits into
crossoverJie:masterfrom
chenqwwq:feature/client_channel_auth
Open

feat: [channel auth]#189
chenqwwq wants to merge 5 commits into
crossoverJie:masterfrom
chenqwwq:feature/client_channel_auth

Conversation

@chenqwwq
Copy link
Copy Markdown
Contributor

@chenqwwq chenqwwq commented Apr 9, 2026

send authentication token as soon as the channel is available

Summary by CodeRabbit

  • New Features
    • JWT token-based authentication for client-server connections; clients now include an auth token during login and servers validate and bind session identity before processing messages.
    • Route responses now include an auth token for clients to use when connecting.
    • One-time authentication enforced on initial connection for improved session handling and reliability.
  • Bug Fixes
    • Improved connection/login error handling and failure reporting during initial handshake.
  • Tests
    • Added unit test coverage for token generation and verification.

send authentication token as soon as the channel is available
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds JWT-based client authentication: route issues authToken, client includes authToken in login, server verifies the token via a new ClientAuthHandler, binds user info to channel attributes, and removes the handler on success; introduces JwtUtils, PayloadVO, ChannelAuthReq, channel attribute keys, and ServerConfig.

Changes

Cohort / File(s) Summary
Client connect/login
cim-client-sdk/src/main/java/com/crossoverjie/cim/client/sdk/impl/ClientImpl.java
Connect flow now calls loginServer(authToken) after connect inside try/catch that invokes conf.getEvent().fatal(this) on error; loginServer builds JSON ChannelAuthReq, writes with writeAndFlush(...).sync() and throws CIMException on write failure.
JWT utilities & DTOs
cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java, cim-common/src/main/java/com/crossoverjie/cim/common/auth/jwt/dto/PayloadVO.java, cim-common/src/test/java/com/crossoverjie/cim/common/auth/JwtAuthTest.java
Added JwtUtils for token generation/verification, PayloadVO DTO for token payload, and unit test validating token generation and verification.
Channel attributes & auth request DTO
cim-common/src/main/java/com/crossoverjie/cim/common/enums/ChannelAttributeKeys.java, cim-common/src/main/java/com/crossoverjie/cim/common/msg/ChannelAuthReq.java
Added Netty AttributeKey constants for auth and user info, and ChannelAuthReq DTO containing userId, userName, and authToken.
Route API: issue token
cim-forward-route/src/main/java/com/crossoverjie/cim/route/controller/RouteController.java, cim-rout-api/src/main/java/com/crossoverjie/cim/route/api/vo/res/CIMServerResVO.java
RouteController now builds a JWT authToken (using PayloadVO) including target host/port and returns it in CIMServerResVO; CIMServerResVO gained authToken field and a 3-arg constructor remains.
Server pipeline & auth handler
cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java, cim-server/src/main/java/com/crossoverjie/cim/server/init/CIMServerInitializer.java
Added ClientAuthHandler to verify JWT, check host/port, set channel attributes (USER_ID, USER_NAME, AUTH_RES), remove itself on success and send a welcome response; initializer now accepts ServerConfig and installs the handler.
Server config & startup refactor
cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java, cim-server/src/main/java/com/crossoverjie/cim/server/config/AppConfiguration.java, cim-server/src/main/java/com/crossoverjie/cim/server/CIMServerApplication.java, cim-server/src/main/java/com/crossoverjie/cim/server/server/CIMServer.java
Added ServerConfig (discovers LAN IPv4 and exposes nettyPort/host), removed cimServerPort from AppConfiguration, and updated application/server to use ServerConfig.
Server login handling
cim-server/src/main/java/com/crossoverjie/cim/server/handle/CIMServerHandle.java
Login now deserializes ChannelAuthReq from request JSON, validates payload, keys sessions by userId, and stores userName in session attributes.
Dependency / build
pom.xml, cim-common/pom.xml
Added com.auth0:java-jwt to parent dependencyManagement and as a dependency in cim-common.
Minor
cim-client/src/main/java/com/crossoverjie/cim/client/service/MsgHandle.java
Whitespace cleanup (removed extra blank line).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RouteServer as Route Server
    participant JwtUtils as JWT Utils
    participant CIMServer as CIM Server

    Client->>RouteServer: request route/login (userId,userName)
    RouteServer->>JwtUtils: generateToken(userId, payload{host,port})
    JwtUtils-->>RouteServer: authToken
    RouteServer-->>Client: CIMServerResVO(authToken,...)

    Client->>CIMServer: connect + send ChannelAuthReq(userId,userName,authToken)
    CIMServer->>CIMServer: ClientAuthHandler.channelRead(msg)
    CIMServer->>JwtUtils: verifyToken(authToken)
    JwtUtils-->>CIMServer: PayloadVO (userId,userName,host,port)
    CIMServer-->>Client: success Response, set channel attrs, remove auth handler
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

new features

Suggested reviewers

  • crossoverJie

Poem

🐰 I stitched a token, neat and bright,
Hopped through tunnels to guard the night,
Claims and ports all checked in line,
Channels bound — the handshake fine,
Hop secure: auth done just right.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'feat: [channel auth]' refers to a real component of the changes (channel authentication) but lacks specificity about the main implementation detail—sending auth tokens upon channel availability. Clarify the title to be more descriptive, such as 'feat: send auth token on channel connect' or 'feat: implement channel auth with JWT tokens', to better reflect the actual change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chenqwwq chenqwwq mentioned this pull request Apr 9, 2026
for checkstyle
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: 13

🧹 Nitpick comments (1)
cim-common/src/test/java/com/crossoverjie/cim/common/auth/JwtAuthTest.java (1)

28-31: Consider adding assertions for host and port fields.

The test sets host and port in the payload but doesn't verify they are correctly preserved after token verification. These fields are critical for server-side authentication (used in ClientAuthHandler).

Proposed fix
         final PayloadVO payloadVO = JwtUtils.verifyToken(token);
         Assert.assertEquals(userId, payloadVO.getUserId());
         Assert.assertEquals(userName, payloadVO.getUserName());
+        Assert.assertEquals(host, payloadVO.getHost());
+        Assert.assertEquals(port, payloadVO.getPort());
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cim-common/src/test/java/com/crossoverjie/cim/common/auth/JwtAuthTest.java`
around lines 28 - 31, The test JwtAuthTest currently verifies userId and
userName but misses host and port; update the JwtAuthTest test method that
creates the PayloadVO and calls JwtUtils.verifyToken(token) to also assert
payloadVO.getHost() equals the host value you set and payloadVO.getPort() equals
the port value you set, ensuring the token round-trip preserves those fields
used by ClientAuthHandler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@cim-client-sdk/src/main/java/com/crossoverjie/cim/client/sdk/impl/ClientImpl.java`:
- Around line 149-156: The current flow in ClientImpl calls doConnectServer(...)
and loginServer(...).sync() then immediately sets future.complete(true), which
only waits for the outbound write and can mark login as successful before the
server accepts the token; also the catch block calls conf.getEvent().fatal(this)
but doesn't complete the future exceptionally. Change the logic so the
CompletableFuture<Boolean> (future) is completed only when the server-side auth
result is received (i.e., from the server response handler/authorization
callback that processes the auth token result), completeExceptionally(failure)
when auth is rejected or on connection errors, and remove relying on
loginServer(...).sync() as the success signal; also ensure
conf.getEvent().fatal(this) is still invoked on fatal errors but does not
prevent future from being completed exceptionally so callers don't hang.

In `@cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java`:
- Line 8: Remove the unused import of Claim from JwtUtils (the import line
"import com.auth0.jwt.interfaces.Claim") to satisfy Checkstyle UnusedImports;
locate the JwtUtils class in
cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java and
delete the Claim import (or organize imports) so the file compiles and passes
maven-checkstyle-plugin checks.
- Around line 74-82: The catch blocks in JwtUtils (TokenExpiredException,
JWTDecodeException, and the generic Exception in the token verification path)
currently log the full JWT via the variable token; remove any logging of the raw
token value and replace it with a safe alternative (e.g., log a generic message
like "jwt token expired/failed to decode/verify" and optionally include a
non-secret identifier such as a token hash or the subject/claim extracted
securely), ensuring the catches still rethrow the original exceptions
(TokenExpiredException, JWTDecodeException) as before; update the log.warn calls
in the TokenExpiredException, JWTDecodeException, and generic Exception handlers
to omit the token parameter or substitute a non-sensitive fingerprint.
- Around line 30-33: The SECRET field in JwtUtils is hard-coded; replace it with
a value read from external configuration (e.g., System.getenv or application
config) and initialize the Algorithm from that runtime secret instead of a
compile-time constant; update the static ALGORITHM to be created in a static
initializer or via a getAlgorithm() method that reads the env/config value,
validate the secret is present (throw or fail-fast) and keep ISSUER unchanged.
- Around line 70-73: The verifier in JwtUtils.verifyToken (the block that builds
the JWT verifier and calls verify(token)) does not validate the issuer, so add
issuer validation to the verifier by configuring JWT.require(ALGORITHM) to
expect the same ISSUER used in generateToken (use withIssuer(ISSUER)) before
calling build().verify(token); update the logic that reads the "payload" claim
(getClaim("payload").as(PayloadVO.class)) to continue returning the payload
after the verifier enforces the issuer.

In
`@cim-common/src/main/java/com/crossoverjie/cim/common/enums/ChannelAttributeKeys.java`:
- Around line 32-34: The file's enum ChannelAttributeKeys has trailing blank
lines before its closing brace causing a checkstyle violation; open the enum
(ChannelAttributeKeys) and remove any extra blank lines so the final '}'
immediately follows the last enum/member or statement, then save and re-run
checks to ensure the trailing-whitespace/blank-line issue is resolved.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java`:
- Around line 31-57: The current init() unconditionally probes network
interfaces via getLocalHostAddress(), which is brittle; change init() to prefer
an explicit configured host (e.g., a new config property/field like serverHost
or `@Value-injected` property) and only call getLocalHostAddress() as a fallback
when that configured host is empty/null; update getLocalHostAddress() to remain
the fallback probe for local IP discovery. Ensure references to host and
nettyPort remain, and update any field name you introduce (e.g., serverHost) in
init() and any Javadoc so tokens/validation use the explicit configured host
when provided.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/handle/CIMServerHandle.java`:
- Around line 76-87: The method channelRead0 currently logs the full Request via
msg.toString() and msg.getReqMsg(), which now contains sensitive authToken;
remove those raw payload logs and instead log only non-sensitive identifiers.
Replace calls to log.info("received msg=[{}]", msg.toString()) and
log.info("client [{}] online success!!", msg.getReqMsg()) with safe messages
that reference Request identifiers (e.g., req.getUserId(), req.getUserName()) or
a redacted placeholder; ensure code paths using Request, ChannelAuthReq,
msg.getReqMsg(), and msg.toString() no longer emit full payloads to logs.
- Around line 80-86: The code currently binds the session using values from
ChannelAuthReq (req.getUserId()/req.getUserName()), which allows a client to
claim a different identity; instead, read the authenticated identity stored on
the Channel by the earlier ClientAuthHandler (the channel attributes for USER_ID
and USER_NAME via ctx.channel().attr(...).get()), validate they are present, and
call SessionSocketHolder.put(...) and SessionSocketHolder.saveSession(...) using
those channel attribute values (and throw a CIMException if the authenticated
attributes are missing) rather than using req.getUserId()/req.getUserName().

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java`:
- Around line 143-147: The current code in ClientAuthHandler lets any token with
payloadHost "127.0.0.1", "0.0.0.0" or "localhost" bypass host pinning; change
the logic so loopback claims are only accepted when the server itself is bound
to a loopback address. Concretely, in the host-check block that uses payloadHost
and serverConfig.getHost(), replace the unconditional loopback return with a
check that returns true only if payloadHost is loopback AND
serverConfig.getHost() is also a loopback (or both normalize to the same
InetAddress); otherwise require payloadHost.equals(serverConfig.getHost()). Use
payloadHost, serverConfig.getHost(), and ClientAuthHandler to locate and update
the code.
- Around line 95-107: ClientAuthHandler currently stores verified
userId/userName in ChannelAttributeKeys but forwards the original ChannelAuthReq
(msg) allowing spoofing; update ClientAuthHandler so that after setting
ctx.channel().attr(...) and before ctx.fireChannelRead(msg) you create a new
ChannelAuthReq (or copy the existing msg) with userId and userName replaced by
payload.getUserId()/payload.getUserName(), then call ctx.fireChannelRead(...)
with that rewritten request so downstream CIMServerHandle will receive the
verified identity; alternatively, if you prefer changing downstream, update
CIMServerHandle to read identities from ChannelAttributeKeys.USER_ID/USER_NAME
instead of req.getUserId()/getUserName().
- Around line 61-69: The null/non-Request branches in ClientAuthHandler
currently just return and leave the channel open; change them to close the
connection consistently by calling writeAndClose(...) as the other error paths
do: when msg is null or when !(msg instanceof Request) use the same
channel/context error handling path (invoke writeAndClose with an appropriate
error Response or message) so the connection is terminated instead of left
unauthenticated; locate the checks around msg, Request, and replace the plain
returns with calls to the existing writeAndClose(...) helper used elsewhere in
this class.

In `@pom.xml`:
- Around line 50-54: The dependency entry for com.auth0:java-jwt (artifactId
java-jwt) is pinned to 3.19.0 which pulls an insecure jackson-databind; update
the <version> for com.auth0:java-jwt to 4.5.1 in the POM to pick up the fixed
transitive dependency, then run mvn dependency:tree to confirm jackson-databind
is upgraded; if you cannot upgrade java-jwt for compatibility reasons,
explicitly override or exclude the transitive jackson-databind and add a safe
jackson-databind version (>= the patched release) as a direct dependency, and
run the test suite to verify there are no runtime incompatibilities with Spring
Boot 3.3.0.

---

Nitpick comments:
In `@cim-common/src/test/java/com/crossoverjie/cim/common/auth/JwtAuthTest.java`:
- Around line 28-31: The test JwtAuthTest currently verifies userId and userName
but misses host and port; update the JwtAuthTest test method that creates the
PayloadVO and calls JwtUtils.verifyToken(token) to also assert
payloadVO.getHost() equals the host value you set and payloadVO.getPort() equals
the port value you set, ensuring the token round-trip preserves those fields
used by ClientAuthHandler.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d55d6b7d-b72e-4f83-b83c-f50823e1f902

📥 Commits

Reviewing files that changed from the base of the PR and between 8863d9f and c503e13.

📒 Files selected for processing (18)
  • cim-client-sdk/src/main/java/com/crossoverjie/cim/client/sdk/impl/ClientImpl.java
  • cim-client/src/main/java/com/crossoverjie/cim/client/service/MsgHandle.java
  • cim-common/pom.xml
  • cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java
  • cim-common/src/main/java/com/crossoverjie/cim/common/auth/jwt/dto/PayloadVO.java
  • cim-common/src/main/java/com/crossoverjie/cim/common/enums/ChannelAttributeKeys.java
  • cim-common/src/main/java/com/crossoverjie/cim/common/msg/ChannelAuthReq.java
  • cim-common/src/test/java/com/crossoverjie/cim/common/auth/JwtAuthTest.java
  • cim-forward-route/src/main/java/com/crossoverjie/cim/route/controller/RouteController.java
  • cim-rout-api/src/main/java/com/crossoverjie/cim/route/api/vo/res/CIMServerResVO.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/CIMServerApplication.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/config/AppConfiguration.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/handle/CIMServerHandle.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/init/CIMServerInitializer.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/server/CIMServer.java
  • pom.xml
💤 Files with no reviewable changes (1)
  • cim-client/src/main/java/com/crossoverjie/cim/client/service/MsgHandle.java

Comment on lines +149 to +156
try {
this.doConnectServer(cimServer, future);
this.loginServer(cimServer.getAuthToken());
this.serverInfo = cimServer;
future.complete(true);
} catch (Exception e) {
this.conf.getEvent().fatal(this);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Drive the login future from a server auth result, not from the local write.

loginServer(...).sync() only waits for the outbound write to finish. future.complete(true) therefore runs before the server has accepted the token, and the catch block only calls fatal(this) without completing the future exceptionally. An auth failure can look like a successful login or leave the future unresolved.

Also applies to: 207-231

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-client-sdk/src/main/java/com/crossoverjie/cim/client/sdk/impl/ClientImpl.java`
around lines 149 - 156, The current flow in ClientImpl calls
doConnectServer(...) and loginServer(...).sync() then immediately sets
future.complete(true), which only waits for the outbound write and can mark
login as successful before the server accepts the token; also the catch block
calls conf.getEvent().fatal(this) but doesn't complete the future exceptionally.
Change the logic so the CompletableFuture<Boolean> (future) is completed only
when the server-side auth result is received (i.e., from the server response
handler/authorization callback that processes the auth token result),
completeExceptionally(failure) when auth is rejected or on connection errors,
and remove relying on loginServer(...).sync() as the success signal; also ensure
conf.getEvent().fatal(this) is still invoked on fatal errors but does not
prevent future from being completed exceptionally so callers don't hang.

Comment thread cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java Outdated
Comment on lines +30 to +33
private static final String SECRET = "cim#crossoverjie";

public static final Algorithm ALGORITHM = Algorithm.HMAC256(SECRET);
public static final String ISSUER = "cim";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Move the JWT secret out of source control.

Hard-coding the HMAC secret makes every environment share the same signing credential and exposes it to anyone with repository access.

🔑 Suggested fix
-    private static final String SECRET = "cim#crossoverjie";
+    private static final String SECRET =
+            System.getProperty("cim.jwt.secret", System.getenv("CIM_JWT_SECRET"));
+
+    static {
+        Preconditions.checkArgument(StringUtils.isNotBlank(SECRET), "cim jwt secret is required");
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static final String SECRET = "cim#crossoverjie";
public static final Algorithm ALGORITHM = Algorithm.HMAC256(SECRET);
public static final String ISSUER = "cim";
private static final String SECRET;
static {
SECRET = System.getProperty("cim.jwt.secret", System.getenv("CIM_JWT_SECRET"));
if (SECRET == null || SECRET.isBlank()) {
throw new IllegalStateException("cim jwt secret is required");
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java`
around lines 30 - 33, The SECRET field in JwtUtils is hard-coded; replace it
with a value read from external configuration (e.g., System.getenv or
application config) and initialize the Algorithm from that runtime secret
instead of a compile-time constant; update the static ALGORITHM to be created in
a static initializer or via a getAlgorithm() method that reads the env/config
value, validate the secret is present (throw or fail-fast) and keep ISSUER
unchanged.

Comment on lines +70 to +73
final DecodedJWT decodedObj = JWT.require(ALGORITHM)
.build()
.verify(token);
return decodedObj.getClaim("payload").as(PayloadVO.class);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "JwtUtils.java" -type f

Repository: crossoverJie/cim

Length of output: 133


🏁 Script executed:

cat -n ./cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java

Repository: crossoverJie/cim

Length of output: 3769


Add issuer validation to the token verifier.

The generateToken() method stamps the ISSUER claim (withIssuer(ISSUER) at line 49), but verifyToken() does not validate it. The verifier accepts any token signed with the same secret regardless of issuer, creating a security gap.

Suggested fix
             final DecodedJWT decodedObj = JWT.require(ALGORITHM)
+                    .withIssuer(ISSUER)
                     .build()
                     .verify(token);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final DecodedJWT decodedObj = JWT.require(ALGORITHM)
.build()
.verify(token);
return decodedObj.getClaim("payload").as(PayloadVO.class);
final DecodedJWT decodedObj = JWT.require(ALGORITHM)
.withIssuer(ISSUER)
.build()
.verify(token);
return decodedObj.getClaim("payload").as(PayloadVO.class);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java`
around lines 70 - 73, The verifier in JwtUtils.verifyToken (the block that
builds the JWT verifier and calls verify(token)) does not validate the issuer,
so add issuer validation to the verifier by configuring JWT.require(ALGORITHM)
to expect the same ISSUER used in generateToken (use withIssuer(ISSUER)) before
calling build().verify(token); update the logic that reads the "payload" claim
(getClaim("payload").as(PayloadVO.class)) to continue returning the payload
after the verifier enforces the issuer.

Comment on lines +74 to +82
} catch (TokenExpiredException e) {
log.warn("jwt token has expired, token:{}", token);
throw e;
} catch (JWTDecodeException e) {
log.warn("jwt auth token decode failure, token:{}", token, e);
throw e;
} catch (Exception e) {
log.warn("jwt auth token verify failure, token:{}", token, e);
throw new JWTDecodeException("user client auth Token decoder failure");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't write bearer tokens to the logs.

These warnings print the full JWT on expiry/verification failures. That turns the log sink into a credential store for replayable tokens.

🧾 Suggested fix
-            log.warn("jwt token has expired, token:{}", token);
+            log.warn("jwt token has expired");
             throw e;
         } catch (JWTDecodeException e) {
-            log.warn("jwt auth token decode failure, token:{}", token, e);
+            log.warn("jwt auth token decode failure", e);
             throw e;
         } catch (Exception e) {
-            log.warn("jwt auth token verify failure, token:{}", token, e);
+            log.warn("jwt auth token verify failure", e);
             throw new JWTDecodeException("user client auth Token decoder failure");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java`
around lines 74 - 82, The catch blocks in JwtUtils (TokenExpiredException,
JWTDecodeException, and the generic Exception in the token verification path)
currently log the full JWT via the variable token; remove any logging of the raw
token value and replace it with a safe alternative (e.g., log a generic message
like "jwt token expired/failed to decode/verify" and optionally include a
non-secret identifier such as a token hash or the subject/claim extracted
securely), ensuring the catches still rethrow the original exceptions
(TokenExpiredException, JWTDecodeException) as before; update the log.warn calls
in the TokenExpiredException, JWTDecodeException, and generic Exception handlers
to omit the token parameter or substitute a non-sensitive fingerprint.

Comment on lines +80 to +86
final ChannelAuthReq req = JSONObject.parseObject(msg.getReqMsg(), ChannelAuthReq.class);
if (Objects.isNull(req)) {
throw new CIMException("auth req body is empty");
}
//保存客户端与 Channel 之间的关系
SessionSocketHolder.put(msg.getRequestId(), (NioSocketChannel) ctx.channel());
SessionSocketHolder.saveSession(msg.getRequestId(), msg.getReqMsg());
SessionSocketHolder.put(req.getUserId(), (NioSocketChannel) ctx.channel());
SessionSocketHolder.saveSession(req.getUserId(), req.getUserName());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Use the authenticated channel identity when binding the session.

ClientAuthHandler already writes the verified USER_ID / USER_NAME onto the channel before this handler runs. Rebinding from req.getUserId() / req.getUserName() lets a client authenticate with one token and then claim a different identity in the JSON body here.

🔐 Suggested fix
+import com.crossoverjie.cim.common.enums.ChannelAttributeKeys;
...
-            SessionSocketHolder.put(req.getUserId(), (NioSocketChannel) ctx.channel());
-            SessionSocketHolder.saveSession(req.getUserId(), req.getUserName());
+            Long userId = ctx.channel().attr(ChannelAttributeKeys.USER_ID).get();
+            String userName = ctx.channel().attr(ChannelAttributeKeys.USER_NAME).get();
+            if (userId == null || userName == null) {
+                throw new CIMException("channel auth context missing");
+            }
+            SessionSocketHolder.put(userId, (NioSocketChannel) ctx.channel());
+            SessionSocketHolder.saveSession(userId, userName);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final ChannelAuthReq req = JSONObject.parseObject(msg.getReqMsg(), ChannelAuthReq.class);
if (Objects.isNull(req)) {
throw new CIMException("auth req body is empty");
}
//保存客户端与 Channel 之间的关系
SessionSocketHolder.put(msg.getRequestId(), (NioSocketChannel) ctx.channel());
SessionSocketHolder.saveSession(msg.getRequestId(), msg.getReqMsg());
SessionSocketHolder.put(req.getUserId(), (NioSocketChannel) ctx.channel());
SessionSocketHolder.saveSession(req.getUserId(), req.getUserName());
final ChannelAuthReq req = JSONObject.parseObject(msg.getReqMsg(), ChannelAuthReq.class);
if (Objects.isNull(req)) {
throw new CIMException("auth req body is empty");
}
//保存客户端与 Channel 之间的关系
Long userId = ctx.channel().attr(ChannelAttributeKeys.USER_ID).get();
String userName = ctx.channel().attr(ChannelAttributeKeys.USER_NAME).get();
if (userId == null || userName == null) {
throw new CIMException("channel auth context missing");
}
SessionSocketHolder.put(userId, (NioSocketChannel) ctx.channel());
SessionSocketHolder.saveSession(userId, userName);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/handle/CIMServerHandle.java`
around lines 80 - 86, The code currently binds the session using values from
ChannelAuthReq (req.getUserId()/req.getUserName()), which allows a client to
claim a different identity; instead, read the authenticated identity stored on
the Channel by the earlier ClientAuthHandler (the channel attributes for USER_ID
and USER_NAME via ctx.channel().attr(...).get()), validate they are present, and
call SessionSocketHolder.put(...) and SessionSocketHolder.saveSession(...) using
those channel attribute values (and throw a CIMException if the authenticated
attributes are missing) rather than using req.getUserId()/req.getUserName().

Comment on lines +61 to +69
if (Objects.isNull(msg)) {
log.warn("Received null message during authentication");
return;
}

if (!(msg instanceof Request)) {
log.warn("Received non-Request message during authentication: {}", msg.getClass());
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java | head -120

Repository: crossoverJie/cim

Length of output: 5579


🏁 Script executed:

cat -n cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java | tail -50

Repository: crossoverJie/cim

Length of output: 1890


🏁 Script executed:

# Search for ReferenceCountUtil usage in the codebase
rg "ReferenceCountUtil" --type java -B 2 -A 2

Repository: crossoverJie/cim

Length of output: 42


🏁 Script executed:

# Search for patterns where messages are handled in other handlers
rg "instanceof Request" --type java -B 3 -A 3

Repository: crossoverJie/cim

Length of output: 838


🏁 Script executed:

# Search for other inbound handler implementations
rg "channelRead" --type java -B 2 -A 5 | head -100

Repository: crossoverJie/cim

Length of output: 5013


🏁 Script executed:

# Check if Request is a ReferenceCounted object by looking at the protocol definition
fd "Request.java" --type f

Repository: crossoverJie/cim

Length of output: 210


🏁 Script executed:

cat -n cim-common/src/main/java/com/crossoverjie/cim/common/protocol/Request.java 2>/dev/null || cat -n cim-common/src/main/java/com/crossoverjie/cim/common/core/proxy/Request.java | head -50

Repository: crossoverJie/cim

Length of output: 517


🏁 Script executed:

find . -path "*protocol*" -name "Request.java" -type f

Repository: crossoverJie/cim

Length of output: 42


🏁 Script executed:

rg "class Request" --type java -B 2 -A 5

Repository: crossoverJie/cim

Length of output: 42


🏁 Script executed:

# Check the import statement for Request in ClientAuthHandler
rg "import.*Request" cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java

Repository: crossoverJie/cim

Length of output: 112


🏁 Script executed:

find . -path "*common/protocol*" -name "*.java" -type f | head -20

Repository: crossoverJie/cim

Length of output: 42


🏁 Script executed:

rg "class Request\b" --type java

Repository: crossoverJie/cim

Length of output: 42


🏁 Script executed:

rg "interface Request|record Request|class Request" --type java

Repository: crossoverJie/cim

Length of output: 164


🏁 Script executed:

# Find all Java files in the common module
fd -e java "common" | grep -E "protocol|msg" | head -20

Repository: crossoverJie/cim

Length of output: 42


🏁 Script executed:

# List files in common module
ls -la cim-common/src/main/java/com/crossoverjie/cim/common/

Repository: crossoverJie/cim

Length of output: 938


🏁 Script executed:

# Search for where Request is actually defined
rg "import com.crossoverjie.cim.common.protocol" --type java

Repository: crossoverJie/cim

Length of output: 2695


Close connection on null or non-Request messages during authentication.

Lines 61-69 return without closing the connection, inconsistent with all other error paths in this handler (lines 75, 83, 92, 110) which call writeAndClose(). On an authentication-gated pipeline, this leaves the connection stuck unauthenticated.

♻️ Suggested fix
         if (Objects.isNull(msg)) {
             log.warn("Received null message during authentication");
+            writeAndClose("Require authentication first", ctx);
             return;
         }
 
-        if (!(msg instanceof Request)) {
+        if (!(msg instanceof Request request)) {
             log.warn("Received non-Request message during authentication: {}", msg.getClass());
+            writeAndClose("Require authentication first", ctx);
             return;
         }
 
         // 处理 Token
-        final String body = ((Request) msg).getReqMsg();
+        final String body = request.getReqMsg();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Objects.isNull(msg)) {
log.warn("Received null message during authentication");
return;
}
if (!(msg instanceof Request)) {
log.warn("Received non-Request message during authentication: {}", msg.getClass());
return;
}
if (Objects.isNull(msg)) {
log.warn("Received null message during authentication");
writeAndClose("Require authentication first", ctx);
return;
}
if (!(msg instanceof Request request)) {
log.warn("Received non-Request message during authentication: {}", msg.getClass());
writeAndClose("Require authentication first", ctx);
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java`
around lines 61 - 69, The null/non-Request branches in ClientAuthHandler
currently just return and leave the channel open; change them to close the
connection consistently by calling writeAndClose(...) as the other error paths
do: when msg is null or when !(msg instanceof Request) use the same
channel/context error handling path (invoke writeAndClose with an appropriate
error Response or message) so the connection is terminated instead of left
unauthenticated; locate the checks around msg, Request, and replace the plain
returns with calls to the existing writeAndClose(...) helper used elsewhere in
this class.

Comment on lines +95 to +107
ctx.channel().attr(ChannelAttributeKeys.USER_ID).set(payload.getUserId());
ctx.channel().attr(ChannelAttributeKeys.USER_NAME).set(payload.getUserName());
attr.set(Boolean.TRUE);
// 认证成功之后移除自身
ctx.pipeline().remove(this);
// 发送欢迎信息
final Response welcome = Response.newBuilder()
.setResponseId(-1)
.setResMsg("auth success,welcome to cim,You can speak freely")
.setCmd(BaseCommand.PING)
.build();
ctx.writeAndFlush(welcome);
ctx.fireChannelRead(msg); // 继续向后传递
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ChannelAuthReq fields =="
fd 'ChannelAuthReq.java' | xargs -r sed -n '1,220p'

echo
echo "== Client auth request construction =="
fd 'ClientImpl.java' | xargs -r rg -n -C3 'loginServer|ChannelAuthReq|authToken'

echo
echo "== Downstream session binding =="
fd 'CIMServerHandle.java' | xargs -r rg -n -C3 'ChannelAuthReq|getUserId|getUserName|ChannelAttributeKeys'

Repository: crossoverJie/cim

Length of output: 3570


🏁 Script executed:

fd 'ClientAuthHandler.java' | xargs -r wc -l

Repository: crossoverJie/cim

Length of output: 146


🏁 Script executed:

fd 'ClientAuthHandler.java' | xargs -r cat -n

Repository: crossoverJie/cim

Length of output: 7058


Rewrite the forwarded message with verified identity values.

Lines 95-96 extract and store verified userId and userName from the JWT payload, but line 107 forwards the original ChannelAuthReq unchanged. The downstream CIMServerHandle (lines 85-86) reads req.getUserId() and req.getUserName() directly from the request body instead of from the verified channel attributes. This allows a client to send a different userId/userName in the request than what the JWT actually authorizes.

Either rewrite the ChannelAuthReq in the forwarded message with the verified payload values before line 107, or update CIMServerHandle to read the identity from ChannelAttributeKeys.USER_ID / USER_NAME instead of the request body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java`
around lines 95 - 107, ClientAuthHandler currently stores verified
userId/userName in ChannelAttributeKeys but forwards the original ChannelAuthReq
(msg) allowing spoofing; update ClientAuthHandler so that after setting
ctx.channel().attr(...) and before ctx.fireChannelRead(msg) you create a new
ChannelAuthReq (or copy the existing msg) with userId and userName replaced by
payload.getUserId()/payload.getUserName(), then call ctx.fireChannelRead(...)
with that rewritten request so downstream CIMServerHandle will receive the
verified identity; alternatively, if you prefer changing downstream, update
CIMServerHandle to read identities from ChannelAttributeKeys.USER_ID/USER_NAME
instead of req.getUserId()/getUserName().

Comment on lines +143 to +147
// 回环地址直接放行
if ("127.0.0.1".equals(payloadHost) || "0.0.0.0".equals(payloadHost) || "localhost".equals(payloadHost)) {
return true;
}
return payloadHost.equals(serverConfig.getHost());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Loopback claims currently bypass host pinning for every server.

A token with host=127.0.0.1, 0.0.0.0, or localhost is accepted here even when serverConfig.getHost() points to a different machine. That effectively turns the host claim into a wildcard and permits replay across any node sharing the same port.

🔐 Suggested hardening
-        if ("127.0.0.1".equals(payloadHost) || "0.0.0.0".equals(payloadHost) || "localhost".equals(payloadHost)) {
-            return true;
-        }
-        return payloadHost.equals(serverConfig.getHost());
+        if (isLoopback(payloadHost)) {
+            return isLoopback(serverConfig.getHost());
+        }
+        return payloadHost.equals(serverConfig.getHost());
     }
+
+    private boolean isLoopback(String host) {
+        return "127.0.0.1".equals(host)
+                || "0.0.0.0".equals(host)
+                || "localhost".equalsIgnoreCase(host);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java`
around lines 143 - 147, The current code in ClientAuthHandler lets any token
with payloadHost "127.0.0.1", "0.0.0.0" or "localhost" bypass host pinning;
change the logic so loopback claims are only accepted when the server itself is
bound to a loopback address. Concretely, in the host-check block that uses
payloadHost and serverConfig.getHost(), replace the unconditional loopback
return with a check that returns true only if payloadHost is loopback AND
serverConfig.getHost() is also a loopback (or both normalize to the same
InetAddress); otherwise require payloadHost.equals(serverConfig.getHost()). Use
payloadHost, serverConfig.getHost(), and ClientAuthHandler to locate and update
the code.

Comment thread pom.xml
Comment on lines +50 to +54
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.0</version>
</dependency>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for security advisories for java-jwt
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: MAVEN, package: "com.auth0:java-jwt") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Repository: crossoverJie/cim

Length of output: 220


🌐 Web query:

What is the latest version of com.auth0 java-jwt library?

💡 Result:

The latest version of com.auth0:java-jwt on Maven Central is 4.5.1 (published Feb 16, 2026). (repo1.maven.org)

Citations:


🌐 Web query:

java-jwt 3.19.0 security vulnerabilities CVE

💡 Result:

For com.auth0:java-jwt:3.19.0, I did not find CVEs that are assigned to java-jwt itself; however, Maven metadata flags known CVEs coming from its runtime dependency com.fasterxml.jackson.core:jackson-databind:2.13.2. (mvnrepository.com)

CVE impact you’ll see reported for java-jwt:3.19.0 (via jackson-databind):

What to do

  • If you must stay on java-jwt 3.19.0, mitigate by overriding/upgrading jackson-databind to a non-vulnerable version for those CVEs (your build tool’s dependency management/BOM). (mvnrepository.com)

Citations:


🌐 Web query:

java-jwt version history release dates

💡 Result:

Auth0 java-jwt (Maven artifact com.auth0:java-jwt) version history with release dates (per MvnRepository’s Maven Central index): [1]

Version Release date
4.5.1 Feb 16, 2026 [1]
4.5.0 Jan 29, 2025 [1]
4.4.0 Mar 31, 2023 [1]
4.3.0 Feb 13, 2023 [1]
4.2.2 Jan 11, 2023 [1]
4.2.1 Oct 25, 2022 [1]
4.2.0 Oct 20, 2022 [1]
4.1.0 Oct 12, 2022 [1]
4.0.0 Jun 24, 2022 [1]
3.19.4 Jan 11, 2023 [1]
… (list continues back to 0.1 on Mar 07, 2014) [1]

If you want, tell me which range you care about (e.g., only 4.x, or since 3.0.0), and I’ll format the full list for that range.

Source: MvnRepository “Versions (60)” page for com.auth0:java-jwt. [1]


Upgrade java-jwt to version 4.5.1 or apply security patches for jackson-databind dependency.

Version 3.19.0 is outdated and carries known security vulnerabilities through its transitive dependency on jackson-databind:2.13.2, which is affected by CVE-2022-42003, CVE-2022-42004, and CVE-2020-36518. Upgrade to the latest version 4.5.1 (released February 2026) to resolve these vulnerabilities and gain compatibility with Spring Boot 3.3.0.

🧰 Tools
🪛 GitHub Actions: Java CI with Maven

[error] Command failed: mvn checkstyle:check --file pom.xml (maven-checkstyle-plugin:3.3.1:check default-cli) on project cim-common due to 1 Checkstyle violation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pom.xml` around lines 50 - 54, The dependency entry for com.auth0:java-jwt
(artifactId java-jwt) is pinned to 3.19.0 which pulls an insecure
jackson-databind; update the <version> for com.auth0:java-jwt to 4.5.1 in the
POM to pick up the fixed transitive dependency, then run mvn dependency:tree to
confirm jackson-databind is upgraded; if you cannot upgrade java-jwt for
compatibility reasons, explicitly override or exclude the transitive
jackson-databind and add a safe jackson-databind version (>= the patched
release) as a direct dependency, and run the test suite to verify there are no
runtime incompatibilities with Spring Boot 3.3.0.

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.

♻️ Duplicate comments (4)
cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java (3)

29-31: ⚠️ Potential issue | 🔴 Critical

Move the JWT signing secret out of source control.

This still hard-codes the HMAC key that both the route service and the server trust for token issuance/verification. Anyone with repo access can mint valid auth tokens for every environment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java`
around lines 29 - 31, The JwtUtils class currently hard-codes the HMAC secret
via the SECRET constant and builds ALGORITHM from it; move secret management out
of source by loading the signing key from configuration or environment at
runtime (e.g., System.getenv or injected application properties) and initialize
ALGORITHM from that value instead of the SECRET constant, and fail-fast or throw
a clear error if the configured secret is missing/empty so no default secret
remains in code; ensure references to SECRET and ALGORITHM are updated to use
the runtime-loaded value.

69-71: ⚠️ Potential issue | 🟠 Major

Validate the issuer during verification.

generateToken() stamps ISSUER, but verifyToken() does not require it. Add .withIssuer(ISSUER) before build() so the verifier rejects tokens signed with the same key but minted for a different issuer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java`
around lines 69 - 71, The token verifier in JwtUtils (where DecodedJWT
decodedObj is created via JWT.require(ALGORITHM).build().verify(token)) does not
validate the issuer; update the verifier used in the verifyToken method to
include .withIssuer(ISSUER) before .build() so tokens are rejected if their
ISSUER claim does not match the one stamped by generateToken().

73-81: ⚠️ Potential issue | 🟠 Major

Don't write bearer tokens to the logs.

These warning paths still log the full JWT. Keep the exception details, but remove token from the log line or replace it with a non-secret fingerprint.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java`
around lines 73 - 81, In JwtUtils.java update the catch blocks
(TokenExpiredException, JWTDecodeException, and the generic Exception handler)
so log.warn no longer prints the full JWT token; instead log only the exception
and a non-secret fingerprint (e.g., a deterministic hash like SHA-256 or first 8
chars of a secure HMAC) computed from the token. Locate the log.warn calls in
the catch blocks around TokenExpiredException, JWTDecodeException and the
generic Exception and replace the token interpolation with the fingerprint or
omit the token entirely, keeping the thrown exceptions unchanged.
cim-client-sdk/src/main/java/com/crossoverjie/cim/client/sdk/impl/ClientImpl.java (1)

149-156: ⚠️ Potential issue | 🟠 Major

Don't mark login successful from the outbound write.

loginServer(...).sync() only waits for the auth request to be written. The actual acceptance happens later in cim-server/src/main/java/com/crossoverjie/cim/server/handle/ClientAuthHandler.java:80-91, so completing future and setting serverInfo here can still report a successful login for a token the server rejects. The catch path also still misses future.completeExceptionally(...), so callers can hang on failures.

Also applies to: 207-230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-client-sdk/src/main/java/com/crossoverjie/cim/client/sdk/impl/ClientImpl.java`
around lines 149 - 156, The code currently treats the outbound write as
successful auth: do not call future.complete(true) or set serverInfo after
loginServer(...) returns, because loginServer(...).sync() only waits for the
request to be written; instead, move completion and serverInfo assignment to the
actual auth-success callback (the handler that processes server acceptance), and
ensure failures complete the future exceptionally; also update the catch to call
future.completeExceptionally(e) in addition to conf.getEvent().fatal(this).
Apply the same change to the other similar block referenced (the code around
207-230) so callers only observe success when the server's auth-accept event is
received.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@cim-client-sdk/src/main/java/com/crossoverjie/cim/client/sdk/impl/ClientImpl.java`:
- Around line 149-156: The code currently treats the outbound write as
successful auth: do not call future.complete(true) or set serverInfo after
loginServer(...) returns, because loginServer(...).sync() only waits for the
request to be written; instead, move completion and serverInfo assignment to the
actual auth-success callback (the handler that processes server acceptance), and
ensure failures complete the future exceptionally; also update the catch to call
future.completeExceptionally(e) in addition to conf.getEvent().fatal(this).
Apply the same change to the other similar block referenced (the code around
207-230) so callers only observe success when the server's auth-accept event is
received.

In `@cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java`:
- Around line 29-31: The JwtUtils class currently hard-codes the HMAC secret via
the SECRET constant and builds ALGORITHM from it; move secret management out of
source by loading the signing key from configuration or environment at runtime
(e.g., System.getenv or injected application properties) and initialize
ALGORITHM from that value instead of the SECRET constant, and fail-fast or throw
a clear error if the configured secret is missing/empty so no default secret
remains in code; ensure references to SECRET and ALGORITHM are updated to use
the runtime-loaded value.
- Around line 69-71: The token verifier in JwtUtils (where DecodedJWT decodedObj
is created via JWT.require(ALGORITHM).build().verify(token)) does not validate
the issuer; update the verifier used in the verifyToken method to include
.withIssuer(ISSUER) before .build() so tokens are rejected if their ISSUER claim
does not match the one stamped by generateToken().
- Around line 73-81: In JwtUtils.java update the catch blocks
(TokenExpiredException, JWTDecodeException, and the generic Exception handler)
so log.warn no longer prints the full JWT token; instead log only the exception
and a non-secret fingerprint (e.g., a deterministic hash like SHA-256 or first 8
chars of a secure HMAC) computed from the token. Locate the log.warn calls in
the catch blocks around TokenExpiredException, JWTDecodeException and the
generic Exception and replace the token interpolation with the fingerprint or
omit the token entirely, keeping the thrown exceptions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7758c857-b9f0-485b-a1c4-b0d16b424451

📥 Commits

Reviewing files that changed from the base of the PR and between c503e13 and 916d65f.

📒 Files selected for processing (4)
  • cim-client-sdk/src/main/java/com/crossoverjie/cim/client/sdk/impl/ClientImpl.java
  • cim-common/src/main/java/com/crossoverjie/cim/common/auth/JwtUtils.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/CIMServerApplication.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/server/CIMServer.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • cim-server/src/main/java/com/crossoverjie/cim/server/server/CIMServer.java
  • cim-server/src/main/java/com/crossoverjie/cim/server/CIMServerApplication.java

沽酒 added 2 commits April 10, 2026 15:14
- 使用 serverConfig.getHost() 替代 InetAddress.getLocalHost().getHostAddress()
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

♻️ Duplicate comments (1)
cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java (1)

31-35: ⚠️ Potential issue | 🟠 Major

Prefer configured host first; current auto-probing can break JWT auth.

Line 33 unconditionally probes interfaces, which is brittle in Docker/multi-NIC setups. Since auth does exact host matching, this can reject valid tokens at runtime.

Suggested fix
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
@@
     `@Value`("${cim.server.port}")
     private int nettyPort;
+
+    `@Value`("${cim.server.host:}")
+    private String configuredHost;
@@
     `@PostConstruct`
     public void init() throws Exception {
-        this.host = getLocalHostAddress();
+        this.host = StringUtils.hasText(configuredHost) ? configuredHost : getLocalHostAddress();
         log.info("当前服务配置, host={}, port={}", host, nettyPort);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java`
around lines 31 - 35, init() currently always overwrites the configured host by
calling getLocalHostAddress(), which breaks exact-host JWT matching in
multi-NIC/Docker environments; change init() so it only probes and assigns
this.host via getLocalHostAddress() when the configured host is null/empty
(trimmed), otherwise keep the configured value; reference the init() method, the
host field, and getLocalHostAddress() to locate where to add the conditional
check and avoid overwriting an explicit configuration (ensure empty-string
checks are handled).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java`:
- Around line 41-63: getLocalHostAddress() currently only skips loopback and
down interfaces, so it can return virtual/container/bridge addresses (e.g.,
docker0) and break host matching; update the method (in getLocalHostAddress) to
also skip NetworkInterface instances where networkInterface.isVirtual() is true
and to filter out common virtual/container interface name patterns (e.g., names
starting with "docker", "veth", "br-") or display names indicating virtual
adapters before iterating addresses, keeping the existing checks
(isLoopback/isUp) and logging behavior.

---

Duplicate comments:
In
`@cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java`:
- Around line 31-35: init() currently always overwrites the configured host by
calling getLocalHostAddress(), which breaks exact-host JWT matching in
multi-NIC/Docker environments; change init() so it only probes and assigns
this.host via getLocalHostAddress() when the configured host is null/empty
(trimmed), otherwise keep the configured value; reference the init() method, the
host field, and getLocalHostAddress() to locate where to add the conditional
check and avoid overwriting an explicit configuration (ensure empty-string
checks are handled).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c7bde790-bac2-4c8a-9aff-3e0114a0810c

📥 Commits

Reviewing files that changed from the base of the PR and between 8edfcb5 and 5cd3f5f.

📒 Files selected for processing (1)
  • cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java

Comment on lines +41 to +63
private String getLocalHostAddress() throws Exception {
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
// 只跳过回环接口和未启用的接口
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
continue;
}

Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
String hostAddress = address.getHostAddress();

// 跳过IPv6地址
if (hostAddress.contains(":")) {
continue;
}

// 跳过127.0.0.1
if (!hostAddress.equals("127.0.0.1")) {
log.debug("找到网络接口: {}, 地址: {}", networkInterface.getName(), hostAddress);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

getLocalHostAddress() does not enforce its own “non-virtual” contract.

Line 39 states virtual interfaces should be excluded, but Lines 47-48 only skip loopback/down interfaces. This can pick docker0/bridge IPs and cause downstream host mismatch in auth/registry.

Suggested fix
-                // 只跳过回环接口和未启用的接口
-                if (networkInterface.isLoopback() || !networkInterface.isUp()) {
+                // 跳过回环、虚拟、未启用接口
+                if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) {
                     continue;
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private String getLocalHostAddress() throws Exception {
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
// 只跳过回环接口和未启用的接口
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
continue;
}
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
String hostAddress = address.getHostAddress();
// 跳过IPv6地址
if (hostAddress.contains(":")) {
continue;
}
// 跳过127.0.0.1
if (!hostAddress.equals("127.0.0.1")) {
log.debug("找到网络接口: {}, 地址: {}", networkInterface.getName(), hostAddress);
private String getLocalHostAddress() throws Exception {
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
// 跳过回环、虚拟、未启用接口
if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) {
continue;
}
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
String hostAddress = address.getHostAddress();
// 跳过IPv6地址
if (hostAddress.contains(":")) {
continue;
}
// 跳过127.0.0.1
if (!hostAddress.equals("127.0.0.1")) {
log.debug("找到网络接口: {}, 地址: {}", networkInterface.getName(), hostAddress);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java`
around lines 41 - 63, getLocalHostAddress() currently only skips loopback and
down interfaces, so it can return virtual/container/bridge addresses (e.g.,
docker0) and break host matching; update the method (in getLocalHostAddress) to
also skip NetworkInterface instances where networkInterface.isVirtual() is true
and to filter out common virtual/container interface name patterns (e.g., names
starting with "docker", "veth", "br-") or display names indicating virtual
adapters before iterating addresses, keeping the existing checks
(isLoopback/isUp) and logging behavior.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 55.55556% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.84%. Comparing base (8863d9f) to head (b330dcc).

Files with missing lines Patch % Lines
...ava/com/crossoverjie/cim/common/auth/JwtUtils.java 72.22% 10 Missing ⚠️
...m/crossoverjie/cim/client/sdk/impl/ClientImpl.java 68.18% 5 Missing and 2 partials ⚠️
...om/crossoverjie/cim/common/msg/ChannelAuthReq.java 0.00% 7 Missing ⚠️
...soverjie/cim/route/controller/RouteController.java 0.00% 7 Missing ⚠️
...overjie/cim/common/enums/ChannelAttributeKeys.java 0.00% 4 Missing ⚠️
...rossoverjie/cim/common/auth/jwt/dto/PayloadVO.java 80.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #189      +/-   ##
============================================
+ Coverage     52.53%   52.84%   +0.31%     
- Complexity      342      353      +11     
============================================
  Files            79       83       +4     
  Lines          1738     1811      +73     
  Branches        134      134              
============================================
+ Hits            913      957      +44     
- Misses          783      811      +28     
- Partials         42       43       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

private static final long serialVersionUID = 1L;

/**
* 用户id
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Change all the comments to English

SessionSocketHolder.put(msg.getRequestId(), (NioSocketChannel) ctx.channel());
SessionSocketHolder.saveSession(msg.getRequestId(), msg.getReqMsg());
SessionSocketHolder.put(req.getUserId(), (NioSocketChannel) ctx.channel());
SessionSocketHolder.saveSession(req.getUserId(), req.getUserName());
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why use the userId replace the requestId

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This userId is what truly represents "which user this channel belongs to", requestId is a protocol-layer request ID

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.

🧹 Nitpick comments (2)
cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java (2)

32-32: Avoid throws Exception on a @PostConstruct method.

NetworkInterface.getNetworkInterfaces() throws SocketException, and the body already swallows it in a try/catch. The only remaining checked throw is InetAddress.getLocalHost() in the fallback (UnknownHostException). Narrow the signature to throws UnknownHostException (or catch it and log, returning a safe default) so startup failure modes are explicit rather than opaque.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java`
at line 32, The `@PostConstruct` method init in ServerConfig currently declares
throws Exception; narrow this to either throws UnknownHostException or, better,
remove the checked exception entirely by catching UnknownHostException inside
init (or the fallback call to InetAddress.getLocalHost()), log a clear warning
and return a safe default instead of propagating Exception. Locate the init
method in ServerConfig and replace the broad throws Exception with the specific
UnknownHostException or add a try/catch around the fallback
InetAddress.getLocalHost() call to handle/log the UnknownHostException and
ensure init does not throw a generic Exception.

60-65: Redundant 127.0.0.1 guard.

Loopback interfaces are already skipped at L47, so the !hostAddress.equals("127.0.0.1") check here is effectively dead (any 127.x address would have been on lo). The inverted condition also makes the control flow harder to read — simplify to a direct return hostAddress; after the IPv6 check, or replace with address.isLoopbackAddress() / address.isAnyLocalAddress() if you want an address-level safety net.

♻️ Proposed simplification
-                    // Skip 127.0.0.1
-                    if (!hostAddress.equals("127.0.0.1")) {
-                        log.debug("Found network interface: {}, address: {}", networkInterface.getName(), hostAddress);
-                        return hostAddress;
-                    }
+                    if (address.isLoopbackAddress() || address.isAnyLocalAddress()) {
+                        continue;
+                    }
+                    log.debug("Found network interface: {}, address: {}", networkInterface.getName(), hostAddress);
+                    return hostAddress;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java`
around lines 60 - 65, The extra check !hostAddress.equals("127.0.0.1") is
redundant because loopback interfaces are already skipped earlier; inside the
loop after the IPv6 check simply return hostAddress (or, if you prefer an
address-level guard, replace the string check with address.isLoopbackAddress()
or address.isAnyLocalAddress()). Update the block around networkInterface /
address / hostAddress in ServerConfig (the loop that inspects
networkInterface.getInetAddresses()) to remove the inverted 127.0.0.1 condition
and return hostAddress directly (or use
address.isLoopbackAddress()/isAnyLocalAddress() for an explicit safety check).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java`:
- Line 32: The `@PostConstruct` method init in ServerConfig currently declares
throws Exception; narrow this to either throws UnknownHostException or, better,
remove the checked exception entirely by catching UnknownHostException inside
init (or the fallback call to InetAddress.getLocalHost()), log a clear warning
and return a safe default instead of propagating Exception. Locate the init
method in ServerConfig and replace the broad throws Exception with the specific
UnknownHostException or add a try/catch around the fallback
InetAddress.getLocalHost() call to handle/log the UnknownHostException and
ensure init does not throw a generic Exception.
- Around line 60-65: The extra check !hostAddress.equals("127.0.0.1") is
redundant because loopback interfaces are already skipped earlier; inside the
loop after the IPv6 check simply return hostAddress (or, if you prefer an
address-level guard, replace the string check with address.isLoopbackAddress()
or address.isAnyLocalAddress()). Update the block around networkInterface /
address / hostAddress in ServerConfig (the loop that inspects
networkInterface.getInetAddresses()) to remove the inverted 127.0.0.1 condition
and return hostAddress directly (or use
address.isLoopbackAddress()/isAnyLocalAddress() for an explicit safety check).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4780c9d9-87f0-44b7-bb84-91531c062ef0

📥 Commits

Reviewing files that changed from the base of the PR and between 5cd3f5f and b330dcc.

📒 Files selected for processing (1)
  • cim-server/src/main/java/com/crossoverjie/cim/server/config/ServerConfig.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants