Skip to content

Commit

Permalink
Merge pull request #36 from G8XSU/java-jwt-authorizer
Browse files Browse the repository at this point in the history
Add JWTAuthorizer Implementation.
  • Loading branch information
G8XSU authored Oct 22, 2024
2 parents 15c68d6 + 4d5cbd5 commit e91d929
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
2 changes: 2 additions & 0 deletions java/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ dependencies {
implementation('org.glassfish.jersey.inject:jersey-hk2:3.1.0')
implementation "org.glassfish.hk2:guice-bridge:3.0.3"

implementation "com.auth0:java-jwt:4.2.0"

compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'

Expand Down
80 changes: 80 additions & 0 deletions java/app/src/main/java/org/vss/auth/JwtAuthorizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.vss.auth;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import jakarta.ws.rs.core.HttpHeaders;
import org.vss.exception.AuthException;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

// A JWT (https://datatracker.ietf.org/doc/html/rfc7519) based authorizer,
public class JwtAuthorizer implements Authorizer {

private final PublicKey publicKey;
private final JWTVerifier verifier;

private static final String BEARER_PREFIX = "Bearer ";
private static final int MAX_USER_TOKEN_LENGTH = 120;

// `pemFormatRSAPublicKey` is RSA public key used by JWT Auth server for creating signed JWT tokens.
// Refer to OpenSSL(https://docs.openssl.org/1.1.1/man1/rsa/) docs for generating valid key pairs.
// Example:
// * To generate private key, run : `openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048`
// * To generate public key, run: `openssl rsa -pubout -in private_key.pem -out public_key.pem`
public JwtAuthorizer(String pemFormatRSAPublicKey) throws Exception {
this.publicKey = loadPublicKey(pemFormatRSAPublicKey);

Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
this.verifier = JWT.require(algorithm).build();
}

@Override
public AuthResponse verify(HttpHeaders headers) throws AuthException {

try {
String authorizationHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null || !authorizationHeader.startsWith(BEARER_PREFIX)) {
throw new AuthException("Missing or invalid Authorization header.");
}

// Extract token by excluding BEARER_PREFIX.
String token = authorizationHeader.substring(BEARER_PREFIX.length());

DecodedJWT jwt = verifier.verify(token);

// Extract the user identity from the token.
String userToken = jwt.getSubject();

if (userToken == null || userToken.isBlank()) {
throw new AuthException("Invalid JWT token.");
} else if (userToken.length() > MAX_USER_TOKEN_LENGTH) {
throw new AuthException("UserToken is too long");
}

return new AuthResponse(userToken);

} catch (JWTVerificationException e) {
throw new AuthException("Invalid JWT token.");
}
}

private PublicKey loadPublicKey(String pemFormatRSAPublicKey) throws Exception {
String key = pemFormatRSAPublicKey
.replaceAll("\\n", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");

byte[] keyBytes = Base64.getDecoder().decode(key);

X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
}
}
75 changes: 75 additions & 0 deletions java/app/src/test/java/org/vss/auth/JwtAuthorizerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.vss.auth;

import jakarta.ws.rs.core.HttpHeaders;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.vss.exception.AuthException;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class JwtAuthorizerTest {

private JwtAuthorizer jwtAuthorizer;
private HttpHeaders headers;

private static final String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAysGpKU+I9i9b+QZSANu/\n" +
"ExaA6w4qiQdFZaXeReiz49r1oDfABwKIFW9gK/kNnrnL9H8P+pYfj7jqUJ/glmgq\n" +
"MsvBshbbD2FhxytSS0mhsbh6QxUhlanymPcSUUyKBD6v7W0CGUhS5luHlsCFn4ys\n" +
"lFk4pavcBtGap0DTUc8yz0j/xnmSQbdjWgm0awbHN48uItRO3UhLAOetG+BzlWCR\n" +
"8YsTa5piV8KgJpG/rwYTGXuu3lcCmnWwjmbeDq1zFFrCDDVkaIHkGJgRuFIDPXaH\n" +
"yUw5H2HvKlP94ySbvTDLXWZj6TyzHEHDbstqs4DgvurB/bIhi/dQ7zK3EIXL8KRB\n" +
"hwIDAQAB\n" +
"-----END PUBLIC KEY-----";

private static final String VALID_AUTH_HEADER = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9." +
"eyJzdWIiOiJ2YWxpZF91c2VyX2lkIiwiaWF0IjoxNzI5NjM0MjYwLCJuYmYiOjE3Mjk2MzQyNjAsImV4cCI6MzA1" +
"NTY4OTE1OTQwMzc0NTl9.xBL5BYiv8B-ZN1bCuljuJ7dZeOPocVPPVwkeK_GH4lD5iQqD08zi93WuXw1c6NWWCK4" +
"jn4ZssYrzSLLL5q3tAYbLKuhQ2-2A-e1HTasfvSnx_jCBUNApbIv3rM19M3rhRVRSxT2s2jI7dJFlM6E_bGMfj9w" +
"uoZiT_amjIIPQJiRkDKcO2sXnD6eU_yx8EIhH_PemSX3kp9Sx9eTYqGbyCtLrs9jK7nr6GQ_1jc6ie03Uh2dsIzW" +
"sZqGHh2n_WmdyURWEfwsMYFpepRLzm77dP9q78RgA8eDLZSLNW9ssJMYWY9DRkOZBFFuf4uy-uqC9MWS64DkJSAo" +
"nH8Zof_tUiQ";

private static final String VALID_USER_ID = "valid_user_id";

@BeforeEach
public void setUp() throws Exception {
jwtAuthorizer = new JwtAuthorizer(PUBLIC_KEY);
headers = mock(HttpHeaders.class);
}

@Test
public void testValidJwtToken() {
when(headers.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(VALID_AUTH_HEADER);

AuthResponse authResponse = jwtAuthorizer.verify(headers);

assertNotNull(authResponse);

assertEquals(VALID_USER_ID, authResponse.getUserToken());
}

@Test
public void testMissingAuthorizationHeader() {
when(headers.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(null);

assertThrows(AuthException.class, () -> jwtAuthorizer.verify(headers));
}

@Test
public void testInvalidAuthorizationHeader() {
when(headers.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn("InvalidHeader");

assertThrows(AuthException.class, () -> jwtAuthorizer.verify(headers));
}

@Test
public void testInvalidJwtToken() {
String invalidJwt = "Bearer invalid.jwt.token";
when(headers.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(invalidJwt);

assertThrows(AuthException.class, () -> jwtAuthorizer.verify(headers));
}
}

0 comments on commit e91d929

Please sign in to comment.