Provide an embedded Docker container for local development #1
@ -113,7 +113,7 @@ public class ClientManager implements RegisteredClientRepository, ClientManageme
|
|||||||
// the client secret if necessary based on the PasswordEncoder bean.
|
// the client secret if necessary based on the PasswordEncoder bean.
|
||||||
@Override
|
@Override
|
||||||
public void save(final RegisteredClient registeredClient) {
|
public void save(final RegisteredClient registeredClient) {
|
||||||
throw new UnsupportedOperationException("ClientManager#save(RegisteredClient)");
|
// TODO fix support for upgrading client secrets
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -1,8 +1,23 @@
|
|||||||
package se.su.dsv.oauth2;
|
package se.su.dsv.oauth2;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.nimbusds.jose.JWSAlgorithm;
|
||||||
|
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||||
|
import com.nimbusds.jose.jwk.source.JWKSourceBuilder;
|
||||||
|
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
|
||||||
|
import com.nimbusds.jose.proc.SecurityContext;
|
||||||
|
import com.nimbusds.jose.util.DefaultResourceRetriever;
|
||||||
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
|
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
@ -10,33 +25,49 @@ import org.testcontainers.images.builder.ImageFromDockerfile;
|
|||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
@Testcontainers
|
@Testcontainers
|
||||||
public class EmbeddedContainerTest {
|
public class EmbeddedContainerTest {
|
||||||
|
|
||||||
|
private static final String CLIENT_ID = "client-id";
|
||||||
|
private static final String CLIENT_SECRET = "client-secret";
|
||||||
|
private static final String CLIENT_REDIRECT_URI = "http://localhost:8080";
|
||||||
|
private static final String AUTHORIZATION_HEADER = "Basic " + HttpHeaders.encodeBasicAuth(CLIENT_ID, CLIENT_SECRET, null);
|
||||||
|
|
||||||
@Container
|
@Container
|
||||||
static GenericContainer<?> container = new GenericContainer<>(
|
static GenericContainer<?> container = new GenericContainer<>(
|
||||||
new ImageFromDockerfile()
|
new ImageFromDockerfile()
|
||||||
.withFileFromPath(".", Paths.get(".")))
|
.withFileFromPath(".", Paths.get(".")))
|
||||||
.withExposedPorts(8080)
|
.withExposedPorts(8080)
|
||||||
.withEnv("CLIENT_ID", "client-id")
|
.withEnv("CLIENT_ID", CLIENT_ID)
|
||||||
.withEnv("CLIENT_SECRET", "client-secret")
|
.withEnv("CLIENT_SECRET", CLIENT_SECRET)
|
||||||
.withEnv("CLIENT_REDIRECT_URI", "http://localhost:8080")
|
.withEnv("CLIENT_REDIRECT_URI", CLIENT_REDIRECT_URI)
|
||||||
.withEnv("CLIENT_SCOPES", "openid profile email");
|
.withEnv("CLIENT_SCOPES", "openid profile email");
|
||||||
|
|
||||||
@Test
|
private RestClient restClient;
|
||||||
public void working_container() {
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
String baseUri = UriComponentsBuilder.newInstance()
|
String baseUri = UriComponentsBuilder.newInstance()
|
||||||
.scheme("http")
|
.scheme("http")
|
||||||
.host(container.getHost())
|
.host(container.getHost())
|
||||||
.port(container.getMappedPort(8080))
|
.port(container.getMappedPort(8080))
|
||||||
.toUriString();
|
.toUriString();
|
||||||
|
|
||||||
RestClient restClient = RestClient.create(baseUri);
|
restClient = RestClient.create(baseUri);
|
||||||
|
objectMapper = new ObjectMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void working_container() {
|
||||||
ResponseEntity<String> response = restClient
|
ResponseEntity<String> response = restClient
|
||||||
.get()
|
.get()
|
||||||
.retrieve()
|
.retrieve()
|
||||||
@ -49,4 +80,83 @@ public class EmbeddedContainerTest {
|
|||||||
assertThat(response.getBody())
|
assertThat(response.getBody())
|
||||||
.contains("DSV");
|
.contains("DSV");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void custom_authorize_flow_via_metadata_and_public_key_verification() throws Exception {
|
||||||
|
String metadata = restClient.get()
|
||||||
|
.uri("/.well-known/oauth-authorization-server")
|
||||||
|
.retrieve()
|
||||||
|
.body(String.class);
|
||||||
|
|
||||||
|
JsonNode parsedMetadata = objectMapper.readTree(metadata);
|
||||||
|
|
||||||
|
// 2. Get JWKS
|
||||||
|
URL jwksUri = URI.create(parsedMetadata.required("jwks_uri").asText()).toURL();
|
||||||
|
JWKSource<SecurityContext> jwkSource = JWKSourceBuilder
|
||||||
|
.create(jwksUri, new DefaultResourceRetriever())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final DefaultJWTProcessor<SecurityContext> processor = new DefaultJWTProcessor<>();
|
||||||
|
JWSAlgorithm acceptedAlgorithms = JWSAlgorithm.RS256;
|
||||||
|
JWSVerificationKeySelector<SecurityContext> keySelector =
|
||||||
|
new JWSVerificationKeySelector<>(acceptedAlgorithms, jwkSource);
|
||||||
|
processor.setJWSKeySelector(keySelector);
|
||||||
|
|
||||||
|
final MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
|
||||||
|
form.put("principal", List.of("test"));
|
||||||
|
|
||||||
|
String authorizationEndpoint = parsedMetadata.required("authorization_endpoint").asText();
|
||||||
|
String authorizeUri = UriComponentsBuilder.fromUriString(authorizationEndpoint)
|
||||||
|
.queryParam("response_type", "code")
|
||||||
|
.queryParam("client_id", CLIENT_ID)
|
||||||
|
.queryParam("redirect_uri", CLIENT_REDIRECT_URI)
|
||||||
|
.queryParam("scope", "openid profile email")
|
||||||
|
.build()
|
||||||
|
.toUriString();
|
||||||
|
|
||||||
|
ResponseEntity<Void> authorizationResponse = restClient.post()
|
||||||
|
.uri(authorizeUri)
|
||||||
|
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
|
.body(form)
|
||||||
|
.retrieve()
|
||||||
|
.toBodilessEntity();
|
||||||
|
|
||||||
|
URI redirectLocation = authorizationResponse.getHeaders().getLocation();
|
||||||
|
assertThat(redirectLocation).isNotNull();
|
||||||
|
|
||||||
|
String query = redirectLocation.getQuery();
|
||||||
|
assertThat(query).isNotBlank();
|
||||||
|
|
||||||
|
String code = query.substring("code=".length());
|
||||||
|
assertThat(code).isNotBlank();
|
||||||
|
|
||||||
|
LinkedMultiValueMap<Object, Object> tokenRequestBody = new LinkedMultiValueMap<>();
|
||||||
|
tokenRequestBody.add("code", code);
|
||||||
|
tokenRequestBody.add("grant_type", "authorization_code");
|
||||||
|
tokenRequestBody.add("redirect_uri", CLIENT_REDIRECT_URI);
|
||||||
|
|
||||||
|
TokenResponse tokenResponse = restClient.post()
|
||||||
|
.uri(parsedMetadata.required("token_endpoint").asText())
|
||||||
|
.header("Authorization", AUTHORIZATION_HEADER)
|
||||||
|
.body(tokenRequestBody)
|
||||||
|
.retrieve()
|
||||||
|
.body(TokenResponse.class);
|
||||||
|
|
||||||
|
assertThat(tokenResponse).isNotNull();
|
||||||
|
assertThat(tokenResponse.accessToken()).isNotBlank();
|
||||||
|
assertThat(tokenResponse.idToken()).isNotBlank();
|
||||||
|
|
||||||
|
JWTClaimsSet accessTokenClaims = assertDoesNotThrow(
|
||||||
|
() -> processor.process(tokenResponse.accessToken(), null),
|
||||||
|
"Failed to verify access token");
|
||||||
|
|
||||||
|
assertThat(accessTokenClaims.getSubject()).isEqualTo("test");
|
||||||
|
|
||||||
|
JWTClaimsSet idTokenClaims = assertDoesNotThrow(
|
||||||
|
() -> processor.process(tokenResponse.idToken(), null),
|
||||||
|
"Failed to verify id token");
|
||||||
|
|
||||||
|
assertThat(accessTokenClaims.getSubject()).isEqualTo("test");
|
||||||
|
assertThat(idTokenClaims.getSubject()).isEqualTo("test");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user