SpnegoAuthenticator extends AbstractAuthenticationBackend { public String authenticateUser(String authorizationHeader) { if (logins.isEmpty()) { initLogin(); } if (! authorizationHeader.startsWith("Negotiate ")) { throw Status.UNAUTHENTICATED.withDescription("Use \"authorization: Negotiate <token>\" metadata to authenticate").asRuntimeException(); } String spnegoToken = authorizationHeader.split("\\s")[1]; String authenticatedUser = null; for (Subject login : logins) { authenticatedUser = Subject.doAs(login, (PrivilegedAction<String>) () -> { try { GSSManager manager = GSSManager.getInstance(); Oid spnegoOid = new Oid("1.3.6.1.5.5.2"); GSSCredential serverCredential = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, spnegoOid, GSSCredential.ACCEPT_ONLY); GSSContext context = manager.createContext(serverCredential); byte[] tokenBytes = Base64.getDecoder().decode(spnegoToken.getBytes()); context.acceptSecContext(tokenBytes, 0, tokenBytes.length); return context.getSrcName().toString(); } catch (GSSException e) { return null; } }); if (authenticatedUser != null) { return authenticatedUser; } } throw Status.UNAUTHENTICATED.withDescription("SPNEGO authentication failed").asRuntimeException(); }  String authenticateUser(String authorizationHeader);  }
@Test public void testInexistentKeytabPath() throws Exception { List<Map<String, String>> config = List.of(Map.of( "keytab", "/home/does-not-exist", "principal", "blah" )); try (SettingsOverride override = SettingsOverride.apply(Map.of(AppSettings.KEYTABS, config))) { try { SpnegoAuthenticator auth = new SpnegoAuthenticator(); auth.authenticateUser(); fail(); } catch (IllegalArgumentException e) { assertEquals("Keytab `/home/does-not-exist` in `authentication.spnego.keytabs` setting does not exist", e.getMessage()); } } }
@Test public void testMissingValue() throws Exception { List<List<Map<String, String>>> configs = List.of( List.of(Map.of("keytab", "bar")), List.of(Map.of("principal", "bar")) ); for (List<Map<String, String>> config: configs) { try (SettingsOverride override = SettingsOverride.apply(Map.of(AppSettings.KEYTABS, config))) { try { SpnegoAuthenticator auth = new SpnegoAuthenticator(); auth.authenticateUser(); fail(); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().startsWith("Invalid `authentication.spnego.keytabs` setting -- Error: hardcoded value: No configuration setting found for key")); } } } }
@Test public void testInvalidKeytab() throws Exception { Path fakeKeytab = Files.createTempFile("fake", ".keytab"); List<Map<String, String>> config = List.of(Map.of( "keytab", fakeKeytab.toString(), "principal", "blah" )); try (SettingsOverride override = SettingsOverride.apply(Map.of(AppSettings.KEYTABS, config))) { try { String token = generateSpnegoToken("alice"); SpnegoAuthenticator auth = new SpnegoAuthenticator(); auth.authenticateUser("Negotiate " + token); fail(); } catch (StatusRuntimeException e) { assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); assertEquals("UNAUTHENTICATED: SPNEGO authentication failed", e.getMessage()); } } }
@Test public void testHeaderDoesntStartWithNegotiate() throws Exception { List<Map<String, String>> config = List.of(Map.of( "keytab", fakeKDC.getKeytabPath(BROKER).toString(), "principal", BROKER )); try (SettingsOverride override = SettingsOverride.apply(Map.of(AppSettings.KEYTABS, config))) { SpnegoAuthenticator auth = new SpnegoAuthenticator(); try { auth.authenticateUser("xxx"); fail(); } catch (StatusRuntimeException e) { assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); assertEquals("UNAUTHENTICATED: Use \"authorization: Negotiate <token>\" metadata to authenticate", e.getMessage()); } } }
@Test public void testInvalidSpnegoToken() throws Exception { List<Map<String, String>> config = List.of(Map.of( "keytab", fakeKDC.getKeytabPath(BROKER).toString(), "principal", BROKER )); try (SettingsOverride override = SettingsOverride.apply(Map.of(AppSettings.KEYTABS, config))) { SpnegoAuthenticator auth = new SpnegoAuthenticator(); try { auth.authenticateUser("Negotiate xxx"); fail(); } catch (StatusRuntimeException e) { assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); assertEquals("UNAUTHENTICATED: SPNEGO authentication failed", e.getMessage()); } } }
@Test public void testSuccess() throws Exception { List<Map<String, String>> config = List.of(Map.of( "keytab", fakeKDC.getKeytabPath(BROKER).toString(), "principal", BROKER )); try (SettingsOverride override = SettingsOverride.apply(Map.of(AppSettings.KEYTABS, config))) { String token = generateSpnegoToken("alice"); SpnegoAuthenticator auth = new SpnegoAuthenticator(); String authenticateUser = auth.authenticateUser("Negotiate " + token); assertEquals("alice@EXAMPLE.COM", authenticateUser); } }