apache/pulsar · error · UnsupportedCallbackException

Unrecognized SASL GSSAPI Server Callback.

Error message

Unrecognized SASL GSSAPI Server Callback.

What it means

UnsupportedCallbackException thrown by PulsarSaslServer.SaslServerCallbackHandler.handle when the SASL framework presents a Callback type the handler does not recognize. This server-side handler only supports AuthorizeCallback, which is what the GSSAPI mechanism supplies; any other callback type (e.g. NameCallback, PasswordCallback from a mechanism mismatch) reaches the default branch and throws.

Solutions

  1. Ensure the SaslServer is created strictly for the GSSAPI mechanism (Sasl.createSaslServer("GSSAPI", ...) as done in PulsarSaslServer.createSaslServer) and the client negotiates GSSAPI/Kerberos only
  2. Check client SASL config (jaas.conf / mechanism properties) so it does not fall back to PLAIN or DIGEST-MD5 against this broker
  3. If you extended the handler for other mechanisms, add instanceof branches for the expected callback types (NameCallback, PasswordCallback, etc.) before the throw
  4. Remove/verify any custom SaslServerFactory or security.provider registrations that could alter the callback list

Example fix

// before
if (callback instanceof AuthorizeCallback) {
    handleAuthorizeCallback((AuthorizeCallback) callback);
} else {
    throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Server Callback.");
}
// after
if (callback instanceof AuthorizeCallback) {
    handleAuthorizeCallback((AuthorizeCallback) callback);
} else if (callback instanceof NameCallback) {
    ((NameCallback) callback).setName("");
} else {
    throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Server Callback.");
}
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm the client negotiates GSSAPI only
if (!saslMechanism.equals("GSSAPI")) {
    throw new IllegalArgumentException("Server supports GSSAPI only; got " + saslMechanism);
}

Type guard

boolean isSupportedCallback(Callback c) {
    return c instanceof AuthorizeCallback;
}

Try / catch

try {
    return AuthData.of(saslServer.evaluateResponse(token.getBytes()));
} catch (SaslException e) {
    Throwable cause = e;
    while ((cause = cause.getCause()) != null) {
        if (cause instanceof UnsupportedCallbackException) {
            throw new AuthenticationException("SASL mechanism/callback mismatch; client must use GSSAPI");
        }
    }
    throw new AuthenticationException(e.getMessage());
}

Prevention

When it happens

Trigger: SaslServer.evaluateResponse (via PulsarSaslServer.response) triggers the underlying SaslServer's callback handler with a callback other than AuthorizeCallback — typically when the negotiated/configured mechanism is not plain GSSAPI (e.g. SASL mechanism negotiation picks DIGEST-MD5/PLAIN which request NameCallback/PasswordCallback), or a custom Sasl factory injects extra callbacks.

Common situations: Client and broker disagree on SASL mechanism (client configured for a mechanism requiring name/password callbacks while the server was built for GSSAPI); a custom SaslServerFactory is on the classpath adding callbacks; broker code modified to create the SaslServer with additional mechanism properties.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/e49d32365fc8559f. Report an issue: GitHub.

Appendix: source

Thrown at pulsar-broker-auth-sasl/src/main/java/org/apache/pulsar/broker/authentication/PulsarSaslServer.java:160

            log.error().exception(e).log("response: Failed to evaluate client token");
            throw new AuthenticationException(e.getMessage());
        }
    }

    static class SaslServerCallbackHandler implements CallbackHandler {
        Pattern allowedIdsPattern;

        public SaslServerCallbackHandler(Pattern pattern) {
            this.allowedIdsPattern = pattern;
        }

        @Override
        public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
            for (Callback callback : callbacks) {
                if (callback instanceof AuthorizeCallback) {
                    handleAuthorizeCallback((AuthorizeCallback) callback);
                } else {
                    throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Server Callback.");
                }
            }
        }

        private void handleAuthorizeCallback(AuthorizeCallback ac) {
            String authenticationID = ac.getAuthenticationID();
            String authorizationID = ac.getAuthorizationID();
            if (!authenticationID.equals(authorizationID)) {
                ac.setAuthorized(false);
                log.info().attr("authenticationID", authenticationID).attr("authorizationID", authorizationID)
                        .log("Forbidden access to client");
                return;
            }
            if (!allowedIdsPattern.matcher(authenticationID).matches()) {
                ac.setAuthorized(false);
                log.info()
                    .attr("authenticationID", authenticationID)
                    .attr("property", SaslConstants.JAAS_CLIENT_ALLOWED_IDS)

View on GitHub (pinned to 820761864e)