Search code examples
javakerberosgssapinegotiatehttp-negotiate

GSSContext, kerberos authentication, negotiate


i've simple java endpoint to validate user credentials on server A (this is where my java application run)

so when user hit /kerberos-auth it should be able validate user identity that connect to the same AD. User should bring negotiate token

this is the code

public LoginResponse authenticateWithKerberos(String kerberosToken) throws GSSException {

        System.setProperty("java.security.krb5.conf", "C:/Windows/krb5.conf");
        System.setProperty("java.security.auth.login.config", "C:/Windows/jaas.conf");
        System.setProperty("sun.security.krb5.debug", "true");
        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
        System.setProperty("sun.security.krb5.disableReferrals", "true");

        kerberosToken = kerberosToken.replace('-', '+').replace('_', '/');
        int paddingLength = 4 - (kerberosToken.length() % 4);
        if (paddingLength < 4) {
            kerberosToken += "=".repeat(paddingLength);
        }

        byte[] tokenBytes = Base64.getDecoder().decode(kerberosToken);
        GSSManager gssManager = GSSManager.getInstance();
        Oid kerberosOid = new Oid(kerberosConfig.getOid());
        GSSName targetName = gssManager.createName(kerberosConfig.getServiceprincipal(), GSSName.NT_HOSTBASED_SERVICE);
        GSSContext gssContext = gssManager.createContext(targetName, kerberosOid, null, GSSContext.DEFAULT_LIFETIME);
        gssContext.requestMutualAuth(false);
        gssContext.requestCredDeleg(true);

        try {
            gssContext.initSecContext(tokenBytes, 0, tokenBytes.length);
            int atIndex = gssContext.getSrcName().toString().indexOf('@');
            String username = gssContext.getSrcName().toString().substring(0, atIndex);
            DetailUserResponse detailUserResponse = getUserWithSystemDetail(username);
            String token = jwtUtil.generateToken(detailUserResponse.getUser(), detailUserResponse.getSystemDetail());

            return new LoginResponse(token, detailUserResponse.getUser());
        }catch (Exception e){
            throw new UserNotFoundException(MessageConstants.USER_NOT_FOUND);
        }finally{
            gssContext.dispose();
        }
    }

server A used rikim user as credentials/ as service account. then user B coming with negotiate token with ticket cache on his local as user ariandop

My problem, when user B ariandop make a request. why my application keep validated as rikim ? instead of ariandop.

is there something wrong with my code ? please help me, thanks ...

I want it to validate as ariandop which is the real username credentials on their local. instead authenticated as rikim which is service account.

how can i achieve it


Solution

  • server A used rikim user as credentials/ as service account. then user B coming with negotiate token with ticket cache on his local as user ariandop

    Avoid using a regular user account as service account – at least not in production. Services (web apps, etc) should have a dedicated account for that purpose.

    My problem, when user B ariandop make a request. why my application keep validated as rikim ? instead of ariandop.

    Because you actually implemented the "client" side of GSSAPI, not the "server" side.

    When you call initSecContext(), you're telling GSSAPI to act as a client ("initiator" in GSSAPI terminology), and the first call probably just ignores the input token (because Kerberos is a "client first" mechanism).

    Instead, the server ("acceptor" in GSSAPI terms) needs to call acceptSecContext() with the received token from client (docs).

    Also, the GSSContext itself likely needs to be created using a different constructor: the Java docs list createContext(GSSCredential myCred) to make an 'acceptor' context. In this case, the GSSCredential seems to be mandatory (as it specifies the service keytab which Java will use to verify tickets), and it should be created with ACCEPT_ONLY flag.

    how to make negotiate token send automatically wired firefox ? or chrome. when user open browser and navigate to my website domain.

    HTTP "Negotiate" authentication follows the same flow as Basic or Digest – browsers will always make an initial request without any token (i.e. without an Authorization header). Whenever your app receives such a request without a token (or with an invalid token), it needs to send a 401 reply with the WWW-Authenticate: Negotiate header; the browser will then get a ticket and automatically retry the request with the token added.

    Client (GSSAPI initiator)         | Server (GSSAPI acceptor)
    ======================================================================
    GET /app                         -->
    ----------------------------------------------------------------------
                                     <-- 401 Unauthorized
                                     <-- WWW-Authenticate: Negotiate
    ----------------------------------------------------------------------
    token = initSecContext()          |
    ----------------------------------------------------------------------
    GET /app                         -->
    Authorization: Negotiate B64TOK= -->
    ----------------------------------------------------------------------
                                      | acceptSecContext(token)
    ----------------------------------------------------------------------
                                     <-- 200 OK
    

    Non-browser HTTP clients can usually be told to send a token proactively (to reduce latency).

    Side note: Clients always send the token using standard (not "urlsafe") Base64, so you shouldn't need any .replace() nor other adjustments before decode().

    Larger side note: HTTP "Negotiate" isn't plain Kerberos; it is Kerberos-inside-SPNEGO. Usually the same GSSAPI can deal with both types (especially on the acceptor side), but if you ever need to write an initiator (client), you may need to somehow configure GSSAPI to use SPNEGO instead of plain Kerberos. Using kerb4j might make that easier.

    is it just to enable trust on internet options ? and config on chrome & firefox itself right ? related to policy

    You need to allow Negotiate via policy and send the standard headers.

    The policy differs between browsers:

    • For Chrome/Edge/IE (Windows), "Internet Options" zones ("Integrated Authentication" setting) should work.

      Although Chrome also has its own policy system on other OS (Linux/macOS/ChromeOS), but I believe it also honors Internet Options zones on Windows.

    • For Firefox (all OS), you need the network.negotiate-auth.trusted-uris setting in about:config – or the corresponding Mozilla GPO setting. Firefox ignores IE zones completely.

    • For testing via curl (all OS), use --negotiate. (In older curl versions, empty username -u: also has to be specified.)

      $ curl --negotiate -u: https://auth.example.com/kerberos-test
      PS> iwr -usedefaultcredentials https://auth.example.com/kerberos-test
      
    • For testing via PowerShell, specify -UseDefaultCredentials either with Invoke-WebRequest or Invoke-RestMethod.

    • For testing via Python: https://stackoverflow.com/a/71643255/49849