Building JAX-RS clients with basic authentication

A normal client-server transaction can follow two scenarios of basic authentication. On one hand, a client submits a request to the server without any authentication credentials (as depicted in the previous sequence diagram). On the other hand, a client submits a request to the server with the authentication credentials. Let's take a closer look at these two scenarios.

When a client submits a request without the authentication credentials, the server responds back stating unauthorized access with an HTTP error code of 401. If the request is executed from a web browser, users see the ubiquitous Authentication Required browser popup, as shown here:

Users can then supply the valid credentials to complete the request. Note that the web browser keeps track of the 401 response and is charged with sending the proper authentication credentials back with the original URI. This makes the transaction seamless for the users. Now, had we been using a client rather than a web browser, we would have needed to programmatically intercept the 401 response and then provide the valid credentials to complete the original request.

The second scenario that comes up while using HTTP basic authentication is when we do not wait for a server's 401 response but provide the authentication credentials at the beginning itself. As we said, we provide the credentials in the HTTP header authorization. The APIs for setting the HTTP headers vary with the client frameworks that you use. The following JAX-RS client example uses the javax.ws.rs.client.ClientRequestFilter implementation to associate the HTTP header authorization with each request. If you need a quick brush up on ClientRequestFilter, refer to the Modifying request and response parameters with JAX-RS filters section, in Chapter 4, Advanced Features in the JAX-RS API:

//Other imports are removed for brevity 
import java.util.Base64; 
import javax.ws.rs.client.ClientRequestContext; 
import javax.ws.rs.client.ClientRequestFilter; 
import javax.ws.rs.core.MultivaluedMap; 
import javax.ws.rs.ext.Provider; 
 
@Provider 
public class ClientAuthenticationFilter implements  
    ClientRequestFilter { 
    
private static final Logger logger =
Logger.getLogger(ClientAuthenticationFilter.class.getName()); private final String user; private final String password; public ClientAuthenticationFilter(String user, String password) { this.user = user; this.password = password; } //The filter() method is called before a request has been //dispatched to a client transport layer. //This method is used in this example for attaching user //name-password token with authorization header public void filter(ClientRequestContext requestContext) throws IOException { MultivaluedMap<String, Object> headers = requestContext.getHeaders(); final String basicAuthentication = getBasicAuthentication(); headers.add("Authorization", basicAuthentication); for (String header : headers.keySet()) {
logger.log(Level.INFO, "Authentication Header:{0}-{1}",
new Object[]{header,headers.get(header)});
} } //Return BASE64 encoded username and password private String getBasicAuthentication() { String token = this.user + ":" + this.password; try { byte[] encoded = Base64.getEncoder().encode( token.getBytes("UTF-8")); return "BASIC " + new String(encoded); } catch (UnsupportedEncodingException ex) { throw new IllegalArgumentException( "Cannot encode with UTF-8", ex); } } }

The next step is to register the preceding filter implementation with the JAX-RS client code that makes the REST API call. The following code snippet illustrates how to accomplish this step. This example invokes a protected RESTful web service URI, /hr/departments, to fetch the departments in an enterprise. The client sets the HTTP basic authentication header via ClientAuthenticationFilter:

//JAX-RS REST client generated for REST resource:HRService
//to demonstrate usage of ClientAuthenticationFilter public class HRServiceBasicAuthClient {

private WebTarget webTarget;
private Client client;
//Port#15647 can vary based on the http listener port of the server
private static final String BASE_URI = "http://localhost:15647/rest-
chapter6-service/webresources";
private static final Logger logger =
Logger.getLogger(HRServiceBasicAuthClient.class.getName());


public HRServiceBasicAuthClient() {

client = javax.ws.rs.client.ClientBuilder.newClient();
webTarget = client.target(BASE_URI).path("hr").path("departments");
//Pass the credentials to Auth Filter
ClientAuthenticationFilter filter = new
ClientAuthenticationFilter("hr", "hr");
webTarget.register(filter);
}

public <T> T findAll(GenericType<T> responseType) throws
ClientErrorException {

return
webTarget.request(javax.ws.rs.core.MediaType.APPLICATION_JSON).
get(responseType);
}

public void disconnect() {
client.close();
}

public static void main(String arg[]) {

HRServiceBasicAuthClient hrServiceClient =
new HRServiceBasicAuthClient();
List<Department> depts = hrServiceClient.findAll(
new GenericType<List<Department>>() { });
logger.log(Level.INFO, depts.toString());
hrServiceClient.disconnect();
}

}

The output of preceding program is as follows:

com.packtpub.rest.ch6.filter.ClientAuthenticationFilter filter
INFO: Authentication Header:Accept-[application/json]
com.packtpub.rest.ch6.filter.ClientAuthenticationFilter filter
INFO: Authentication Header:Authorization-[BASIC dGVzdDE6dGVzdDE=]
com.packtpub.rest.ch6.client.HRServiceBasicAuthClient main
INFO: [com.packtpub.rest.ch6.model.Department[ departmentId=10 ], com.packtpub.rest.ch6.model.Department[ departmentId=20 ], com.packtpub.rest.ch6.model.Department[ departmentId=30 ], com.packtpub.rest.ch6.model.Department[ departmentId=40 ], com.packtpub.rest.ch6.model.Department[ departmentId=50 ], com.packtpub.rest.ch6.model.Department[ departmentId=60 ], com.packtpub.rest.ch6.model.Department[ departmentId=70 ], com.packtpub.rest.ch6.model.Department[ departmentId=80 ], com.packtpub.rest.ch6.model.Department[ departmentId=90 ], com.packtpub.rest.ch6.model.Department[ departmentId=100 ], com.packtpub.rest.ch6.model.Department[ departmentId=110 ], com.packtpub.rest.ch6.model.Department[ departmentId=120 ], com.packtpub.rest.ch6.model.Department[ departmentId=130 ], com.packtpub.rest.ch6.model.Department[ departmentId=140 ], com.packtpub.rest.ch6.model.Department[ departmentId=150 ], com.packtpub.rest.ch6.model.Department[ departmentId=160 ], com.packtpub.rest.ch6.model.Department[ departmentId=170 ], com.packtpub.rest.ch6.model.Department[ departmentId=180 ], com.packtpub.rest.ch6.model.Department[ departmentId=190 ], com.packtpub.rest.ch6.model.Department[ departmentId=200 ], com.packtpub.rest.ch6.model.Department[ departmentId=210 ], com.packtpub.rest.ch6.model.Department[ departmentId=220 ], com.packtpub.rest.ch6.model.Department[ departmentId=230 ], com.packtpub.rest.ch6.model.Department[ departmentId=240 ], com.packtpub.rest.ch6.model.Department[ departmentId=250 ], com.packtpub.rest.ch6.model.Department[ departmentId=260 ], com.packtpub.rest.ch6.model.Department[ departmentId=270 ]]

The JAX-RS client runtime invokes all the registered filters before the request has been dispatched to the transport layer. In this example, ClientAuthenticationFilter adds the HTTP authentication header to each request made by the client. Because we are sending the authentication credentials in the first invocation itself, we do not expect a 401 response from the server, and the API call will proceed as any other request.

For the other HTTP request types, such as POST, PUT, and DELETE, the client request process cycle follows the same pattern as shown earlier. The client runtime sets the credentials and proceeds with the request and consumption of the response. Finally, every request type must include the authentication credentials, or else we will have to programmatically handle the server's 401 response. If the client knows the type of authentication required by the target API in advance, it makes sense to attach the necessary authentication content with the very first request itself, which will help you in avoiding an extra round trip that would otherwise be required for authentication.

For more details on HTTP basic authentication, see the page at http://www.ietf.org/rfc/rfc2617.txt.

In the next section, we will learn how to secure a JAX-RS RESTful web service application deployed on a server, such as the GlassFish server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.35.255