Skip to content

Commit

Permalink
Refactor security/jwt in order to use JWT builder inside a Quarkus ap…
Browse files Browse the repository at this point in the history
…plication
  • Loading branch information
pablo gonzalez granados committed May 30, 2022
1 parent de8158c commit 147aac8
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 87 deletions.
1 change: 0 additions & 1 deletion security/jwt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt-build</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.quarkus.ts.security.jwt;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.util.Collections;
import java.util.Date;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.annotation.security.PermitAll;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.build.JwtClaimsBuilder;

@Path("/login")
public class GenerateJwtResource {

public enum Invalidity {
WRONG_ISSUER,
WRONG_DATE,
WRONG_KEY
}

private static final String DEFAULT_ISSUER = "https://my.auth.server/";
private static final int TEN = 10;
private static final int NINETY = 90;

@POST
@Path("/jwt")
@PermitAll
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String login(@QueryParam("invalidity") String invalidity, String body) throws NoSuchAlgorithmException {
Date now = new Date();
Date expiration = new Date(TimeUnit.SECONDS.toMillis(TEN) + now.getTime());
String issuer = DEFAULT_ISSUER;
if (invalidity.equalsIgnoreCase(Invalidity.WRONG_ISSUER.name())) {
issuer = "https://wrong/";
}

if (invalidity.equalsIgnoreCase(Invalidity.WRONG_DATE.name())) {
now = new Date(now.getTime() - TimeUnit.DAYS.toMillis(TEN));
expiration = new Date(now.getTime() - TimeUnit.DAYS.toMillis(TEN));
}

PrivateKey privateKey = null;
if (invalidity.equalsIgnoreCase(Invalidity.WRONG_KEY.name())) {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();
privateKey = (RSAPrivateKey) keyPair.getPrivate();
}

JwtClaimsBuilder jwtbuilder = Jwt.issuer(issuer)
.expiresAt(expiration.getTime())
.issuedAt(now.getTime())
.subject("test_subject_at_example_com")
.groups(Set.of(body))
.claim("upn", "[email protected]")
.claim("roleMappings", Collections.singletonMap("admin", "superuser"));

if (!Objects.isNull(privateKey)) {
return jwtbuilder.jws().sign(privateKey);
}

return jwtbuilder.sign();
}
}
1 change: 1 addition & 0 deletions security/jwt/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
smallrye.jwt.sign.key.location=private-key.pem
mp.jwt.verify.publickey.location=public-key.pem
mp.jwt.verify.issuer=https://my.auth.server/
smallrye.jwt.expiration.grace=120
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,30 @@
package io.quarkus.ts.security.jwt;

import static io.quarkus.ts.security.jwt.GenerateJwtResource.Invalidity;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import org.apache.http.HttpStatus;
import org.jboss.logging.Logger;
import org.jose4j.base64url.internal.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.Test;

import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.smallrye.jwt.algorithm.SignatureAlgorithm;
import io.smallrye.jwt.build.Jwt;

public abstract class BaseJwtSecurityIT {

private static final Logger LOG = Logger.getLogger(BaseJwtSecurityIT.class);

private static final int TEN = 10;
private static final int NINETY = 90;

@Test
public void securedEveryoneNoGroup() throws Exception {
givenWithToken(createToken())
givenWithToken(createToken(""))
.get("/secured/everyone")
.then()
.statusCode(HttpStatus.SC_OK)
Expand Down Expand Up @@ -70,31 +57,31 @@ public void securedEveryoneAdminGroup() throws Exception {

@Test
public void securedEveryoneWrongIssuer() throws Exception {
givenWithToken(createToken(Invalidity.WRONG_ISSUER))
givenWithToken(createToken(Invalidity.WRONG_ISSUER, ""))
.get("/secured/everyone")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED);
}

@Test
public void securedEveryoneWrongDate() throws Exception {
givenWithToken(createToken(Invalidity.WRONG_DATE))
givenWithToken(createToken(Invalidity.WRONG_DATE, ""))
.get("/secured/everyone")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED);
}

@Test
public void securedEveryoneWrongKey() throws Exception {
givenWithToken(createToken(Invalidity.WRONG_KEY))
givenWithToken(createToken(Invalidity.WRONG_KEY, ""))
.get("/secured/everyone")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED);
}

@Test
public void securedAdminNoGroup() throws Exception {
givenWithToken(createToken())
givenWithToken(createToken(""))
.get("/secured/admin")
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
Expand All @@ -119,7 +106,7 @@ public void securedAdminAdminGroup() throws Exception {

@Test
public void securedNoOneNoGroup() throws Exception {
givenWithToken(createToken())
givenWithToken(createToken(""))
.get("/secured/noone")
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
Expand All @@ -143,7 +130,7 @@ public void securedNoOneAdminGroup() throws Exception {

@Test
public void permittedCorrectToken() throws Exception {
givenWithToken(createToken())
givenWithToken(createToken(""))
.get("/permitted")
.then()
.statusCode(HttpStatus.SC_OK)
Expand All @@ -152,39 +139,39 @@ public void permittedCorrectToken() throws Exception {

@Test
public void permittedWrongIssuer() throws Exception {
givenWithToken(createToken(Invalidity.WRONG_ISSUER))
givenWithToken(createToken(Invalidity.WRONG_ISSUER, ""))
.get("/permitted")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED); // in Thorntail, this is 200, but both approaches are likely valid
}

@Test
public void permittedWrongDate() throws Exception {
givenWithToken(createToken(Invalidity.WRONG_DATE))
givenWithToken(createToken(Invalidity.WRONG_DATE, ""))
.get("/permitted")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED); // in Thorntail, this is 200, but both approaches are likely valid
}

@Test
public void permittedWrongKey() throws Exception {
givenWithToken(createToken(Invalidity.WRONG_KEY))
givenWithToken(createToken(Invalidity.WRONG_KEY, ""))
.get("/permitted")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED); // in Thorntail, this is 200, but both approaches are likely valid
}

@Test
public void deniedCorrectToken() throws Exception {
givenWithToken(createToken())
givenWithToken(createToken(""))
.get("/denied")
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
}

@Test
public void mixedConstrained() throws Exception {
givenWithToken(createToken())
givenWithToken(createToken(""))
.get("/mixed/constrained")
.then()
.statusCode(HttpStatus.SC_OK)
Expand All @@ -193,7 +180,7 @@ public void mixedConstrained() throws Exception {

@Test
public void mixedUnconstrained() throws Exception {
givenWithToken(createToken())
givenWithToken(createToken(""))
.get("/mixed/unconstrained")
.then()
.statusCode(HttpStatus.SC_FORBIDDEN); // quarkus.security.deny-unannotated-members=true
Expand Down Expand Up @@ -279,7 +266,7 @@ public void tokenExpirationGracePeriod() throws Exception {
return now;
};

givenWithToken(createToken(clock, null, "admin"))
givenWithToken(createToken("admin"))
.get("/secured/admin")
.then()
.statusCode(HttpStatus.SC_OK)
Expand All @@ -288,64 +275,17 @@ public void tokenExpirationGracePeriod() throws Exception {

protected abstract RequestSpecification givenWithToken(String token);

private static RSAPrivateKey loadPrivateKey() throws Exception {
String key = new String(Files.readAllBytes(Paths.get("target/test-classes/private-key.pem")), Charset.defaultCharset());

String privateKeyPEM = key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PRIVATE KEY-----", "");

byte[] encoded = Base64.decodeBase64(privateKeyPEM);

KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}

private enum Invalidity {
WRONG_ISSUER,
WRONG_DATE,
WRONG_KEY
}

private static String createToken(String... groups) throws Exception {
return createToken(Date::new, null, groups);
}

private static String createToken(Invalidity invalidity, String... groups) throws Exception {
return createToken(Date::new, invalidity, groups);
private static String createToken(String group) throws Exception {
return createToken(null, group);
}

private static String createToken(Supplier<Date> clock, Invalidity invalidity, String... groups)
throws Exception {
String issuer = "https://my.auth.server/";
if (invalidity == Invalidity.WRONG_ISSUER) {
issuer = "https://wrong/";
}

Date now = clock.get();
Date expiration = new Date(TimeUnit.SECONDS.toMillis(TEN) + now.getTime());
if (invalidity == Invalidity.WRONG_DATE) {
now = new Date(now.getTime() - TimeUnit.DAYS.toMillis(TEN));
expiration = new Date(now.getTime() - TimeUnit.DAYS.toMillis(TEN));
}

RSAPrivateKey privateKey = loadPrivateKey();
if (invalidity == Invalidity.WRONG_KEY) {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();
privateKey = (RSAPrivateKey) keyPair.getPrivate();
}

return Jwt.issuer(issuer)
.expiresAt(expiration.getTime())
.issuedAt(now.getTime())
.subject("test_subject_at_example_com")
.groups(Set.of(groups))
.claim("upn", "[email protected]")
.claim("roleMappings", Collections.singletonMap("admin", "superuser"))
.jws().algorithm(SignatureAlgorithm.RS256).sign(privateKey);
private static String createToken(Invalidity invalidity, String group) {
return given()
.body(group)
.when()
.post("/login/jwt?invalidity=" + (Objects.isNull(invalidity) ? "" : invalidity.name())).then()
.statusCode(200)
.extract().body().asString();
}

@Test
Expand Down

0 comments on commit 147aac8

Please sign in to comment.