Skip to content

Commit e91d929

Browse files
authored
Merge pull request #36 from G8XSU/java-jwt-authorizer
Add JWTAuthorizer Implementation.
2 parents 15c68d6 + 4d5cbd5 commit e91d929

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

java/app/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ dependencies {
5959
implementation('org.glassfish.jersey.inject:jersey-hk2:3.1.0')
6060
implementation "org.glassfish.hk2:guice-bridge:3.0.3"
6161

62+
implementation "com.auth0:java-jwt:4.2.0"
63+
6264
compileOnly 'org.projectlombok:lombok:1.18.24'
6365
annotationProcessor 'org.projectlombok:lombok:1.18.24'
6466

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.vss.auth;
2+
3+
import com.auth0.jwt.JWT;
4+
import com.auth0.jwt.algorithms.Algorithm;
5+
import com.auth0.jwt.exceptions.JWTVerificationException;
6+
import com.auth0.jwt.interfaces.DecodedJWT;
7+
import com.auth0.jwt.interfaces.JWTVerifier;
8+
import jakarta.ws.rs.core.HttpHeaders;
9+
import org.vss.exception.AuthException;
10+
11+
import java.security.KeyFactory;
12+
import java.security.PublicKey;
13+
import java.security.interfaces.RSAPublicKey;
14+
import java.security.spec.X509EncodedKeySpec;
15+
import java.util.Base64;
16+
17+
// A JWT (https://datatracker.ietf.org/doc/html/rfc7519) based authorizer,
18+
public class JwtAuthorizer implements Authorizer {
19+
20+
private final PublicKey publicKey;
21+
private final JWTVerifier verifier;
22+
23+
private static final String BEARER_PREFIX = "Bearer ";
24+
private static final int MAX_USER_TOKEN_LENGTH = 120;
25+
26+
// `pemFormatRSAPublicKey` is RSA public key used by JWT Auth server for creating signed JWT tokens.
27+
// Refer to OpenSSL(https://docs.openssl.org/1.1.1/man1/rsa/) docs for generating valid key pairs.
28+
// Example:
29+
// * To generate private key, run : `openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048`
30+
// * To generate public key, run: `openssl rsa -pubout -in private_key.pem -out public_key.pem`
31+
public JwtAuthorizer(String pemFormatRSAPublicKey) throws Exception {
32+
this.publicKey = loadPublicKey(pemFormatRSAPublicKey);
33+
34+
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
35+
this.verifier = JWT.require(algorithm).build();
36+
}
37+
38+
@Override
39+
public AuthResponse verify(HttpHeaders headers) throws AuthException {
40+
41+
try {
42+
String authorizationHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
43+
if (authorizationHeader == null || !authorizationHeader.startsWith(BEARER_PREFIX)) {
44+
throw new AuthException("Missing or invalid Authorization header.");
45+
}
46+
47+
// Extract token by excluding BEARER_PREFIX.
48+
String token = authorizationHeader.substring(BEARER_PREFIX.length());
49+
50+
DecodedJWT jwt = verifier.verify(token);
51+
52+
// Extract the user identity from the token.
53+
String userToken = jwt.getSubject();
54+
55+
if (userToken == null || userToken.isBlank()) {
56+
throw new AuthException("Invalid JWT token.");
57+
} else if (userToken.length() > MAX_USER_TOKEN_LENGTH) {
58+
throw new AuthException("UserToken is too long");
59+
}
60+
61+
return new AuthResponse(userToken);
62+
63+
} catch (JWTVerificationException e) {
64+
throw new AuthException("Invalid JWT token.");
65+
}
66+
}
67+
68+
private PublicKey loadPublicKey(String pemFormatRSAPublicKey) throws Exception {
69+
String key = pemFormatRSAPublicKey
70+
.replaceAll("\\n", "")
71+
.replace("-----BEGIN PUBLIC KEY-----", "")
72+
.replace("-----END PUBLIC KEY-----", "");
73+
74+
byte[] keyBytes = Base64.getDecoder().decode(key);
75+
76+
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
77+
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
78+
return keyFactory.generatePublic(spec);
79+
}
80+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.vss.auth;
2+
3+
import jakarta.ws.rs.core.HttpHeaders;
4+
import org.junit.jupiter.api.BeforeEach;
5+
import org.junit.jupiter.api.Test;
6+
import org.vss.exception.AuthException;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
import static org.mockito.Mockito.mock;
10+
import static org.mockito.Mockito.when;
11+
12+
class JwtAuthorizerTest {
13+
14+
private JwtAuthorizer jwtAuthorizer;
15+
private HttpHeaders headers;
16+
17+
private static final String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" +
18+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAysGpKU+I9i9b+QZSANu/\n" +
19+
"ExaA6w4qiQdFZaXeReiz49r1oDfABwKIFW9gK/kNnrnL9H8P+pYfj7jqUJ/glmgq\n" +
20+
"MsvBshbbD2FhxytSS0mhsbh6QxUhlanymPcSUUyKBD6v7W0CGUhS5luHlsCFn4ys\n" +
21+
"lFk4pavcBtGap0DTUc8yz0j/xnmSQbdjWgm0awbHN48uItRO3UhLAOetG+BzlWCR\n" +
22+
"8YsTa5piV8KgJpG/rwYTGXuu3lcCmnWwjmbeDq1zFFrCDDVkaIHkGJgRuFIDPXaH\n" +
23+
"yUw5H2HvKlP94ySbvTDLXWZj6TyzHEHDbstqs4DgvurB/bIhi/dQ7zK3EIXL8KRB\n" +
24+
"hwIDAQAB\n" +
25+
"-----END PUBLIC KEY-----";
26+
27+
private static final String VALID_AUTH_HEADER = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9." +
28+
"eyJzdWIiOiJ2YWxpZF91c2VyX2lkIiwiaWF0IjoxNzI5NjM0MjYwLCJuYmYiOjE3Mjk2MzQyNjAsImV4cCI6MzA1" +
29+
"NTY4OTE1OTQwMzc0NTl9.xBL5BYiv8B-ZN1bCuljuJ7dZeOPocVPPVwkeK_GH4lD5iQqD08zi93WuXw1c6NWWCK4" +
30+
"jn4ZssYrzSLLL5q3tAYbLKuhQ2-2A-e1HTasfvSnx_jCBUNApbIv3rM19M3rhRVRSxT2s2jI7dJFlM6E_bGMfj9w" +
31+
"uoZiT_amjIIPQJiRkDKcO2sXnD6eU_yx8EIhH_PemSX3kp9Sx9eTYqGbyCtLrs9jK7nr6GQ_1jc6ie03Uh2dsIzW" +
32+
"sZqGHh2n_WmdyURWEfwsMYFpepRLzm77dP9q78RgA8eDLZSLNW9ssJMYWY9DRkOZBFFuf4uy-uqC9MWS64DkJSAo" +
33+
"nH8Zof_tUiQ";
34+
35+
private static final String VALID_USER_ID = "valid_user_id";
36+
37+
@BeforeEach
38+
public void setUp() throws Exception {
39+
jwtAuthorizer = new JwtAuthorizer(PUBLIC_KEY);
40+
headers = mock(HttpHeaders.class);
41+
}
42+
43+
@Test
44+
public void testValidJwtToken() {
45+
when(headers.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(VALID_AUTH_HEADER);
46+
47+
AuthResponse authResponse = jwtAuthorizer.verify(headers);
48+
49+
assertNotNull(authResponse);
50+
51+
assertEquals(VALID_USER_ID, authResponse.getUserToken());
52+
}
53+
54+
@Test
55+
public void testMissingAuthorizationHeader() {
56+
when(headers.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(null);
57+
58+
assertThrows(AuthException.class, () -> jwtAuthorizer.verify(headers));
59+
}
60+
61+
@Test
62+
public void testInvalidAuthorizationHeader() {
63+
when(headers.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn("InvalidHeader");
64+
65+
assertThrows(AuthException.class, () -> jwtAuthorizer.verify(headers));
66+
}
67+
68+
@Test
69+
public void testInvalidJwtToken() {
70+
String invalidJwt = "Bearer invalid.jwt.token";
71+
when(headers.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(invalidJwt);
72+
73+
assertThrows(AuthException.class, () -> jwtAuthorizer.verify(headers));
74+
}
75+
}

0 commit comments

Comments
 (0)