diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 815f96f0c5..8afaeb57b5 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -145,6 +145,7 @@ import org.opensearch.security.dlic.rest.api.SecurityRestApiActions; import org.opensearch.security.filter.SecurityFilter; import org.opensearch.security.filter.SecurityRestFilter; +import org.opensearch.security.http.HTTPOnBehalfOfJwtAuthenticator; import org.opensearch.security.http.SecurityHttpServerTransport; import org.opensearch.security.http.SecurityNonSslHttpServerTransport; import org.opensearch.security.http.XFFResolver; @@ -846,6 +847,8 @@ public Collection createComponents(Client localClient, ClusterService cl securityRestHandler = new SecurityRestFilter(backendRegistry, auditLog, threadPool, principalExtractor, settings, configPath, compatConfig); + //TODO: CREATE A INSTANCE OF HTTPExtensionAuthenticationBackend + HTTPOnBehalfOfJwtAuthenticator acInstance = new HTTPOnBehalfOfJwtAuthenticator(); final DynamicConfigFactory dcf = new DynamicConfigFactory(cr, settings, configPath, localClient, threadPool, cih); dcf.registerDCFListener(backendRegistry); @@ -854,6 +857,7 @@ public Collection createComponents(Client localClient, ClusterService cl dcf.registerDCFListener(xffResolver); dcf.registerDCFListener(evaluator); dcf.registerDCFListener(securityRestHandler); + dcf.registerDCFListener(acInstance); if (!(auditLog instanceof NullAuditLog)) { // Don't register if advanced modules is disabled in which case auditlog is instance of NullAuditLog dcf.registerDCFListener(auditLog); diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 635811a7ae..a36aced5e1 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -224,6 +224,8 @@ public boolean authenticate(final RestRequest request, final RestChannel channel HTTPAuthenticator firstChallengingHttpAuthenticator = null; + //TODO: ADD OUR AUTHC BACKEND IN/BEFORE THIS LIST + //loop over all http/rest auth domains for (final AuthDomain authDomain: restAuthDomains) { if (isDebugEnabled) { diff --git a/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java index bdd14c4d69..5328453c98 100644 --- a/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java +++ b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java @@ -166,9 +166,10 @@ public String createJwt(String issuer, String subject, String audience, Integer throw new Exception("The expiration time should be a positive integer"); } + //TODO: IF USER ENABLES THE BWC MODE, WE ARE EXPECTING TO SET PLAIN TEXT ROLE AS `dr` if (roles != null) { String listOfRoles = String.join(",", roles); - jwtClaims.setProperty("roles", EncryptionDecryptionUtil.encrypt(claimsEncryptionKey, listOfRoles)); + jwtClaims.setProperty("er", EncryptionDecryptionUtil.encrypt(claimsEncryptionKey, listOfRoles)); } else { throw new Exception("Roles cannot be null"); } diff --git a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java index 477c2a80d1..ebd2e8d633 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java @@ -124,7 +124,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c if (!checkAndAuthenticateRequest(request, channel, client)) { User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); if (userIsSuperAdmin(user, adminDNs) || (whitelistingSettings.checkRequestIsAllowed(request, channel, client) && allowlistingSettings.checkRequestIsAllowed(request, channel, client))) { - //TODO: If the request is going to the extension, issue a JWT for authenticated user. + //TODO: If the request is going to the ext, issue a JWT for authenticated user. original.handleRequest(request, channel, client); } } diff --git a/src/main/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticator.java new file mode 100644 index 0000000000..1fabd0874c --- /dev/null +++ b/src/main/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticator.java @@ -0,0 +1,272 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.http; + +import java.security.AccessController; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivilegedAction; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Map.Entry; +import java.util.regex.Pattern; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.WeakKeyException; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.greenrobot.eventbus.Subscribe; + +import org.opensearch.OpenSearchSecurityException; +import org.opensearch.SpecialPermission; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; +import org.opensearch.security.auth.HTTPAuthenticator; +import org.opensearch.security.authtoken.jwt.EncryptionDecryptionUtil; +import org.opensearch.security.securityconf.DynamicConfigModel; +import org.opensearch.security.user.AuthCredentials; + +public class HTTPOnBehalfOfJwtAuthenticator implements HTTPAuthenticator { + + protected final Logger log = LogManager.getLogger(this.getClass()); + + private static final Pattern BEARER = Pattern.compile("^\\s*Bearer\\s.*", Pattern.CASE_INSENSITIVE); + private static final String BEARER_PREFIX = "bearer "; + + //TODO: TO SEE IF WE NEED THE FINAL FOR FOLLOWING + private JwtParser jwtParser; + private String subjectKey; + + private String signingKey; + private String encryptionKey; + + public HTTPOnBehalfOfJwtAuthenticator() { + super(); + init(); + } + + // FOR TESTING + public HTTPOnBehalfOfJwtAuthenticator(String signingKey, String encryptionKey){ + this.signingKey = signingKey; + this.encryptionKey = encryptionKey; + init(); + } + + private void init() { + + try { + if(signingKey == null || signingKey.length() == 0) { + log.error("signingKey must not be null or empty. JWT authentication will not work"); + } else { + + signingKey = signingKey.replace("-----BEGIN PUBLIC KEY-----\n", ""); + signingKey = signingKey.replace("-----END PUBLIC KEY-----", ""); + + byte[] decoded = Decoders.BASE64.decode(signingKey); + Key key = null; + + try { + key = getPublicKey(decoded, "RSA"); + } catch (Exception e) { + log.debug("No public RSA key, try other algos ({})", e.toString()); + } + + try { + key = getPublicKey(decoded, "EC"); + } catch (Exception e) { + log.debug("No public ECDSA key, try other algos ({})", e.toString()); + } + + if(key != null) { + jwtParser = Jwts.parser().setSigningKey(key); + } else { + jwtParser = Jwts.parser().setSigningKey(decoded); + } + + } + } catch (Throwable e) { + log.error("Error while creating JWT authenticator", e); + throw new RuntimeException(e); + } + + subjectKey = "sub"; + } + + @Override + @SuppressWarnings("removal") + public AuthCredentials extractCredentials(RestRequest request, ThreadContext context) throws OpenSearchSecurityException { + final SecurityManager sm = System.getSecurityManager(); + + if (sm != null) { + sm.checkPermission(new SpecialPermission()); + } + + AuthCredentials creds = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public AuthCredentials run() { + return extractCredentials0(request); + } + }); + + return creds; + } + + private AuthCredentials extractCredentials0(final RestRequest request) { + if (jwtParser == null) { + log.error("Missing Signing Key. JWT authentication will not work"); + return null; + } + + String jwtToken = request.header(HttpHeaders.AUTHORIZATION); + + if (jwtToken == null || jwtToken.length() == 0) { + if(log.isDebugEnabled()) { + log.debug("No JWT token found in '{}' header", HttpHeaders.AUTHORIZATION); + } + return null; + } + + if (!BEARER.matcher(jwtToken).matches()) { + jwtToken = null; + } + + final int index; + if((index = jwtToken.toLowerCase().indexOf(BEARER_PREFIX)) > -1) { //detect Bearer + jwtToken = jwtToken.substring(index+BEARER_PREFIX.length()); + } else { + if(log.isDebugEnabled()) { + log.debug("No Bearer scheme found in header"); + } + } + + try { + final Claims claims = jwtParser.parseClaimsJws(jwtToken).getBody(); + + final String subject = extractSubject(claims, request); + + final String audience = claims.getAudience(); + + //TODO: GET ROLESCLAIM DEPENDING ON THE STATUS OF BWC MODE. ON: er / OFF: dr + Object rolesObject = null; + String[] roles; + + try { + rolesObject = claims.get("er"); + } catch (Throwable e) { + log.debug("No encrypted role founded in the claim, continue searching for decrypted roles."); + } + + try { + rolesObject = claims.get("dr"); + } catch (Throwable e) { + log.debug("No decrypted role founded in the claim."); + } + + if (rolesObject == null) { + log.warn( + "Failed to get roles from JWT claims. Check if this key is correct and available in the JWT payload."); + roles = new String[0]; + } else { + final String rolesClaim = rolesObject.toString(); + + // Extracting roles based on the compatbility mode + String decryptedRoles = rolesClaim; + if (rolesObject == claims.get("er")) { + //TODO: WHERE TO GET THE ENCRYTION KEY + decryptedRoles = EncryptionDecryptionUtil.decrypt(encryptionKey, rolesClaim); + } + roles = Arrays.stream(decryptedRoles.split(",")).map(String::trim).toArray(String[]::new); + } + + if (subject == null) { + log.error("No subject found in JWT token"); + return null; + } + + if (audience == null) { + log.error("No audience found in JWT token"); + } + + final AuthCredentials ac = new AuthCredentials(subject, roles).markComplete(); + + for(Entry claim: claims.entrySet()) { + ac.addAttribute("attr.jwt."+claim.getKey(), String.valueOf(claim.getValue())); + } + + return ac; + + } catch (WeakKeyException e) { + log.error("Cannot authenticate user with JWT because of ", e); + return null; + } catch (Exception e) { + if(log.isDebugEnabled()) { + log.debug("Invalid or expired JWT token.", e); + } + return null; + } + } + + @Override + public boolean reRequestAuthentication(final RestChannel channel, AuthCredentials creds) { + return false; + } + + @Override + public String getType() { + return "onbehalfof_jwt"; + } + + //TODO: Extract the audience (ext_id) and inject it into thread context + + protected String extractSubject(final Claims claims, final RestRequest request) { + String subject = claims.getSubject(); + if(subjectKey != null) { + // try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException + Object subjectObject = claims.get(subjectKey, Object.class); + if(subjectObject == null) { + log.warn("Failed to get subject from JWT claims, check if subject_key '{}' is correct.", subjectKey); + return null; + } + // We expect a String. If we find something else, convert to String but issue a warning + if(!(subjectObject instanceof String)) { + log.warn("Expected type String in the JWT for subject_key {}, but value was '{}' ({}). Will convert this value to String.", subjectKey, subjectObject, subjectObject.getClass()); + } + subject = String.valueOf(subjectObject); + } + return subject; + } + + private static PublicKey getPublicKey(final byte[] keyBytes, final String algo) throws NoSuchAlgorithmException, InvalidKeySpecException { + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance(algo); + return kf.generatePublic(spec); + } + + @Subscribe + public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { + + //TODO: #2615 FOR CONFIGURATION + //For Testing + signingKey = "abcd1234"; + encryptionKey = RandomStringUtils.randomAlphanumeric(16); + } + +} diff --git a/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java b/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java index 94c93c61da..db69fd02b1 100644 --- a/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java +++ b/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java @@ -68,8 +68,8 @@ public void testCreateJwtWithRoles() throws Exception { Assert.assertNotNull(jwt.getClaim("iat")); Assert.assertNotNull(jwt.getClaim("exp")); Assert.assertEquals(expectedExp, jwt.getClaim("exp")); - Assert.assertNotEquals(expectedRoles, jwt.getClaim("roles")); - Assert.assertEquals(expectedRoles, EncryptionDecryptionUtil.decrypt(claimsEncryptionKey, jwt.getClaim("roles").toString())); + Assert.assertNotEquals(expectedRoles, jwt.getClaim("er")); + Assert.assertEquals(expectedRoles, EncryptionDecryptionUtil.decrypt(claimsEncryptionKey, jwt.getClaim("er").toString())); } @Test (expected = Exception.class) diff --git a/src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java b/src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java new file mode 100644 index 0000000000..5a6c5ec41c --- /dev/null +++ b/src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java @@ -0,0 +1,287 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.http; + +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.crypto.SecretKey; + +import com.google.common.io.BaseEncoding; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.hc.core5.http.HttpHeaders; +import org.junit.Assert; +import org.junit.Test; + +import com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator; + +import org.opensearch.common.settings.Settings; +import org.opensearch.security.user.AuthCredentials; +import org.opensearch.security.util.FakeRestRequest; + +public class HTTPOnBehalfOfJwtAuthenticatorTest { + final static byte[] secretKeyBytes = new byte[1024]; + final static String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16); + final static SecretKey secretKey; + + static { + new SecureRandom().nextBytes(secretKeyBytes); + secretKey = Keys.hmacShaKeyFor(secretKeyBytes); + } + + final static String signingKey = BaseEncoding.base64().encode(secretKeyBytes); + + @Test + public void testNoKey() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + null, + claimsEncryptionKey, + Jwts.builder().setSubject("Leonard McCoy"), + false); + + Assert.assertNull(credentials); + } + + @Test + public void testEmptyKey() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + "", + claimsEncryptionKey, + Jwts.builder().setSubject("Leonard McCoy"), + false); + + Assert.assertNull(credentials); + } + + @Test + public void testBadKey() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + BaseEncoding.base64().encode(new byte[]{1,3,3,4,3,6,7,8,3,10}), + claimsEncryptionKey, + Jwts.builder().setSubject("Leonard McCoy"), + false); + + Assert.assertNull(credentials); + } + + @Test + public void testTokenMissing() throws Exception { + + HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(BaseEncoding.base64().encode(secretKeyBytes),claimsEncryptionKey); + Map headers = new HashMap(); + + AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); + + Assert.assertNull(credentials); + } + + @Test + public void testInvalid() throws Exception { + + String jwsToken = "123invalidtoken.."; + + HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(BaseEncoding.base64().encode(secretKeyBytes), claimsEncryptionKey); + Map headers = new HashMap(); + headers.put("Authorization", "Bearer "+jwsToken); + + AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); + Assert.assertNull(credentials); + } + + @Test + public void testBearer() throws Exception { + + String jwsToken = Jwts.builder().setSubject("Leonard McCoy").setAudience("ext_0").signWith(secretKey, SignatureAlgorithm.HS512).compact(); + + HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(BaseEncoding.base64().encode(secretKeyBytes), claimsEncryptionKey); + Map headers = new HashMap(); + headers.put("Authorization", "Bearer "+jwsToken); + + AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); + + Assert.assertNotNull(credentials); + Assert.assertEquals("Leonard McCoy", credentials.getUsername()); + Assert.assertEquals(0, credentials.getBackendRoles().size()); + Assert.assertEquals(2, credentials.getAttributes().size()); + } + + @Test + public void testBearerWrongPosition() throws Exception { + + Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).build(); + + String jwsToken = Jwts.builder().setSubject("Leonard McCoy").signWith(secretKey, SignatureAlgorithm.HS512).compact(); + + HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); + Map headers = new HashMap(); + headers.put("Authorization", jwsToken + "Bearer " + " 123"); + + AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); + + Assert.assertNull(credentials); + } + + + @Test + public void testBasicAuthHeader() throws Exception { + Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).build(); + HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); + + String basicAuth = BaseEncoding.base64().encode("user:password".getBytes(StandardCharsets.UTF_8)); + Map headers = Collections.singletonMap(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); + + AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, Collections.emptyMap()), null); + Assert.assertNull(credentials); + } + + @Test + public void testRoles() throws Exception { + + List roles = List.of("IT", "HR"); + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + signingKey, + claimsEncryptionKey, + Jwts.builder().setSubject("Leonard McCoy").claim("dr", "role1,role2"), + true); + + Assert.assertNotNull(credentials); + Assert.assertEquals("Leonard McCoy", credentials.getUsername()); + Assert.assertEquals(2, credentials.getBackendRoles().size()); + } + + @Test + public void testNullClaim() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + signingKey, + claimsEncryptionKey, + Jwts.builder().setSubject("Leonard McCoy").claim("dr", null), + false); + + Assert.assertNotNull(credentials); + Assert.assertEquals("Leonard McCoy", credentials.getUsername()); + Assert.assertEquals(0, credentials.getBackendRoles().size()); + } + + @Test + public void testNonStringClaim() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + signingKey, + claimsEncryptionKey, + Jwts.builder().setSubject("Leonard McCoy").claim("dr", 123L), + true); + + Assert.assertNotNull(credentials); + Assert.assertEquals("Leonard McCoy", credentials.getUsername()); + Assert.assertEquals(1, credentials.getBackendRoles().size()); + Assert.assertTrue( credentials.getBackendRoles().contains("123")); + } + + @Test + public void testRolesMissing() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + signingKey, + claimsEncryptionKey, + Jwts.builder().setSubject("Leonard McCoy"), + false); + + Assert.assertNotNull(credentials); + Assert.assertEquals("Leonard McCoy", credentials.getUsername()); + Assert.assertEquals(0, credentials.getBackendRoles().size()); + } + + @Test + public void testWrongSubjectKey() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + signingKey, + claimsEncryptionKey, + Jwts.builder().claim("roles", "role1,role2").claim("asub", "Dr. Who"), + false); + + Assert.assertNull(credentials); + } + + @Test + public void testExp() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + signingKey, + claimsEncryptionKey, + Jwts.builder().setSubject("Expired").setExpiration(new Date(100)), + false); + + Assert.assertNull(credentials); + } + + @Test + public void testNbf() throws Exception { + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + signingKey, + claimsEncryptionKey, + Jwts.builder().setSubject("Expired").setNotBefore(new Date(System.currentTimeMillis()+(1000*36000))), + false); + + Assert.assertNull(credentials); + } + + @Test + public void testRolesArray() throws Exception { + + JwtBuilder builder = Jwts.builder() + .setPayload("{"+ + "\"sub\": \"Cluster_0\","+ + "\"aud\": \"ext_0\","+ + "\"dr\": \"a,b,3rd\""+ + "}"); + + final AuthCredentials credentials = extractCredentialsFromJwtHeader( + signingKey, + claimsEncryptionKey, + builder, + true); + + Assert.assertNotNull(credentials); + Assert.assertEquals("Cluster_0", credentials.getUsername()); + Assert.assertEquals(3, credentials.getBackendRoles().size()); + Assert.assertTrue(credentials.getBackendRoles().contains("a")); + Assert.assertTrue(credentials.getBackendRoles().contains("b")); + Assert.assertTrue(credentials.getBackendRoles().contains("3rd")); + } + + /** extracts a default user credential from a request header */ + private AuthCredentials extractCredentialsFromJwtHeader( + final String signingKey, + final String encryptionKey, + final JwtBuilder jwtBuilder, + final Boolean bwcPluginCompatibilityMode) { + final String jwsToken = jwtBuilder.signWith(secretKey, SignatureAlgorithm.HS512).compact(); + final HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(signingKey, encryptionKey); + final Map headers = Map.of("Authorization", "Bearer " + jwsToken); + return jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap<>()), null); + } +}