In this tutorial, we will learn how to secure a Jersey based REST server implementation using Basic Authentication. As we have already discussed various ways of securing a REST Service, here we look in detail at Basic Authentication.
REST Basic Authentication Tutorial
In Basic Authentication, the client will send user credentials every time data is requested from server. The server takes up authentication information from incoming HTTP request’s authorization header, decodes it and checks whether it is from a valid user. If it is from a valid user, it will respond with the information requested. If it is not a valid user, the server responds with an error saying that the user is unauthorized to access or modify the data requested.
Create POJO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package com.javagists.jersey.model; import java.io.Serializable; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Student implements Serializable{ private static final long serialVersionUID = 90049366623560406L; private int id; private String fullName; private String dept; private int semister; public Student(){} public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public String getDept() { return dept; } public void setDept(String dept) { this.dept = dept; } public int getSemister() { return semister; } public void setSemister(int semister) { this.semister = semister; } } |
Create Controller Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
package com.javagists.jersey.controller; import static com.javagists.jersey.utils.Constants.*; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.Response.ResponseBuilder; import com.javagists.jersey.model.Student; @Path(STUDENTS_URI) public class StudentsController { private static Map<Integer, Student> studentMap = new HashMap<Integer, Student>(); @Context UriInfo uriinfo; @GET @Path(GET_DEFAULT_STUDENT) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Student getDefault(){ Student student; if(studentMap.get(0) != null){ student = studentMap.get(0); } else { student = new Student(); student.setId(0); student.setFullName("Nikola Tesla"); student.setDept("Electrical Engineering"); student.setSemister(5); studentMap.put(0, student); } return student; } @GET @Path(STUDENT_URI) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Student getOneStudent(@PathParam("id") int id){ Student student = null; student = studentMap.get(id); return student; } @GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public List<Student> getAllStudents(){ Collection<Student> students = studentMap.values(); List<Student> returnList = new ArrayList<Student>(); returnList.addAll(students); return returnList; } @POST @Consumes(MediaType.APPLICATION_JSON) public Response addNewStudent(Student student){ Student oldData = null; ResponseBuilder builder; oldData = studentMap.get(student.getId()); if(oldData == null){ studentMap.put(student.getId(), student); StringBuilder newloc = new StringBuilder(); newloc.append(uriinfo.getBaseUri().toString()); newloc.append(STUDENTS); newloc.append(DELIMITTER); newloc.append(student.getId()); URI newuri = URI.create(newloc.toString()); builder = Response.created(newuri); } else { builder = Response.status(409); } return builder.build(); } @PUT @Path(STUDENT_URI) @Consumes(MediaType.APPLICATION_JSON) public Response modifyStudentDetails(@PathParam("id") int id, Student student){ Student oldData = studentMap.get(student.getId()); if(oldData == null){ studentMap.put(student.getId(), student); } else { studentMap.remove(student.getId()); studentMap.put(student.getId(), student); } return Response.noContent().build(); } @DELETE @Path(STUDENT_URI) public Response removeStudent(@PathParam("id") int id){ studentMap.remove(id); return Response.noContent().build(); } } |
Constants.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.javagists.jersey.utils; public interface Constants { String DELIMITTER = "/"; String STUDENTS = "students"; String ID = "{id}"; String DEFAULT = "default"; String STUDENTS_URI = DELIMITTER + STUDENTS; String STUDENT_URI = DELIMITTER +ID; String GET_DEFAULT_STUDENT= DELIMITTER + DEFAULT; String ADMIN = "admin"; String PWD = "test123"; } |
Notice that we are using annotations @GET, @POST, @PUT, @DELETE to perform CRUD operations. javax.ws.rs.core.Response.ResponseBuilder is used in cases when we need to return the right status code.
In our POST call, we embed the location of the newly created student record in the header. This is a standard that is followed widely in various applications so that the REST client can issue a GET request to ensure that the entity has been created properly. While using SoapUI for testing, the location header can be seen in response headers like the following:
The following dependencies need to be added to pom.xml so that xml and json MediaTypes are supported. Note that the first two dependencies are added to avoid errors since we are using Jersey2.0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<properties> <version.jersey>2.25.1</version.jersey> </properties> <dependencies> <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey-server</artifactId> <version>${version.jersey}</version> </dependency> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-servlet</artifactId> <version>${version.jersey}</version> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>${version.jersey}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.8.8</version> </dependency> </dependencies> |
Implementation For The ContainerRequestFilter Interface
We will create a class named AuthFilter that does the authentication by inspecting the authorization headers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package com.javagists.jersey.filter; import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.Provider; @Provider public class AuthFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext containerRequest) throws WebApplicationException { String authCredentials = containerRequest.getHeaderString(HttpHeaders.AUTHORIZATION); AuthService authsvc = new AuthService(); boolean authStatus = authsvc.authenticate(authCredentials); if (!authStatus) { throw new WebApplicationException(Status.UNAUTHORIZED); } } } |
Our AuthService is nothing but a simple class which has a boolean method that validates the data included in authorization headers. The header is sent in the format “Basic <encodedString>” where encoded string is usually encoded using Base64. We decode it to get a string in format “username:password”. We check whether the credentials are proper here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package com.javagists.jersey.filter; import java.io.IOException; import java.util.StringTokenizer; import java.util.Base64; import static com.javagists.jersey.utils.Constants.*; public class AuthService { public boolean authenticate(String authCredentials) { if (null == authCredentials) return false; final String encodedUserPassword = authCredentials.replaceFirst("Basic" + " ", ""); String usernameAndPassword = null; try { byte[] decodedBytes = Base64.getDecoder().decode( encodedUserPassword); usernameAndPassword = new String(decodedBytes, "UTF-8"); } catch (IOException e) { e.printStackTrace(); } final StringTokenizer tokenizer = new StringTokenizer( usernameAndPassword, ":"); final String username = tokenizer.nextToken(); final String password = tokenizer.nextToken(); // the following logic has been written for simplicity. In actual production environment, // corresponding credentials may be obtained from an LDAP server or from the database layer // which deals with user authentication related data. boolean authenticationStatus = ADMIN.equals(username) && PWD.equals(password); return authenticationStatus; } } |
Testing Using Jersey-based REST Client Program
The initial Jersey client created in earlier tutorial can be reused and user credentials can be added in the following manner. This has to be done before creating the WebTarget instance.
1 2 3 |
HttpAuthenticationFeature authDetails = HttpAuthenticationFeature.basic("admin", "test123"); Client client = ClientBuilder.newClient(config); client.register(authDetails); |
In our example, username is admin and password is test123
Testing Using SoapUI
The accept headers can be added as shown:

Credentials for authentication can be added in the following manner via the Auth tab:

Summary
In this tutorial we saw how easy it is to secure a REST Service using Basic Authentication. It may not be the best way of securing a REST Service but still does provide a way to secure a REST Service at a basic level.