[OAuth2.0] Building an OAuth2.0 Authentication Server with ScribeJAVA - 9. Providing RESTful API Services with Jersey
[OAuth2.0] Building an OAuth2.0 Authentication Server with ScribeJAVA - 9. Providing RESTful API Services with Jersey
This chapter covers how to provide RESTful API services using Jersey. Since this project receives and responds to requests through Jersey, we'll briefly cover Jersey before setting up the controllers.
Typically, the Spring framework is widely used to build JAVA servers. Still, the reason I chose Jersey instead is, first of all, that I don't know Spring very well. But beyond that, compared to Spring, it's smaller in scale and simpler to configure, letting me focus entirely on building a RESTful server. Not to mention how vicious Spring's configuration can be, this project doesn't require complex logic or a wide variety of features by its nature. Since we wouldn't be using Spring's vast scale to its full extent, the tail would be wagging the dog.
Make sure to use Tomcat version 10 or higher. As mentioned repeatedly in the previous chapter, Jersey 3.x only uses jakarta.*, which is Servlet 5.0. It doesn't support anything below Servlet 4.x, so no matter how spec-compliant your request is, all you'll get is a 404. Tomcat only provides this starting from version 10, so be careful. I lost several hours to this issue.
Servlet 5.0?
Starting with Servlet 5.0, the jakarta.* package is used. Servlet 4.x and below, which we've used until now, uses the javax.* package. Aside from the package name change, the usage is completely identical. Migration is complete simply by changing javax.* to jakarta.*.
Let's set up Jersey 3 in the project.
The explanation is based on Gradle. Add the following to the dependencies section of build.gradle.
TXT
dependencies { // https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '5.0.0' // https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-server implementation group: 'org.glassfish.jersey.core', name: 'jersey-server', version: '3.0.3' // https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet implementation group: 'org.glassfish.jersey.containers', name: 'jersey-container-servlet', version: '3.0.3' // https://mvnrepository.com/artifact/org.glassfish.jersey.inject/jersey-hk2 implementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '3.0.3' // https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '3.0.3' // other libraries... }
- jakarta.servlet-api - Servlet 5.0
- jersey-server - Jersey server core implementation
- jersey-container-servlet - Jersey's Servlet implementation
- jersey-hk2 - HK2 InjectionManager implementation
- jersey-media-json-jackson - A module that connects Jersey's response object with a JSON provider using Jackson
That's what each library means. Of these, jersey-media-json-jackson is a plugin, and this provider plugin is required in order to respond in JSON format. There are many other useful plugins as well, but for this project, these four are enough to use Jersey.
Let's specify Jersey's request URL. What this means is that we're specifying the top-level URL that Jersey will handle.
For example, if you specify /api, requests starting with https://example.com/api will be delegated to Jersey instead of a regular Servlet.
There's a traditional way to specify this in web.xml, but it's rather cumbersome and not recommended. We'll implement this at the server level instead.
JAVA
@ApplicationPath("/api") public class App extends Application { // Requests with the api prefix are handled by jersey }
Specify it as shown above. Just create a class and have it extend Application. Then specify the desired path prefix in @ApplicationPath.
The above configuration delegates paths starting with /api to Jersey.
With just the configuration above, Jersey is ready to use. Now let's design a RESTful API and receive a response.
Create an arbitrary class and specify it as shown below. The class name can be anything you want.
JAVA
@Path("/userinfo") public class TestAPI { // /api/userinfo API }
Specify the desired path in @Path. The class above becomes an API class that handles the /api/userinfo request.
JAVA
@Path("/userinfo") public class TestAPI { @GET @Path("") public String testResponse() { return "It's Worked!"; } }
Let's create a method as shown above. A GET request to /api/userinfo will return "It's Worked!" via the testResponse method, which will be displayed in the browser. Besides @GET, various other HTTP methods such as @POST and @PUT are also supported, so configure them as needed.
@Path likewise specifies the URL to receive requests on. Note that the parent @Path values are prepended to the URL prefix in order. Using this pattern, you can create a variety of RESTful APIs.
JAVA
@Path("/userinfo") public class TestAPI { @GET @Path("") public String testResponse() { return "It's Worked!"; } @GET @Path("/{id}") public String userinfoResponse(@PathParam("id") String id) { return "{ key1: \"value1\", key2: \"value2\", id: \"" + id + "\" }"; } @POST @Path("/{hash}") public String useraddResponse(@PathParam("hash") String hash, @FormParam("key") String key) { return hash + key; } @GET @Path("/check") public boolean usercheckResponse(@QueryParam("id") String id, @QueryParam("key") String key) { return true; } @DELETE @Path("/remove") public int userremoveResponse(@CookieParam("auth") String auth) { return 1; } }
Let's design a variety of RESTful APIs like this. You can return various kinds of values.
| Annotation | Description |
|---|---|
| @PathParam | Assigned the value of the specified URL. Entered as /{key} in @Path |
| @QueryParam | Assigned the URL parameter with the specified key |
| @FormParam | Assigned the specified body parameter |
| @CookieParam | Assigned the Cookie with the specified key |
| @HeaderParam | Assigned the Header with the specified key |
Using these annotations, you can easily receive the desired elements as arguments.
| Method | HTTP | Target URL |
|---|---|---|
| testResponse | GET | /api/userinfo |
| userinfoResponse | GET | /api/userinfo/{id} |
| useraddResponse | POST | /api/userinfo/{hash} |
| usercheckResponse | GET | /api/userinfo/check?id={id}&key={key} |
| userremoveResponse | DELETE | /api/userinfo/remove |
The URL matching each method is as shown in the table above.
There's also a @Producer feature that lets you enforce the response type, response headers, etc., but that's not particularly important for this project, so we'll skip it.
For more details, refer to the official Jersey 3 documentation.
But as you use it, something feels a bit off. Where did HttpServletRequest and HttpServletResponse, which we used constantly with Servlet, go?
Since Jersey is delegated the request in place of Servlet, the Servlet objects aren't exposed on the surface. In this case, you can access the Servlet objects through the @Context annotation.
I create one abstract object that bundles all the Context objects I need, and have every controller API inherit from it.
JAVA
abstract public class API { @Context protected HttpServletRequest request; @Context protected HttpServletResponse response; @Context protected UriInfo uriInfo; }
Declare one abstract object called API as shown above. Then specify HttpServletRequest and HttpServletResponse with the @Context annotation attached. I haven't really used UriInfo much myself, so it's fine to omit it if you don't need it.
If there are other methods you'd like to include in the common API object, feel free to include them as well.
JAVA
@Path("/userinfo") public class TestAPI extends API { @GET @Path("") public String testResponse() { return request.getContextPath(); } }
Have the TestAPI object inherit from the API abstract object. Since it's declared as protected, any object that extends API can access HttpServletRequest and the other Context objects.
If for whatever reason you don't want to implement it via inheritance, you can just add everything directly as shown below.
JAVA
@Path("/userinfo") public class TestAPI { @Context protected HttpServletRequest request; @Context protected HttpServletResponse response; @Context protected UriInfo uriInfo; @GET @Path("") public String testResponse() { return request.getContextPath(); } }
This works fine too. Just remember that you'll need to add the same code to every controller API.
By using Jersey, we can handle requests and responses much more powerfully than with Servlet. In the next chapter, let's build the controllers directly based on this.
![[OAuth2.0] Building an OAuth2.0 Authorization Server with ScribeJAVA - 12. Closing Thoughts](https://user-images.githubusercontent.com/50317129/137171016-99af1db1-a346-4def-9329-6072b927bdc0.png)
![[NextJS] Blog Overhaul Journey - 4. Implementing a Markdown Converter Using marked](https://user-images.githubusercontent.com/50317129/134931033-89954c3d-5e00-4b3b-85aa-54a1dfa29e46.png)