diff --git a/core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java b/core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java index 653ccaac..5a9bb410 100644 --- a/core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java +++ b/core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java @@ -26,12 +26,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Comparator; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyWriter; - import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.support.ReflectionSupport; @@ -55,8 +55,8 @@ public class RestClientBuilder { /** * @param appContextRoot The protocol, hostname, port, and application root path for the REST Client - * For example, http://localhost:8080/myapp/. If unspecified, the app context - * root will be automatically detected by {@link ApplicationEnvironment#getApplicationURL()} + * For example, http://localhost:8080/myapp/. If unspecified, the app context + * root will be automatically detected by {@link ApplicationEnvironment#getApplicationURL()} * @return The same builder instance */ public RestClientBuilder withAppContextRoot(String appContextRoot) { @@ -67,9 +67,9 @@ public RestClientBuilder withAppContextRoot(String appContextRoot) { /** * @param jaxrsPath The portion of the path after the app context root. For example, if a JAX-RS - * endpoint is deployed at http://localhost:8080/myapp/hello and the app context root - * is http://localhost:8080/myapp/, then the jaxrsPath is hello. If - * unspecified, the JAX-RS path will be automatically detected by annotation scanning. + * endpoint is deployed at http://localhost:8080/myapp/hello and the app context root + * is http://localhost:8080/myapp/, then the jaxrsPath is hello. If + * unspecified, the JAX-RS path will be automatically detected by annotation scanning. * @return The same builder instance */ public RestClientBuilder withJaxrsPath(String jaxrsPath) { @@ -93,7 +93,7 @@ public RestClientBuilder withJwt(String jwt) { } /** - * @param user The username portion of the Basic auth header + * @param user The username portion of the Basic auth header * @param password The password portion of the Basic auth header * @return The same builder instance */ @@ -110,7 +110,7 @@ public RestClientBuilder withBasicAuth(String user, String password) { } /** - * @param key The header key + * @param key The header key * @param value The header value * @return The same builder instance */ @@ -126,8 +126,8 @@ public RestClientBuilder withHeader(String key, String value) { /** * @param providers One or more providers to apply. Providers typically implement - * {@link MessageBodyReader} and/or {@link MessageBodyWriter}. If unspecified, - * the {@link JsonBProvider} will be applied. + * {@link MessageBodyReader} and/or {@link MessageBodyWriter}. If unspecified, + * the {@link JsonBProvider} will be applied. * @return The same builder instance */ public RestClientBuilder withProviders(Class... providers) { @@ -145,7 +145,7 @@ public T build(Class clazz) { providers = Collections.singletonList(JsonBProvider.class); JAXRSClientFactoryBean bean = new org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean(); - String basePath = join(appContextRoot, jaxrsPath); + String basePath = joinAppAndJaxrsPath(appContextRoot, jaxrsPath); LOG.info("Building rest client for " + clazz + " with base path: " + basePath + " and providers: " + providers); bean.setResourceClass(clazz); bean.setProviders(providers); @@ -163,10 +163,10 @@ private static String locateApplicationPath(Class clazz) { // First check for a jakarta.ws.rs.core.Application in the same package as the resource List> appClasses = ReflectionSupport.findAllClassesInPackage(resourcePackage, - c -> Application.class.isAssignableFrom(c) && - AnnotationSupport.isAnnotated(c, ApplicationPath.class), - n -> true); - if (appClasses.size() == 0) { + c -> Application.class.isAssignableFrom(c) && + AnnotationSupport.isAnnotated(c, ApplicationPath.class), + n -> true); + if (appClasses.isEmpty()) { LOG.debug("no classes implementing Application found in pkg: " + resourcePackage); // If not found, check under the 3rd package, so com.foo.bar.* // Classpath scanning can be expensive, so we jump straight to the 3rd package from root instead @@ -176,33 +176,33 @@ private static String locateApplicationPath(Class clazz) { String checkPkg = pkgs[0] + '.' + pkgs[1] + '.' + pkgs[2]; LOG.debug("checking in pkg: " + checkPkg); appClasses = ReflectionSupport.findAllClassesInPackage(checkPkg, - c -> Application.class.isAssignableFrom(c) && - AnnotationSupport.isAnnotated(c, ApplicationPath.class), - n -> true); + c -> Application.class.isAssignableFrom(c) && + AnnotationSupport.isAnnotated(c, ApplicationPath.class), + n -> true); } } - if (appClasses.size() == 0) { + if (appClasses.isEmpty()) { LOG.info("No classes implementing 'jakarta.ws.rs.core.Application' found on classpath to set base path from " + clazz + - ". Defaulting base path to '/'"); + ". Defaulting base path to '/'"); return ""; } Class selectedClass = appClasses.stream() - .sorted((c1, c2) -> c1.getName().compareTo(c2.getName())) - .findFirst() - .get(); + .sorted(Comparator.comparing(Class::getName)) + .findFirst() + .get(); ApplicationPath appPath = AnnotationSupport.findAnnotation(selectedClass, ApplicationPath.class).get(); if (appClasses.size() > 1) { LOG.warn("Found multiple classes implementing 'jakarta.ws.rs.core.Application' on classpath: " + appClasses + - ". Setting base path from the first class discovered (" + selectedClass.getCanonicalName() + ") with path: " + - appPath.value()); + ". Setting base path from the first class discovered (" + selectedClass.getCanonicalName() + ") with path: " + + appPath.value()); } LOG.debug("Using base ApplicationPath of '" + appPath.value() + "'"); return appPath.value(); } - private static String join(String firstPart, String secondPart) { + private static String joinAppAndJaxrsPath(String firstPart, String secondPart) { if (firstPart.endsWith("/") && secondPart.startsWith("/")) return firstPart + secondPart.substring(1); else if (firstPart.endsWith("/") || secondPart.startsWith("/")) @@ -211,4 +211,5 @@ else if (firstPart.endsWith("/") || secondPart.startsWith("/")) return firstPart + "/" + secondPart; } + } diff --git a/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java b/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java index d9743f38..04f2fe74 100644 --- a/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java +++ b/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java @@ -18,17 +18,6 @@ */ package org.microshed.testing.jupiter; -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.Properties; - import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; @@ -44,6 +33,12 @@ import org.microshed.testing.kafka.KafkaConsumerClient; import org.microshed.testing.kafka.KafkaProducerClient; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.*; + /** * JUnit Jupiter extension that is applied whenever the @MicroProfileTest is used on a test class. * Currently this is tied to Testcontainers managing runtime build/deployment, but in a future version @@ -90,8 +85,8 @@ private static void injectRestClients(Class clazz) { for (Field restClientField : restClientFields) { if (!Modifier.isPublic(restClientField.getModifiers()) || - !Modifier.isStatic(restClientField.getModifiers()) || - Modifier.isFinal(restClientField.getModifiers())) { + !Modifier.isStatic(restClientField.getModifiers()) || + Modifier.isFinal(restClientField.getModifiers())) { throw new ExtensionConfigurationException("REST client field must be public, static, and non-final: " + restClientField); } RestClientBuilder rcBuilder = new RestClientBuilder(); @@ -137,10 +132,10 @@ private static void injectKafkaClients(Class clazz) { throw new ExtensionConfigurationException("Fields annotated with @KafkaProducerClient must be of the type " + KafkaProducer.getName()); } if (!Modifier.isPublic(producerField.getModifiers()) || - !Modifier.isStatic(producerField.getModifiers()) || - Modifier.isFinal(producerField.getModifiers())) { + !Modifier.isStatic(producerField.getModifiers()) || + Modifier.isFinal(producerField.getModifiers())) { throw new ExtensionConfigurationException("The KafkaProducer field annotated with @KafkaProducerClient " + - "must be public, static, and non-final: " + producerField); + "must be public, static, and non-final: " + producerField); } Properties properties = kafkaProcessor.getProducerProperties(producerField); @@ -159,10 +154,10 @@ private static void injectKafkaClients(Class clazz) { throw new ExtensionConfigurationException("Fields annotated with @KafkaConsumerClient must be of the type " + KafkaConsumer.getName()); } if (!Modifier.isPublic(consumerField.getModifiers()) || - !Modifier.isStatic(consumerField.getModifiers()) || - Modifier.isFinal(consumerField.getModifiers())) { + !Modifier.isStatic(consumerField.getModifiers()) || + Modifier.isFinal(consumerField.getModifiers())) { throw new ExtensionConfigurationException("The KafkaProducer field annotated with @KafkaConsumerClient " + - "must be public, static, and non-final: " + consumerField); + "must be public, static, and non-final: " + consumerField); } Properties properties = kafkaProcessor.getConsumerProperties(consumerField); @@ -182,7 +177,7 @@ private static void injectKafkaClients(Class clazz) { } } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) private static void configureRestAssured(ApplicationEnvironment config) { if (!config.configureRestAssured()) return; diff --git a/core/src/main/java/org/microshed/testing/jwt/JwtConfig.java b/core/src/main/java/org/microshed/testing/jwt/JwtConfig.java index 0b522962..cdd839c4 100644 --- a/core/src/main/java/org/microshed/testing/jwt/JwtConfig.java +++ b/core/src/main/java/org/microshed/testing/jwt/JwtConfig.java @@ -18,21 +18,23 @@ */ package org.microshed.testing.jwt; +import org.junit.jupiter.api.extension.ExtendWith; +import org.microshed.testing.jaxrs.RESTClient; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.microshed.testing.jaxrs.RESTClient; - /** * Used to annotate a REST Client to configure MicroProfile JWT settings * that will be applied to all of its HTTP invocations. * In order for this annotation to have any effect, the field must also * be annotated with {@link RESTClient}. */ -@Target({ ElementType.FIELD }) +@Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) +@ExtendWith(JwtConfigExtension.class) public @interface JwtConfig { public static final String DEFAULT_ISSUER = "http://testissuer.com"; @@ -46,7 +48,7 @@ * array of claims in the following format: * key=value * example: {"sub=fred", "upn=fred", "kid=123"} - * + *

* For arrays, separate values with a comma. * example: {"groups=red,green,admin", "sub=fred"} * diff --git a/core/src/main/java/org/microshed/testing/jwt/JwtConfigExtension.java b/core/src/main/java/org/microshed/testing/jwt/JwtConfigExtension.java new file mode 100644 index 00000000..55484135 --- /dev/null +++ b/core/src/main/java/org/microshed/testing/jwt/JwtConfigExtension.java @@ -0,0 +1,100 @@ +package org.microshed.testing.jwt; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.microshed.testing.internal.InternalLogger; +import org.microshed.testing.jupiter.MicroShedTestExtension; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JwtConfigExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + private static final InternalLogger LOG = InternalLogger.get(JwtConfigExtension.class); + + @Override + public void beforeTestExecution(ExtensionContext context) throws Exception { + configureJwt(context); + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + removeJwt(context); + } + + private void configureJwt(ExtensionContext context) throws Exception { + + // Check if the test method has the @JwtConfig annotation + Method testMethod = context.getTestMethod().orElse(null); + if (testMethod != null) { + + // Check if RestAssured is being used + Class restAssuredClass = tryLoad("io.restassured.RestAssured"); + if (restAssuredClass == null) { + LOG.debug("RESTAssured not found!"); + } else { + + LOG.debug("RESTAssured found!"); + + JwtConfig jwtConfig = testMethod.getAnnotation(JwtConfig.class); + if (jwtConfig != null) { + // Configure RestAssured with the values from @JwtConfig for each test method + LOG.info("JWTConfig on method: " + testMethod.getName()); + // Get the RequestSpecBuilder class + Class requestSpecBuilderClass = Class.forName("io.restassured.builder.RequestSpecBuilder"); + // Create an instance of RequestSpecBuilder + Object requestSpecBuilder = requestSpecBuilderClass.newInstance(); + // Get the requestSpecification field + Field requestSpecificationField = restAssuredClass.getDeclaredField("requestSpecification"); + requestSpecificationField.setAccessible(true); + + // Get the header method of RequestSpecBuilder + Method headerMethod = requestSpecBuilderClass.getDeclaredMethod("addHeader", String.class, String.class); + + try { + String jwt = JwtBuilder.buildJwt(jwtConfig.subject(), jwtConfig.issuer(), jwtConfig.claims()); + headerMethod.invoke(requestSpecBuilder, "Authorization", "Bearer " + jwt); + LOG.debug("Using provided JWT auth header: " + jwt); + } catch (Exception e) { + throw new ExtensionConfigurationException("Error while building JWT for method " + testMethod.getName() + " with JwtConfig: " + jwtConfig, e); + } + + // Set the updated requestSpecification + requestSpecificationField.set(null, requestSpecBuilderClass.getMethod("build").invoke(requestSpecBuilder)); + } + } + } + } + + private void removeJwt(ExtensionContext context) throws Exception { + // Check if the test method has the @JwtConfig annotation + Method testMethod = context.getTestMethod().orElse(null); + if (testMethod != null) { + LOG.debug("Method was annotated with: " + testMethod.getName()); + + + // Check if RestAssured is being used + Class restAssuredClass = tryLoad("io.restassured.RestAssured"); + if (restAssuredClass == null) { + LOG.debug("RESTAssured not found!"); + } else { + // Get the requestSpecification field + Field requestSpecificationField = restAssuredClass.getDeclaredField("requestSpecification"); + requestSpecificationField.setAccessible(true); + + // Removes all requestSpec + requestSpecificationField.set(null, null); + } + } + } + + private static Class tryLoad(String clazz) { + try { + return Class.forName(clazz, false, MicroShedTestExtension.class.getClassLoader()); + } catch (ClassNotFoundException | LinkageError e) { + return null; + } + } +} diff --git a/docs/features/MP_JWT.md b/docs/features/MP_JWT.md index 368c2a5c..b15b53e5 100644 --- a/docs/features/MP_JWT.md +++ b/docs/features/MP_JWT.md @@ -9,7 +9,7 @@ is a specification that standardizes OpenID Connect (OIDC) based JSON Web Tokens ## Sample MP JWT secured endpoint -Typically MP JWT is used to secure REST endpoints using the `@javax.annotation.security.RolesAllowed` annotation at either the class or method level. Suppose we have a REST endpoint secured with MP JWT as follows: +Typically MP JWT is used to secure REST endpoints using the `@jakarta.annotation.security.RolesAllowed` annotation at either the class or method level. Suppose we have a REST endpoint secured with MP JWT as follows: ```java @Path("/data") @@ -38,11 +38,12 @@ As the `@RolesAllowed` annotations imply, anyone can access the `GET /data/ping` ## Testing a MP JWT secured endpoint -When MicroShed Testing will automatically generate and configure a pair of JWT secrets for the `ApplicationContainer` container. Then a test client may access these endpoints using the `@JwtConfig` annotation on injected REST clients as follows: +### MicroShed RestClient +MicroShed Testing will automatically generate and configure a pair of JWT secrets for the `ApplicationContainer` container when a test client is annotated with: `@JwtConfig` on the injected REST clients as follows: ```java -import javax.ws.rs.ForbiddenException; -import javax.ws.rs.NotAuthorizedException; +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.NotAuthorizedException; import org.junit.jupiter.api.Test; import org.microshed.testing.jaxrs.RESTClient; @@ -94,6 +95,44 @@ In the above code example, the `securedSvc` REST client will be generated with t The `noJwtSecuredSvc` REST client will be generated with no JWT header, and the `misSecuredSvc` client will be generated with an invalid group claim. As a result, neither of these REST clients will be able to sucessfully access the `GET /data/users` secured endpoint, as expected. +### RestAssured +When using RestAssured, the `@JwtConfig` can be used on the test which will use RestAssured. MicroShed Testing will automatically generate and configure a pair of JWT secrets for the `ApplicationContainer` container. And injected a header in the RestAssured configuration, with: "Authorization: Bearer ": + +```java +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.NotAuthorizedException; + +import org.junit.jupiter.api.Test; +import org.microshed.testing.jaxrs.RESTClient; +import org.microshed.testing.jupiter.MicroShedTest; +import org.microshed.testing.jwt.JwtConfig; +import org.microshed.testing.testcontainers.ApplicationContainer; +import org.testcontainers.junit.jupiter.Container; + +@MicroShedTest +public class SecuredSvcIT { + + @Container + public static ApplicationContainer app = new ApplicationContainer() + .withAppContextRoot("/") + .withReadinessPath("/data/ping"); + + @Test + @JwtConfig(claims = {"groups=users"}) + public void givenAPersonResourceWhenUsingRASecuredEndPointWithCorrectGroupThen200() { + given().when().get("app/data").then().statusCode(200); + } + + @Test + @JwtConfig(claims = {"groups=wrong"}) + public void givenAPersonResourceWhenUsingRASecuredEndPointWithWrongGroupThen403() { + given().when().get("app/data").then().statusCode(403); + } +} +``` + +In the above code example, the `givenAPersonResourceWhenUsingRASecuredEndPointWithCorrectGroupThen200` test will be given an Authorization header, with the generated JWT key that has been configured on the `app` container, along with the group claim `users`. The result is that the `secureSvc` REST client can successfully access the `GET app/data` endpoint, which is restricted to clients in the `users` role. + ## Learning resources - [Tomitribe blog explaining MicroProfile JWT](https://www.tomitribe.com/blog/microprofile-json-web-token-jwt/) diff --git a/docs/features/RestAssured.md b/docs/features/RestAssured.md index f15a2a50..37a7193f 100644 --- a/docs/features/RestAssured.md +++ b/docs/features/RestAssured.md @@ -14,7 +14,7 @@ To enable REST Assured, add the following dependency to your pom.xml: io.rest-assured rest-assured - 4.2.0 + 5.4.0 test ``` @@ -105,3 +105,6 @@ import io.restassured.mapper.ObjectMapperType; ObjectMapperConfig omConfig = ObjectMapperConfig.objectMapperConfig().defaultObjectMapperType(ObjectMapperType.JACKSON_2); RestAssured.config = RestAssured.config.objectMapperConfig(omConfig); ``` +## JWT + +Autoconfiguration of JWT can be done in combination with the `jwtConfig` annotation. See [MicroProfile JWT](MP_JWT.md) \ No newline at end of file diff --git a/sample-apps/jaxrs-mpjwt/Dockerfile b/sample-apps/jaxrs-mpjwt/Dockerfile index a1f6e36b..2a64af60 100644 --- a/sample-apps/jaxrs-mpjwt/Dockerfile +++ b/sample-apps/jaxrs-mpjwt/Dockerfile @@ -1,3 +1,3 @@ -FROM openliberty/open-liberty:full-java11-openj9-ubi +FROM openliberty/open-liberty:23.0.0.12-full-java17-openj9-ubi COPY src/main/liberty/config /config/ ADD build/libs/myservice.war /config/dropins/ \ No newline at end of file diff --git a/sample-apps/jaxrs-mpjwt/build.gradle b/sample-apps/jaxrs-mpjwt/build.gradle index ee3123e3..ab786618 100644 --- a/sample-apps/jaxrs-mpjwt/build.gradle +++ b/sample-apps/jaxrs-mpjwt/build.gradle @@ -10,6 +10,7 @@ dependencies { testImplementation 'org.slf4j:slf4j-log4j12:2.0.9' testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' + testImplementation 'io.rest-assured:rest-assured:5.4.0' } war.archiveFileName.set 'myservice.war' diff --git a/sample-apps/jaxrs-mpjwt/src/main/java/org/example/app/SecuredService.java b/sample-apps/jaxrs-mpjwt/src/main/java/org/example/app/SecuredService.java index 6dbfe926..cf56b280 100644 --- a/sample-apps/jaxrs-mpjwt/src/main/java/org/example/app/SecuredService.java +++ b/sample-apps/jaxrs-mpjwt/src/main/java/org/example/app/SecuredService.java @@ -39,8 +39,6 @@ @Consumes(MediaType.APPLICATION_JSON) public class SecuredService { - - @Inject JsonWebToken callerPrincipal; diff --git a/sample-apps/jaxrs-mpjwt/src/test/java/org/example/app/SecuredSvcIT.java b/sample-apps/jaxrs-mpjwt/src/test/java/org/example/app/SecuredSvcIT.java index 1d8c1749..668a3bf6 100644 --- a/sample-apps/jaxrs-mpjwt/src/test/java/org/example/app/SecuredSvcIT.java +++ b/sample-apps/jaxrs-mpjwt/src/test/java/org/example/app/SecuredSvcIT.java @@ -18,12 +18,9 @@ */ package org.example.app; -import static org.junit.Assert.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; - import jakarta.ws.rs.ForbiddenException; import jakarta.ws.rs.NotAuthorizedException; - +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.microshed.testing.jaxrs.RESTClient; import org.microshed.testing.jupiter.MicroShedTest; @@ -31,20 +28,24 @@ import org.microshed.testing.testcontainers.ApplicationContainer; import org.testcontainers.junit.jupiter.Container; +import static io.restassured.RestAssured.given; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; + @MicroShedTest public class SecuredSvcIT { @Container public static ApplicationContainer app = new ApplicationContainer() - .withAppContextRoot("/myservice") - .withReadinessPath("/myservice/app/data/ping"); + .withAppContextRoot("/myservice") + .withReadinessPath("/myservice/app/data/ping"); @RESTClient - @JwtConfig(claims = { "groups=users" }) + @JwtConfig(claims = {"groups=users"}) public static SecuredService securedSvc; @RESTClient - @JwtConfig(claims = { "groups=wrong" }) + @JwtConfig(claims = {"groups=wrong"}) public static SecuredService misSecuredSvc; @RESTClient @@ -73,4 +74,30 @@ public void testGetSecuredInfoNoJwt() { assertThrows(NotAuthorizedException.class, () -> noJwtSecuredSvc.getSecuredInfo()); } + @Test + @DisplayName("Using RestAssured ensure a status code of 200 when accessing a PermitAll endpoint") + public void testRestAssuredGetHeaders() { + given().when().get("app/data/headers").then().statusCode(200); + } + + @Test + @DisplayName("Using RestAssured ensure a status code of 401 when accessing a secured endpoint without authorization") + public void testRAGetSecuredInfoNoJWT() { + given().when().get("app/data").then().statusCode(401); + } + + @Test + @DisplayName("Using RestAssured ensure a status code of 200 when accessing a secured endpoint with correct JwtConfig") + @JwtConfig(claims = {"groups=users"}) + public void testRAGetSecuredInfoCorrectJwt() { + given().when().get("app/data").then().statusCode(200); + } + + @Test + @DisplayName("Using RestAssured ensure a status code of 403 when accessing a secured endpoint with wrong JwtConfig") + @JwtConfig(claims = {"groups=wrong"}) + public void testRAGetSecuredInfoBadJwt() { + given().when().get("app/data").then().statusCode(403); + } + } \ No newline at end of file