public String generateToken(String host) throws SpnegoEngineException {
        GSSContext gssContext = null;
        byte[] token = null; // base64 decoded challenge
        Oid negotiationOid;

        try {
            /*
             * Using the SPNEGO OID is the correct method. Kerberos v5 works for IIS but not JBoss.
             * Unwrapping the initial token when using SPNEGO OID looks like what is described here...
             *
             * http://msdn.microsoft.com/en-us/library/ms995330.aspx
             *
             * Another helpful URL...
             *
             * http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html
             *
             * Unfortunately SPNEGO is JRE >=1.6.
             */
            // Try SPNEGO by default, fall back to Kerberos later if error
            negotiationOid = new Oid(SPNEGO_OID);

            String spn = getCompleteServicePrincipalName(host);
            try {
                GSSManager manager = GSSManager.getInstance();
                GSSName serverName = manager.createName(spn, GSSName.NT_HOSTBASED_SERVICE);
                GSSCredential myCred = null;
                if (username != null || loginContextName != null || customLoginConfig != null && !customLoginConfig.isEmpty()) {
                    String contextName = loginContextName;
                    if (contextName == null) {
                        contextName = "";
                    }
                    LoginContext loginContext = new LoginContext(contextName, null, getUsernamePasswordHandler(), getLoginConfiguration());
                    loginContext.login();
                    final Oid negotiationOidFinal = negotiationOid;
                    final PrivilegedExceptionAction<GSSCredential> action = () ->
                            manager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME, negotiationOidFinal, GSSCredential.INITIATE_AND_ACCEPT);
                    myCred = Subject.doAs(loginContext.getSubject(), action);
                }
                gssContext = manager.createContext(useCanonicalHostname ? serverName.canonicalize(negotiationOid) : serverName,
                        negotiationOid,
                        myCred,
                        GSSContext.DEFAULT_LIFETIME);
                gssContext.requestMutualAuth(true);
                gssContext.requestCredDeleg(true);
            } catch (GSSException ex) {
                log.error("generateToken", ex);
                // BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH.
                // Rethrow any other exception.
                if (ex.getMajor() == GSSException.BAD_MECH) {
                    log.debug("GSSException BAD_MECH, retry with Kerberos MECH");
                } else {
                    throw ex;
                }

            }
            if (gssContext == null) {
                /* Kerberos v5 GSS-API mechanism defined in RFC 1964. */
                log.debug("Using Kerberos MECH {}", KERBEROS_OID);
                negotiationOid = new Oid(KERBEROS_OID);
                GSSManager manager = GSSManager.getInstance();
                GSSName serverName = manager.createName(spn, GSSName.NT_HOSTBASED_SERVICE);
                gssContext = manager.createContext(serverName.canonicalize(negotiationOid), negotiationOid, null,
                        GSSContext.DEFAULT_LIFETIME);
                gssContext.requestMutualAuth(true);
                gssContext.requestCredDeleg(true);
            }

            // TODO suspicious: this will always be null because no value has been assigned before. Assign directly?
            if (token == null) {
                token = new byte[0];
            }

            token = gssContext.initSecContext(token, 0, token.length);
            if (token == null) {
                throw new SpnegoEngineException("GSS security context initialization failed");
            }

            /*
             * IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish? seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token.
             */
            if (spnegoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) {
                token = spnegoGenerator.generateSpnegoDERObject(token);
            }

            gssContext.dispose();

            String tokenstr = Base64.getEncoder().encodeToString(token);
            log.debug("Sending response '{}' back to the server", tokenstr);

            return tokenstr;
        } catch (GSSException gsse) {
            log.error("generateToken", gsse);
            if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) {
                throw new SpnegoEngineException(gsse.getMessage(), gsse);
            }
            if (gsse.getMajor() == GSSException.NO_CRED) {
                throw new SpnegoEngineException(gsse.getMessage(), gsse);
            }
            if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
                    || gsse.getMajor() == GSSException.OLD_TOKEN) {
                throw new SpnegoEngineException(gsse.getMessage(), gsse);
            }
            // other error
            throw new SpnegoEngineException(gsse.getMessage());
        } catch (IOException | LoginException | PrivilegedActionException ex) {
            throw new SpnegoEngineException(ex.getMessage());
        }
    }
@Test
    public void testSpnegoGenerateTokenWithNullPasswordFail() {
        SpnegoEngine spnegoEngine = new SpnegoEngine("alice",
                null,
                "bob",
                "service.ws.apache.org",
                false,
                null,
                "alice",
                null);
        assertThrows(SpnegoEngineException.class, () -> spnegoEngine.generateToken("localhost"), "No password provided");
    }