diff --git a/jersey/pom.xml b/jersey/pom.xml
index 708b36ce41..0b4d469e3a 100644
--- a/jersey/pom.xml
+++ b/jersey/pom.xml
@@ -32,7 +32,7 @@
org.glassfish.jersey.containers
jersey-container-grizzly2-servlet
- ${jersey.version}
+ ${jersey.version}
org.glassfish.jersey.ext
@@ -44,6 +44,21 @@
jersey-bean-validation
${jersey.version}
+
+ org.glassfish.jersey.security
+ oauth1-client
+ ${jersey.version}
+
+
+ org.glassfish.jersey.security
+ oauth2-client
+ ${jersey.version}
+
+
+ org.glassfish.jersey.media
+ jersey-media-sse
+ ${jersey.version}
+
org.glassfish.jersey.test-framework
jersey-test-framework-core
diff --git a/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java b/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java
new file mode 100644
index 0000000000..ebcbe1d4ab
--- /dev/null
+++ b/jersey/src/main/java/com/baeldung/jersey/client/JerseyClientHeaders.java
@@ -0,0 +1,189 @@
+package com.baeldung.jersey.client;
+
+import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter;
+import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
+import org.glassfish.jersey.client.oauth1.AccessToken;
+import org.glassfish.jersey.client.oauth1.ConsumerCredentials;
+import org.glassfish.jersey.client.oauth1.OAuth1ClientSupport;
+import org.glassfish.jersey.client.oauth2.OAuth2ClientSupport;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.sse.InboundSseEvent;
+import javax.ws.rs.sse.SseEventSource;
+
+import static org.glassfish.jersey.client.authentication.HttpAuthenticationFeature.*;
+
+public class JerseyClientHeaders {
+
+ private static final String BEARER_CONSUMER_SECRET = "my-consumer-secret";
+ private static final String BEARER_ACCESS_TOKEN_SECRET = "my-access-token-secret";
+ private static final String TARGET = "http://localhost:9998/";
+ private static final String MAIN_RESOURCE = "echo-headers";
+ private static final String RESOURCE_AUTH_DIGEST = "digest";
+
+ private static String sseHeaderValue;
+
+ public static Response simpleHeader(String headerKey, String headerValue) {
+ Client client = ClientBuilder.newClient();
+ WebTarget webTarget = client.target(TARGET);
+ WebTarget resourceWebTarget = webTarget.path(MAIN_RESOURCE);
+ Invocation.Builder invocationBuilder = resourceWebTarget.request();
+ invocationBuilder.header(headerKey, headerValue);
+ return invocationBuilder.get();
+ }
+
+ public static Response simpleHeaderFluently(String headerKey, String headerValue) {
+ Client client = ClientBuilder.newClient();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .header(headerKey, headerValue)
+ .get();
+ }
+
+ public static Response basicAuthenticationAtClientLevel(String username, String password) {
+ //To simplify we removed de SSL/TLS protection, but it's required to have an encryption
+ // when using basic authentication schema as it's send only on Base64 encoding
+ HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic(username, password);
+ Client client = ClientBuilder.newBuilder().register(feature).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .get();
+ }
+
+ public static Response basicAuthenticationAtRequestLevel(String username, String password) {
+ //To simplify we removed de SSL/TLS protection, but it's required to have an encryption
+ // when using basic authentication schema as it's send only on Base64 encoding
+ HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder().build();
+ Client client = ClientBuilder.newBuilder().register(feature).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .property(HTTP_AUTHENTICATION_BASIC_USERNAME, username)
+ .property(HTTP_AUTHENTICATION_BASIC_PASSWORD, password)
+ .get();
+ }
+
+ public static Response digestAuthenticationAtClientLevel(String username, String password) {
+ HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest(username, password);
+ Client client = ClientBuilder.newBuilder().register(feature).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .path(RESOURCE_AUTH_DIGEST)
+ .request()
+ .get();
+ }
+
+ public static Response digestAuthenticationAtRequestLevel(String username, String password) {
+ HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest();
+
+ Client client = ClientBuilder.newBuilder().register(feature).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .path(RESOURCE_AUTH_DIGEST)
+ .request()
+ .property(HTTP_AUTHENTICATION_DIGEST_USERNAME, username)
+ .property(HTTP_AUTHENTICATION_DIGEST_PASSWORD, password)
+ .get();
+ }
+
+ public static Response bearerAuthenticationWithOAuth1AtClientLevel(String token, String consumerKey) {
+ ConsumerCredentials consumerCredential = new ConsumerCredentials(consumerKey, BEARER_CONSUMER_SECRET);
+ AccessToken accessToken = new AccessToken(token, BEARER_ACCESS_TOKEN_SECRET);
+ Feature feature = OAuth1ClientSupport
+ .builder(consumerCredential)
+ .feature()
+ .accessToken(accessToken)
+ .build();
+
+ Client client = ClientBuilder.newBuilder().register(feature).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .get();
+ }
+
+ public static Response bearerAuthenticationWithOAuth1AtRequestLevel(String token, String consumerKey) {
+ ConsumerCredentials consumerCredential = new ConsumerCredentials(consumerKey, BEARER_CONSUMER_SECRET);
+ AccessToken accessToken = new AccessToken(token, BEARER_ACCESS_TOKEN_SECRET);
+ Feature feature = OAuth1ClientSupport
+ .builder(consumerCredential)
+ .feature()
+ .build();
+
+ Client client = ClientBuilder.newBuilder().register(feature).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .property(OAuth1ClientSupport.OAUTH_PROPERTY_ACCESS_TOKEN, accessToken)
+ .get();
+ }
+
+ public static Response bearerAuthenticationWithOAuth2AtClientLevel(String token) {
+ Feature feature = OAuth2ClientSupport.feature(token);
+
+ Client client = ClientBuilder.newBuilder().register(feature).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .get();
+ }
+
+ public static Response bearerAuthenticationWithOAuth2AtRequestLevel(String token, String otherToken) {
+ Feature feature = OAuth2ClientSupport.feature(token);
+
+ Client client = ClientBuilder.newBuilder().register(feature).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .property(OAuth2ClientSupport.OAUTH2_PROPERTY_ACCESS_TOKEN, otherToken)
+ .get();
+ }
+
+ public static Response filter() {
+ Client client = ClientBuilder.newBuilder().register(AddHeaderOnRequestFilter.class).build();
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .get();
+ }
+
+ public static Response sendRestrictedHeaderThroughDefaultTransportConnector(String headerKey, String headerValue) {
+ Client client = ClientBuilder.newClient();
+ System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
+
+ return client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .request()
+ .header(headerKey, headerValue)
+ .get();
+ }
+
+ public static String simpleSSEHeader() throws InterruptedException {
+ Client client = ClientBuilder.newBuilder()
+ .register(AddHeaderOnRequestFilter.class)
+ .build();
+
+ WebTarget webTarget = client.target(TARGET)
+ .path(MAIN_RESOURCE)
+ .path("events");
+
+ SseEventSource sseEventSource = SseEventSource.target(webTarget).build();
+ sseEventSource.register(JerseyClientHeaders::receiveEvent);
+ sseEventSource.open();
+ Thread.sleep(3_000);
+ sseEventSource.close();
+
+ return sseHeaderValue;
+ }
+
+ private static void receiveEvent(InboundSseEvent event) {
+ sseHeaderValue = event.readData();
+ }
+}
diff --git a/jersey/src/main/java/com/baeldung/jersey/client/filter/AddHeaderOnRequestFilter.java b/jersey/src/main/java/com/baeldung/jersey/client/filter/AddHeaderOnRequestFilter.java
new file mode 100644
index 0000000000..a874928c16
--- /dev/null
+++ b/jersey/src/main/java/com/baeldung/jersey/client/filter/AddHeaderOnRequestFilter.java
@@ -0,0 +1,16 @@
+package com.baeldung.jersey.client.filter;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import java.io.IOException;
+
+public class AddHeaderOnRequestFilter implements ClientRequestFilter {
+
+ public static final String FILTER_HEADER_VALUE = "filter-header-value";
+ public static final String FILTER_HEADER_KEY = "x-filter-header";
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.getHeaders().add(FILTER_HEADER_KEY, FILTER_HEADER_VALUE);
+ }
+}
diff --git a/jersey/src/main/java/com/baeldung/jersey/server/EchoHeaders.java b/jersey/src/main/java/com/baeldung/jersey/server/EchoHeaders.java
new file mode 100644
index 0000000000..a8df3c10a8
--- /dev/null
+++ b/jersey/src/main/java/com/baeldung/jersey/server/EchoHeaders.java
@@ -0,0 +1,72 @@
+package com.baeldung.jersey.server;
+
+import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.*;
+import javax.ws.rs.sse.OutboundSseEvent;
+import javax.ws.rs.sse.Sse;
+import javax.ws.rs.sse.SseEventSink;
+
+@Path("/echo-headers")
+public class EchoHeaders {
+
+ static final String REALM_KEY = "realm";
+ static final String REALM_VALUE = "Baeldung";
+ static final String QOP_KEY = "qop";
+ static final String QOP_VALUE = "auth";
+ static final String NONCE_KEY = "nonce";
+ static final String NONCE_VALUE = "dcd98b7102dd2f0e8b11d0f600bfb0c093";
+ static final String OPAQUE_KEY = "opaque";
+ static final String OPAQUE_VALUE = "5ccc069c403ebaf9f0171e9517f40e41";
+ static final String SSE_HEADER_KEY = "x-sse-header-key";
+
+ @Context
+ HttpHeaders headers;
+
+ @GET
+ public Response getHeadersBack() {
+ return echoHeaders();
+ }
+
+ @RolesAllowed("ADMIN")
+ @GET
+ @Path("/digest")
+ public Response getHeadersBackFromDigestAuthentication() {
+ // As the Digest authentication require some complex steps to work we'll simulate the process
+ // https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation
+ if (headers.getHeaderString("authorization") == null) {
+ String authenticationRequired = "Digest " + REALM_KEY + "=\"" + REALM_VALUE + "\", " + QOP_KEY + "=\"" + QOP_VALUE + "\", " + NONCE_KEY + "=\"" + NONCE_VALUE + "\", " + OPAQUE_KEY + "=\"" + OPAQUE_VALUE + "\"";
+ return Response.status(Response.Status.UNAUTHORIZED)
+ .header("WWW-Authenticate", authenticationRequired)
+ .build();
+ } else {
+ return echoHeaders();
+ }
+ }
+
+ @GET
+ @Path("/events")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void getServerSentEvents(@Context SseEventSink eventSink, @Context Sse sse) {
+ OutboundSseEvent event = sse.newEventBuilder()
+ .name("echo-headers")
+ .data(String.class, headers.getHeaderString(AddHeaderOnRequestFilter.FILTER_HEADER_KEY))
+ .build();
+ eventSink.send(event);
+ }
+
+ private Response echoHeaders() {
+ Response.ResponseBuilder responseBuilder = Response.noContent();
+
+ headers.getRequestHeaders()
+ .forEach((k, v) -> {
+ v.forEach(value -> responseBuilder.header(k, value));
+ });
+
+ return responseBuilder.build();
+ }
+}
diff --git a/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java b/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java
new file mode 100644
index 0000000000..ac2cc2c4eb
--- /dev/null
+++ b/jersey/src/test/java/com/baeldung/jersey/server/EchoHeadersUnitTest.java
@@ -0,0 +1,197 @@
+package com.baeldung.jersey.server;
+
+import com.baeldung.jersey.client.JerseyClientHeaders;
+import com.baeldung.jersey.client.filter.AddHeaderOnRequestFilter;
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class EchoHeadersUnitTest extends JerseyTest {
+
+ private static final String SIMPLE_HEADER_KEY = "my-header-key";
+ private static final String SIMPLE_HEADER_VALUE = "my-header-value";
+ private static final String USERNAME = "baeldung";
+ private static final String PASSWORD = "super-secret";
+ private static final String AUTHORIZATION_HEADER_KEY = "authorization";
+ private static final String BEARER_TOKEN_VALUE = "my-token";
+ private static final String BEARER_CONSUMER_KEY_VALUE = "my-consumer-key";
+ private static final String BEARER_REQUEST_TOKEN_VALUE = "my-request-token";
+
+ @Test
+ public void whenCallingSimpleHeader_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.simpleHeader(SIMPLE_HEADER_KEY, SIMPLE_HEADER_VALUE);
+
+ assertEquals(response.getHeaderString(SIMPLE_HEADER_KEY), SIMPLE_HEADER_VALUE);
+ }
+
+ @Test
+ public void whenCallingSimpleHeaderFluently_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.simpleHeaderFluently(SIMPLE_HEADER_KEY, SIMPLE_HEADER_VALUE);
+
+ assertEquals(response.getHeaderString(SIMPLE_HEADER_KEY), SIMPLE_HEADER_VALUE);
+ }
+
+ @Test
+ public void whenCallingBasicAuthenticationAtClientLevel_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.basicAuthenticationAtClientLevel(USERNAME, PASSWORD);
+
+ assertBasicAuthenticationHeaders(response);
+ }
+
+ @Test
+ public void whenCallingBasicAuthenticationAtRequestLevel_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.basicAuthenticationAtRequestLevel(USERNAME, PASSWORD);
+
+ assertBasicAuthenticationHeaders(response);
+ }
+
+ @Test
+ public void whenCallingDigestAuthenticationAtClientLevel_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.digestAuthenticationAtClientLevel(USERNAME, PASSWORD);
+
+ Map subHeadersMap = parseAuthenticationSubHeader(response, 7);
+
+ assertDigestAuthenticationHeaders(subHeadersMap);
+ }
+
+ @Test
+ public void whenCallingDigestAuthenticationAtRequestLevel_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.digestAuthenticationAtRequestLevel(USERNAME, PASSWORD);
+
+ Map subHeadersMap = parseAuthenticationSubHeader(response, 7);
+
+ assertDigestAuthenticationHeaders(subHeadersMap);
+ }
+
+ @Test
+ public void whenCallingBearerAuthenticationWithOAuth1AtClientLevel_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.bearerAuthenticationWithOAuth1AtClientLevel(BEARER_TOKEN_VALUE, BEARER_CONSUMER_KEY_VALUE);
+
+ Map subHeadersMap = parseAuthenticationSubHeader(response, 6);
+
+ assertBearerAuthenticationHeaders(subHeadersMap);
+ }
+
+ @Test
+ public void whenCallingBearerAuthenticationWithOAuth1AtRequestLevel_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.bearerAuthenticationWithOAuth1AtRequestLevel(BEARER_TOKEN_VALUE, BEARER_CONSUMER_KEY_VALUE);
+
+ Map subHeadersMap = parseAuthenticationSubHeader(response, 6);
+
+ assertBearerAuthenticationHeaders(subHeadersMap);
+ }
+
+ @Test
+ public void whenCallingBearerAuthenticationWithOAuth2AtClientLevel_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.bearerAuthenticationWithOAuth2AtClientLevel(BEARER_TOKEN_VALUE);
+
+ assertEquals("Bearer " + BEARER_TOKEN_VALUE, response.getHeaderString(AUTHORIZATION_HEADER_KEY));
+ }
+
+ @Test
+ public void whenCallingBearerAuthenticationWithOAuth2AtRequestLevel_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.bearerAuthenticationWithOAuth2AtRequestLevel(BEARER_TOKEN_VALUE, BEARER_REQUEST_TOKEN_VALUE);
+
+ assertEquals("Bearer " + BEARER_REQUEST_TOKEN_VALUE, response.getHeaderString(AUTHORIZATION_HEADER_KEY));
+ }
+
+ @Test
+ public void whenCallingFilter_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.filter();
+
+ assertEquals(AddHeaderOnRequestFilter.FILTER_HEADER_VALUE, response.getHeaderString(AddHeaderOnRequestFilter.FILTER_HEADER_KEY));
+ }
+
+ @Test
+ public void whenCallingSendRestrictedHeaderThroughDefaultTransportConnector_thenHeadersReturnedBack() {
+ Response response = JerseyClientHeaders.sendRestrictedHeaderThroughDefaultTransportConnector("keep-alive", "keep-alive-value");
+
+ assertEquals("keep-alive-value", response.getHeaderString("keep-alive"));
+ }
+
+ @Test
+ public void whenCallingSimpleSSEHeader_thenHeadersReturnedBack() throws InterruptedException {
+ String sseHeaderBackValue = JerseyClientHeaders.simpleSSEHeader();
+
+ assertEquals(AddHeaderOnRequestFilter.FILTER_HEADER_VALUE, sseHeaderBackValue);
+ }
+
+ private void assertBearerAuthenticationHeaders(Map subHeadersMap) {
+
+ assertEquals(BEARER_TOKEN_VALUE, subHeadersMap.get("oauth_token"));
+ assertEquals(BEARER_CONSUMER_KEY_VALUE, subHeadersMap.get("oauth_consumer_key"));
+ assertNotNull(subHeadersMap.get("oauth_nonce"));
+ assertNotNull(subHeadersMap.get("oauth_signature"));
+ assertNotNull(subHeadersMap.get("oauth_callback"));
+ assertNotNull(subHeadersMap.get("oauth_signature_method"));
+ assertNotNull(subHeadersMap.get("oauth_version"));
+ assertNotNull(subHeadersMap.get("oauth_timestamp"));
+ }
+
+ private void assertDigestAuthenticationHeaders(Map subHeadersMap) {
+ assertEquals(EchoHeaders.NONCE_VALUE, subHeadersMap.get(EchoHeaders.NONCE_KEY));
+ assertEquals(EchoHeaders.OPAQUE_VALUE, subHeadersMap.get(EchoHeaders.OPAQUE_KEY));
+ assertEquals(EchoHeaders.QOP_VALUE, subHeadersMap.get(EchoHeaders.QOP_KEY));
+ assertEquals(EchoHeaders.REALM_VALUE, subHeadersMap.get(EchoHeaders.REALM_KEY));
+
+ assertEquals(USERNAME, subHeadersMap.get("username"));
+ assertEquals("/echo-headers/digest", subHeadersMap.get("uri"));
+ assertNotNull(subHeadersMap.get("cnonce"));
+ assertEquals("00000001", subHeadersMap.get("nc"));
+ assertNotNull(subHeadersMap.get("response"));
+ }
+
+ private Map parseAuthenticationSubHeader(Response response, int startAt) {
+ String authorizationHeader = response.getHeaderString(AUTHORIZATION_HEADER_KEY);
+ // The substring(startAt) is used to cut off the authentication schema part from the value returned.
+ String[] subHeadersKeyValue = authorizationHeader.substring(startAt).split(",");
+ Map subHeadersMap = new HashMap<>();
+
+ for (String subHeader : subHeadersKeyValue) {
+ String[] keyValue = subHeader.split("=");
+
+ if (keyValue[1].startsWith("\"")) {
+ keyValue[1] = keyValue[1].substring(1, keyValue[1].length() - 1);
+ }
+
+ subHeadersMap.put(keyValue[0].trim(), keyValue[1].trim());
+ }
+ return subHeadersMap;
+ }
+
+ private void assertBasicAuthenticationHeaders(Response response) {
+ String base64Credentials = response.getHeaderString(AUTHORIZATION_HEADER_KEY);
+ // The substring(6) is used to cut the "Basic " part of the value returned,
+ // as it's used to indicates the authentication schema and does not belong to the credentials
+ byte[] credentials = Base64.getDecoder().decode(base64Credentials.substring(6));
+ String[] credentialsParsed = new String(credentials).split(":");
+
+ assertEquals(credentialsParsed[0], USERNAME);
+ assertEquals(credentialsParsed[1], PASSWORD);
+ }
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig()
+ .register(EchoHeaders.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ // We need this definition here, because if you are running
+ // the complete suit test the sendingRestrictedHeaderThroughDefaultTransportConnector_shouldReturnThanBack
+ // will fail if only defined on the client method, since the JerseyTest is created once.
+ System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
+ }
+}
\ No newline at end of file