Skip to content

Feat: support for authentication#75

Open
678098 wants to merge 1 commit intobloomberg:mainfrom
678098:260427_authn_sdk
Open

Feat: support for authentication#75
678098 wants to merge 1 commit intobloomberg:mainfrom
678098:260427_authn_sdk

Conversation

@678098
Copy link
Copy Markdown
Collaborator

@678098 678098 commented Apr 28, 2026

Description

  • Extend Java SDK protocol with AuthenticationRequest / AuthenticationResponse messages.
  • Make authentication callback configuration in SessionOptions.
  • Enable the default ANONYMOUS authentication for docker bmqbrkr.tsk ITs and bmq-examples.
  • Extend connection FSM with new AUTHENTICATING state, handle authentication events.

Design decisions

Authentication auto enablement

I decided not to add ANONYMOUS authentication callback to the default SessionOptions because it breaks a half of the existing tests. We have 2 types of ITs in SDK: the ones with real broker and the ones with mock broker simulator. The real broker works fine, the mocks require not-trivial updating. I will do it in the next PR.

AuthnCredential callback

a. C++ mirror (Optional, StringBuilder):

        final SessionOptions sessionOptions =
                SessionOptions.builder()
                        .setBrokerUri(URI.create(brokerUri))
                        .setAuthnCredentialCb(
                                error ->
                                        Optional.of(
                                                AuthnCredential.builder()
                                                        .setMechanism("ANONYMOUS")
                                                        .setData(new byte[0])
                                                        .build()))
                        .build();

Java already supports nullable references so there is no real need to use Optional, we can just return null. Also it is not very convenient to provide a string builder to the call.

b. Builder variant:

        final SessionOptions sessionOptions =
                SessionOptions.builder()
                        .setBrokerUri(URI.create(brokerUri))
                        .setAuthnCredentialCb(
                                () ->
                                        AuthnCredentialResult.success(
                                                AuthnCredential.builder()
                                                        .setMechanism("ANONYMOUS")
                                                        .setData(new byte[0])
                                                        .build()))
                        .build();

AuthnCredentialResult holds either a valid credential or an error message.

c. Hidden builder variant - implemented:

        final SessionOptions sessionOptions =
                SessionOptions.builder()
                        .setBrokerUri(URI.create(brokerUri))
                        .setAuthnCredentialCb(
                                () ->
                                        AuthnCredentialResult.success("ANONYMOUS", new byte[0]))
                        .build();

The protocol is fixed, so we can rely on the fact that we will always have a mechanism and a data. It is unlikely that we will be able to extend the protocol easily. So we can hide the AuthnCredential builder and make AuthnCredentialResult build easier.

@678098 678098 force-pushed the 260427_authn_sdk branch from 5c83a2f to ab8db41 Compare April 28, 2026 22:15
AuthnCredentialResult.success(
AuthnCredential.builder()
.setMechanism("ANONYMOUS")
.setData(new byte[0])
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We can assume that missing setData means empty byte array new byte[0], but in most cases we expect to have something in data, so this API simplification is not useful for the future use.

SessionOptions.builder().setBrokerUri(URI.create(brokerUri)).build(),
SessionOptions.builder()
.setBrokerUri(URI.create(brokerUri))
.setAuthnCredentialCb(
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This callback is more convenient than C++ one.
In C++, the callback is:

typedef bsl::function<bsl::optional<AuthnCredential>(bsl::ostream& error)>
        AuthnCredentialCb;

In Java, we don't want to provide a string builder arg when we can just return a "variant" type that is either a correct result or error message.

Maybe we can update the C++ implementation to match this.

@678098 678098 changed the title Feat: support authentication Feat: support for authentication Apr 29, 2026
@678098 678098 force-pushed the 260427_authn_sdk branch 3 times, most recently from a22a9e0 to ebcff13 Compare April 30, 2026 17:49
/* s6 STOPPED*/ {
1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
/* s7 STOPPED */ {
1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
Copy link
Copy Markdown
Collaborator Author

@678098 678098 Apr 30, 2026

Choose a reason for hiding this comment

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

Changes here:

  1. Added a new row /* s2 AUTHENTICATING */. As a result, all values in this table that are > 2 got incremented by 1. Value 2 was for NEGOTIATING and now it is pointing to AUTHENTICATING state.

  2. In each existing row, 3 values added to the end. These values are for authentication events. We do not expect these events in any state except AUTHENTICATING so we do not change state if we observe them in s0-s1, s3-s7.

  3. We go from AUTHENTICATING to NEGOTIATING only on AUTHENTICATION_RESPONSE

@678098 678098 force-pushed the 260427_authn_sdk branch from ebcff13 to 5663bc6 Compare April 30, 2026 19:43
*/
package com.bloomberg.bmq.impl.infr.msg;

public class AuthenticationResponse {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

AuthenticationResponse is constructed by Gson during deserialization with Field.set, there is no need to provide any specific APIs to set fields such as setters or constructor with arguments

* A message sent from a client to a broker during session initiation or reauthentication,
* indicating the client is attempting to authenticate with the supplied authentication material.
*/
public class AuthenticationRequest {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

AuthenticationRequest is constructed in SDK.
We provide only a constructor with arguments and make it immutable after construction

@678098 678098 force-pushed the 260427_authn_sdk branch from 5663bc6 to a115359 Compare April 30, 2026 20:11
} catch (IOException | JsonSyntaxException ex) {
logger.error("Failed to decode Authentication event: ", ex);
}
authnChoice = decoded;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If decoding fails, we will log the error and still pass this event to FSM.
FSM handler TcpBrokerConnection::handleAuthenticationResponse will see the null authnChoice and will send AUTHENTICATION_FAILURE to FSM.

Signed-off-by: Evgeny Malygin <emalygin@bloomberg.net>
@678098 678098 force-pushed the 260427_authn_sdk branch from a115359 to 92ea514 Compare April 30, 2026 20:26
.setBrokerUri(uri)
.setAuthnCredentialCb(
() -> AuthnCredentialResult.success("ANONYMOUS", new byte[0]))
.build();
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This will make container ITs run with authn

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