Skip to content

Latest commit

 

History

History
232 lines (171 loc) · 8.38 KB

File metadata and controls

232 lines (171 loc) · 8.38 KB

Token-Sheriff Core Test Utilities

1. Generators Artifact

The project provides a test artifact with the classifier generators containing utility classes for testing JWT validation.

1.1. Maven Dependency

See Test Artifact Dependencies in the main module documentation.

1.2. Contents

  • de.cuioss.sheriff.token.validation.test - Core test utilities

  • de.cuioss.sheriff.token.validation.test.dispatcher - MockWebServer dispatchers: discovery/JWKS (WellKnownDispatcher, JwksResolveDispatcher, EnhancedJwksResolveDispatcher, MultiIssuerJwksDispatcher) and the OP-endpoint dispatchers (TokenDispatcher, UserInfoDispatcher, RevocationDispatcher, IntrospectionDispatcher, EndSessionDispatcher, ParDispatcher), plus the shared AdversarialResponses payloads

  • de.cuioss.sheriff.token.validation.test.generator - Token and claim generators

  • de.cuioss.sheriff.token.validation.test.junit - JUnit 5 extensions and annotations (@TestTokenSource)

2. Key Test Utilities

2.1. TestTokenHolder

Comprehensive TokenContent implementation for testing with dynamic token generation.

2.1.1. JWT Semantic Constants

public static final String TEST_AUDIENCE = "test-audience";
public static final String TEST_CLIENT_ID = "test-client-app";
public static final String TEST_ISSUER = "Token-Test-testIssuer";

These constants model the semantic distinctions required by RFC 7519, RFC 9068, and OpenID Connect Core.

2.1.2. Usage Examples

TestTokenHolder tokenHolder = new TestTokenHolder(TokenType.ACCESS_TOKEN,
    ClaimControlParameter.defaultForTokenType(TokenType.ACCESS_TOKEN));

String token = tokenHolder.getRawToken();
tokenHolder.withClaim("custom-claim", ClaimValue.forPlainString("custom-value"));
tokenHolder.withoutClaim("sub");
IssuerConfig issuerConfig = tokenHolder.getIssuerConfig();

2.1.3. Key Features

  • Dynamic Token Generation: Creates JWT tokens on demand with configurable claims

  • Claim Manipulation: Add, remove, or modify claims

  • Token Type Support: ACCESS_TOKEN, ID_TOKEN, REFRESH_TOKEN

  • IssuerConfig Generation: Creates matching IssuerConfig for the token

  • Key Material Integration: Uses InMemoryKeyMaterialHandler for signing and verification

2.2. ClaimControlParameter

Controls which claims are included or excluded when generating tokens:

ClaimControlParameter params = ClaimControlParameter.builder()
    .missingIssuer(true)
    .missingSubject(true)
    .build();

TestTokenHolder tokenHolder = new TestTokenHolder(TokenType.ACCESS_TOKEN, params);
ClaimControlParameter defaultParams = ClaimControlParameter.defaultForTokenType(TokenType.ID_TOKEN);

2.3. TestTokenGenerators

Factory methods for creating TypedGenerator instances producing TestTokenHolder objects, built on the cui-test-generator framework:

TypedGenerator<TestTokenHolder> accessTokenGenerator = TestTokenGenerators.accessTokens();
TestTokenHolder accessToken = accessTokenGenerator.next();

TypedGenerator<TestTokenHolder> idTokenGenerator = TestTokenGenerators.idTokens();
TypedGenerator<TestTokenHolder> refreshTokenGenerator = TestTokenGenerators.refreshTokens();

2.4. TestTokenSource (Preferred Approach)

The @TestTokenSource annotation is the preferred way to inject test tokens into parameterized tests:

@ParameterizedTest
@TestTokenSource(value = TokenType.ACCESS_TOKEN, count = 5)
@DisplayName("Test with access token")
void shouldTestWithAccessToken(TestTokenHolder tokenHolder) {
    String token = tokenHolder.getRawToken();

    AccessTokenContent result = TokenValidator.builder()
            .issuerConfig(tokenHolder.getIssuerConfig())
            .build()
            .createAccessToken(AccessTokenRequest.of(token));

    assertNotNull(result);
    assertEquals(TestTokenHolder.TEST_ISSUER, result.getIssuer());
}

3. Usage Examples

3.1. Testing Token Validation

@Test
void shouldValidateToken() {
    TestTokenHolder tokenHolder = TestTokenGenerators.accessTokens().next();
    String token = tokenHolder.getRawToken();

    AccessTokenContent result = TokenValidator.builder()
            .issuerConfig(tokenHolder.getIssuerConfig())
            .build()
            .createAccessToken(AccessTokenRequest.of(token));

    assertNotNull(result);
    assertEquals(tokenHolder.getClaims().get("sub").getOriginalString(), result.getSubject().orElseThrow());
}

3.2. Testing Invalid Tokens

@Test
void shouldRejectTokenWithMissingClaims() {
    ClaimControlParameter params = ClaimControlParameter.builder()
        .missingIssuer(true)
        .build();

    TestTokenHolder tokenHolder = new TestTokenHolder(TokenType.ACCESS_TOKEN, params);
    IssuerConfig issuerConfig = IssuerConfig.builder()
        .issuerIdentifier(TestTokenHolder.TEST_ISSUER)
        .expectedAudience(TestTokenHolder.TEST_AUDIENCE)
        .expectedClientId(TestTokenHolder.TEST_CLIENT_ID)
        .jwksContent(InMemoryJWKSFactory.createDefaultJwks())
        .build();

    TokenValidator validator = TokenValidator.builder()
            .issuerConfig(issuerConfig)
            .build();

    TokenValidationException exception = assertThrows(TokenValidationException.class,
        () -> validator.createAccessToken(AccessTokenRequest.of(tokenHolder.getRawToken())));

    assertEquals(SecurityEventCounter.EventType.MISSING_CLAIM, exception.getEventType());
}

4. Testing Code Using OIDC Discovery (HttpWellKnownResolver)

The WellKnownDispatcher utility simulates an OpenID Provider’s /.well-known/openid-configuration and JWKS endpoints for testing without network calls. It implements ModuleDispatcherElement from cui-test-mockwebserver-junit5.

4.1. Using WellKnownDispatcher with JUnit 5 Tests

@EnableMockWebServer
class MyServiceUsingWellKnownTest {

    @Getter
    private final WellKnownDispatcher moduleDispatcher = new WellKnownDispatcher();

    @BeforeEach
    void setUp() {
        moduleDispatcher.returnDefault();
        moduleDispatcher.setCallCounter(0);
    }

    @Test
    void testSuccessfulOidcDiscovery(URIBuilder uriBuilder) throws Exception {
        moduleDispatcher.returnDefault();

        String baseUrl = uriBuilder.buildAsString();
        String wellKnownUrl = uriBuilder.addPathSegment(".well-known")
                .addPathSegment("openid-configuration").buildAsString();

        WellKnownConfig wellKnownConfig = WellKnownConfig.builder()
            .wellKnownUrl(wellKnownUrl)
            .retryConfig(RetryConfig.builder().maxAttempts(1).build())
            .build();
        HttpWellKnownResolver resolver = wellKnownConfig.createResolver();

        assertEquals(baseUrl, resolver.getIssuer().orElseThrow());
        moduleDispatcher.assertCallsAnswered(1);
    }

    @Test
    void testOidcDiscoveryError(URIBuilder uriBuilder) throws Exception {
        moduleDispatcher.returnError();

        String wellKnownUrl = uriBuilder.addPathSegment(".well-known")
                .addPathSegment("openid-configuration").buildAsString();

        WellKnownConfig wellKnownConfig = WellKnownConfig.builder()
            .wellKnownUrl(wellKnownUrl)
            .retryConfig(RetryConfig.builder().maxAttempts(1).build())
            .build();
        HttpWellKnownResolver resolver = wellKnownConfig.createResolver();

        // getIssuer() returns empty Optional when discovery fails
        assertTrue(resolver.getIssuer().isEmpty());
        assertEquals(LoaderStatus.ERROR, resolver.getLoaderStatus());
    }
}

4.2. Key WellKnownDispatcher Methods

  • Configuration: returnDefault(), returnError(), returnInvalidJson(), returnMissingIssuer(), returnMissingJwksUri(), returnInvalidIssuer(), returnOnlyRequiredFields()

  • Verification: assertCallsAnswered(int), getCallCounter(), setCallCounter(int)

The dispatcher dynamically generates URLs based on incoming requests, ensuring issuer and jwks_uri values match the server’s base URL.

4.3. Integration with cui-test-mockwebserver-junit5

The framework provides automatic server management, dynamic port assignment, URIBuilder parameter injection, and HTTPS support. See the cui-test-mockwebserver-junit5 repository for details.