DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Issue and Present Verifiable Credentials With Spring Boot and Android
  • Secure Your Frontend: Practical Tips for Developers
  • A Practical Guide to Securing NodeJS APIs With JWT
  • How to Set up OAuth JWT Flow and mTLS in the Salesforce Connector for a MuleSoft App

Trending

  • Modern Test Automation With AI (LLM) and Playwright MCP
  • How to Merge HTML Documents in Java
  • Go 1.24+ Native FIPS Support for Easier Compliance
  • The Role of AI in Identity and Access Management for Organizations
  1. DZone
  2. Software Design and Architecture
  3. Security
  4. Securing JAX-RS Endpoints with JWT

Securing JAX-RS Endpoints with JWT

Securing REST endpoints is an important part of any REST development cycle. Learn how to do just that using JWT in this post.

By 
Antonio Goncalves user avatar
Antonio Goncalves
·
Oct. 07, 16 · Opinion
Likes (20)
Comment
Save
Tweet
Share
29.1K Views

Join the DZone community and get the full member experience.

Join For Free

(Download the code here.)

In this blog post, I’ll show you how to use the JJWT library to issue and verify JSON Web Tokens with JAX-RS endpoints. The idea is to allow an invocation when no token is needed, but also, be able to reject an invocation when a JWT token is explicitly needed.

Let’s say we have a REST Endpoint with several methods: methods that can be invoked directly, and methods that can be invoked only if the caller is authenticated. There are several ways to authenticate, authorize, encrypt… REST endpoints invocations. Some complex, some easier. Here I will use JWT, or JSON Web Token. The idea is that when authorization is needed, the caller needs to get a JWT token and then pass it around. I won’t go into too much details on JSon Web Token as you can find plenty of resources. I just want to show you some code so you see how easy it is to setup with JAX-RS.

Use Case

In this example we have two REST Endpoints:

  • EchoEndpoint: this is just an Echo endpoint with two methods: one accessible by everyone (echo), another one accessible only if you pass a valid JSON Web Token (echoWithJWTToken), meaning you identified first using UserEndpoint
  • UserEndpoint: this endpoint returns information about the users of the application(the User JPA entity), but more important, has a method to authenticate (authenticateUser) using login/password. Once authenticate, you get aJSON Web Token (and can then pass it around)

classes

Securing an Invocation

Below is the code of the EchoEndpoint. As you can see, this basic JAX-RS Endpoint has two GET methods both returning a String:

  • one on /echo accessible by everyone
  • one on /echo/jwt only accessible if the client passes a token. How do we check that the token is needed? Using the JWTTokenNeeded name binding and the JWTTokenNeededFilter (see below)
@Path("/echo")
@Produces(TEXT_PLAIN)
public class EchoEndpoint {

    @GET
    public Response echo(@QueryParam("message") String message) {
        return Response.ok().entity(message == null ? "no message" : message).build();
    }

    @GET
    @Path("jwt")
    @JWTTokenNeeded
    public Response echoWithJWTToken(@QueryParam("message") String message) {
        return Response.ok().entity(message == null ? "no message" : message).build();
    }
}

Filter Checking the JSon Web Token

The magic hides behind JWTTokenNeeded. Well, not really, it hides behind the JWTTokenNeededFilter. JWTTokenNeeded is just a JAX-RS name binding (think of it as a CDI interceptor binding), so it’s just an annotation that binds to a filter.

@javax.ws.rs.NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface JWTTokenNeeded {
}

The filter itself is the one doing all the work. It implements ContainerRequestFilter and therefore allows us to check the request headers. Basically, when the EchoEndpoint::echoWithJWTToken method is invoked, the runtime intercepts the invocation, and does the following:

  1. Gets the HTTP Authorization header from the request and checks for the JSon Web Token (the Bearer string)
  2. It validates the token (using the JJWT library)
  3. If the token is valid, fine, the echoWithJWTToken method is invoked
  4. If the token is invalid, a 401 Unauthorized is sent to the client
@Provider
@JWTTokenNeeded
@Priority(Priorities.AUTHENTICATION)
public class JWTTokenNeededFilter implements ContainerRequestFilter {

    @Inject
    private KeyGenerator keyGenerator;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        // Get the HTTP Authorization header from the request
        String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

        // Extract the token from the HTTP Authorization header
        String token = authorizationHeader.substring("Bearer".length()).trim();

        try {

            // Validate the token
            Key key = keyGenerator.generateKey();
            Jwts.parser().setSigningKey(key).parseClaimsJws(token);
            logger.info("#### valid token : " + token);

        } catch (Exception e) {
            logger.severe("#### invalid token : " + token);
            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
        }
    }
}

As you can see on line 22, the JJWT library is very simple as it checks if the token is valid in only 1 line. Validation is made depending on a Key. Here I just use a String to make the example easy to understand, but it could be something safer, like a keystore.

Issuing a JSON Web Token

Ok, now we have a filter that checks that the token is passed in the HTTP header. But how is this token issued? The user needs to log in, invoking an HTTP POST and passing a login and password (here, log in and password are passed in clear for sake of simplicity, but this part should use HTTPs). Once authenticated, JJWT is used to create a token based on the users’ login and the secret key (the same key used in JWTTokenNeededFilter).

@Path("/users")
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@Transactional
public class UserEndpoint {

    @Inject
    private KeyGenerator keyGenerator;

    @POST
    @Path("/login")
    @Consumes(APPLICATION_FORM_URLENCODED)
    public Response authenticateUser(@FormParam("login") String login,
                                     @FormParam("password") String password) {
        try {

            // Authenticate the user using the credentials provided
            authenticate(login, password);

            // Issue a token for the user
            String token = issueToken(login);

            // Return the token on the response
            return Response.ok().header(AUTHORIZATION, "Bearer " + token).build();

        } catch (Exception e) {
            return Response.status(UNAUTHORIZED).build();
        }
    }

    private String issueToken(String login) {
        Key key = keyGenerator.generateKey();
        String jwtToken = Jwts.builder()
                .setSubject(login)
                .setIssuer(uriInfo.getAbsolutePath().toString())
                .setIssuedAt(new Date())
                .setExpiration(toDate(LocalDateTime.now().plusMinutes(15L)))
                .signWith(SignatureAlgorithm.HS512, key)
                .compact();
        return jwtToken;
    }
}

Notice that the JSon Web Token sets a few claims (in the issueToken method): subject (the principal’s login), an issuer (the one who issued the token), an issued date, a signing algorithm, and very important, an expiration date for the token. Now, the client is authenticated, and it has a token that it needs to pass to be able to invoke the Echo endpoint again.

The token looks like this (using the JWT debugger):

JSon Web Token

Putting It All Together

Now that you have all the pieces, let’s put them together.

  1. First, the client invokes the EchoEndpoint with no token. This invocation is intercepted by the filter that checks that there is no token and…
  2. returns a 401 Unauthorized to the client
  3. The client needs to authenticate issuing an HTTP POST and passing a login/password
  4. After authentication, a JSon Web Token is returned to the client. It is encrypted using the user’s login and a key.
  5. The user re-invokes the same EchoEndpoint but this time with a token. The same filter intercepts the call, checks the token is valid, and…
  6. allows the EchoEndpoint invocation

Testing the Interaction

Thanks to Arquillian we can easily test this entire integration. Here I’ve slightly simplified the code (get it all on GitHub) but the idea is that this test class:

  1. First, invokes the EchoEndpoint with no token and gets a 401 Unauthorized
  2. Then it creates a user…
  3. So it can then authenticate the user and get the token. As you can see, I’m using JJWT to check that the token has the needed claims.
  4. Then, it invokes the same EchoEndpoint but this time passing the token in the HTTP header, and this time gets a 200 OK.
@RunWith(Arquillian.class)
@RunAsClient
public class JWTEchoEndpointTest {

    private static final User TEST_USER = new User("id", "last name", "first name", "login", "password");
    private static String token;
    private Client client;
    private WebTarget echoEndpointTarget;
    private WebTarget userEndpointTarget;

    @ArquillianResource
    private URI baseURL;

    @Before
    public void initWebTarget() {
        client = ClientBuilder.newClient();
        echoEndpointTarget = client.target(baseURL).path("api/echo/jwt");
        userEndpointTarget = client.target(baseURL).path("api/users");
    }

    // ======================================
    // =            Test methods            =
    // ======================================

    @Test
    @InSequence(1)
    public void invokingEchoShouldFailCauseNoToken() throws Exception {
        Response response = echoEndpointTarget.request(TEXT_PLAIN).get();
        assertEquals(401, response.getStatus());
    }

    @Test
    @InSequence(2)
    public void shouldCreateAUser() throws Exception {
        Response response = userEndpointTarget.request(APPLICATION_JSON_TYPE).post(Entity.entity(TEST_USER, APPLICATION_JSON_TYPE));
        assertEquals(201, response.getStatus());
    }

    @Test
    @InSequence(3)
    public void shouldLogUserIn() throws Exception {
        Form form = new Form();
        form.param("login", TEST_USER.getLogin());
        form.param("password", TEST_USER.getPassword());

        Response response = userEndpointTarget.path("login").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));

        assertEquals(200, response.getStatus());
        assertNotNull(response.getHeaderString(HttpHeaders.AUTHORIZATION));
        token = response.getHeaderString(HttpHeaders.AUTHORIZATION);

        // Check the JWT Token
        String justTheToken = token.substring("Bearer".length()).trim();
        Key key = new SimpleKeyGenerator().generateKey();
        assertEquals(1, Jwts.parser().setSigningKey(key).parseClaimsJws(justTheToken).getHeader().size());
        assertEquals("HS512", Jwts.parser().setSigningKey(key).parseClaimsJws(justTheToken).getHeader().getAlgorithm());
        assertEquals(4, Jwts.parser().setSigningKey(key).parseClaimsJws(justTheToken).getBody().size());
        assertEquals("login", Jwts.parser().setSigningKey(key).parseClaimsJws(justTheToken).getBody().getSubject());
        assertEquals(baseURL.toString().concat("api/users/login"), Jwts.parser().setSigningKey(key).parseClaimsJws(justTheToken).getBody().getIssuer());
        assertNotNull(Jwts.parser().setSigningKey(key).parseClaimsJws(justTheToken).getBody().getIssuedAt());
        assertNotNull(Jwts.parser().setSigningKey(key).parseClaimsJws(justTheToken).getBody().getExpiration());
    }

    @Test
    @InSequence(4)
    public void invokingEchoShouldSucceedCauseToken() throws Exception {
        Response response = echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, token).get();
        assertEquals(200, response.getStatus());
        assertEquals("no message", response.readEntity(String.class));
    }
}

Conclusion

In this blog, I wanted to show you how easy it is to issue and validate a JSON Web Token with JAX-RS. Here I’m using the external JJWT library as this is not standard in JAX-RS. I find JJWT easy to use but you can find other libraries that do more or less the same (jose.4.j, Nimbus or Java JWT). I didn’t use any security for authentication (security is complex and not very portable in Java EE) so the login/password are not encrypted and no realm is setup.

Download the code, give it a try and leave some comments.

JWT (JSON Web Token)

Published at DZone with permission of Antonio Goncalves, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Issue and Present Verifiable Credentials With Spring Boot and Android
  • Secure Your Frontend: Practical Tips for Developers
  • A Practical Guide to Securing NodeJS APIs With JWT
  • How to Set up OAuth JWT Flow and mTLS in the Salesforce Connector for a MuleSoft App

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!