Test for authorization code flow with consent
This commit is contained in:
parent
ea5c3a1c00
commit
1b08cdaf44
src
@ -9,13 +9,16 @@ import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
@ -81,6 +84,9 @@ public class AuthorizationServer extends SpringBootServletInitializer {
|
||||
http
|
||||
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
|
||||
.with(authorizationServerConfigurer, authorizationServer -> authorizationServer
|
||||
.authorizationEndpoint(authorizeEndpoint -> authorizeEndpoint
|
||||
.consentPage("/oauth2/consent"))
|
||||
.withObjectPostProcessor(enableGivingConsentWithNoScopes())
|
||||
.oidc(Customizer.withDefaults()))
|
||||
.with(new ShibbolethConfigurer(), Customizer.withDefaults())
|
||||
.sessionManagement(session -> session
|
||||
@ -105,6 +111,25 @@ public class AuthorizationServer extends SpringBootServletInitializer {
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/// The [OAuth2AuthorizationConsentAuthenticationProvider], which is the component
|
||||
/// that handles incoming consent, requires there to be at least one scope that is
|
||||
/// approved by the user. This is a problem since we want to allow the user to give
|
||||
/// consent without any scopes to enable, for example, just using this service as a
|
||||
/// way to authenticate the end user (getting access to the "sub" claim).
|
||||
///
|
||||
/// To get around this limitation, a dummy scope is added that is always approved.
|
||||
private ObjectPostProcessor<OAuth2AuthorizationConsentAuthenticationProvider> enableGivingConsentWithNoScopes() {
|
||||
return new ObjectPostProcessor<>() {
|
||||
@Override
|
||||
public <O extends OAuth2AuthorizationConsentAuthenticationProvider> O postProcess(final O object) {
|
||||
object.setAuthorizationConsentCustomizer(consentContext ->
|
||||
consentContext.getAuthorizationConsent()
|
||||
.authority(new SimpleGrantedAuthority("CONSENT")));
|
||||
return object;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public interface HttpSecurityCustomizer {
|
||||
void customize(HttpSecurity http) throws Exception;
|
||||
}
|
||||
|
96
src/test/java/se/su/dsv/oauth2/ConsentFlowTest.java
Normal file
96
src/test/java/se/su/dsv/oauth2/ConsentFlowTest.java
Normal file
@ -0,0 +1,96 @@
|
||||
package se.su.dsv.oauth2;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.testcontainers.containers.MariaDBContainer;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
import static se.su.dsv.oauth2.ShibbolethRequestProcessor.remoteUser;
|
||||
|
||||
@SpringBootTest
|
||||
public class ConsentFlowTest extends AbstractMetadataTest {
|
||||
|
||||
@ServiceConnection
|
||||
static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb:10.11");
|
||||
|
||||
@Test
|
||||
public void asks_end_user_for_consent() throws Exception {
|
||||
MvcResult authorizeResult = mockMvc.perform(get(getAuthorizationEndpoint())
|
||||
.with(remoteUser("some-end-user"))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", TestConfig.CLIENT_ID)
|
||||
.queryParam("redirect_uri", TestConfig.REDIRECT_URI))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrlPattern("**/oauth2/consent?**"))
|
||||
.andReturn();
|
||||
|
||||
UriComponents redirectUrl = parseUrl(authorizeResult.getResponse().getRedirectedUrl());
|
||||
String state = redirectUrl.getQueryParams().getFirst("state");
|
||||
String clientId = redirectUrl.getQueryParams().getFirst("client_id");
|
||||
|
||||
MvcResult consentResult = mockMvc.perform(post(getAuthorizationEndpoint())
|
||||
.with(remoteUser("some-end-user"))
|
||||
.formField("client_id", clientId)
|
||||
.formField("state", state))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrlPattern(TestConfig.REDIRECT_URI + "?**"))
|
||||
.andReturn();
|
||||
|
||||
UriComponents callbackUrl = parseUrl(consentResult.getResponse().getRedirectedUrl());
|
||||
|
||||
String code = callbackUrl.getQueryParams().getFirst("code");
|
||||
assertNotNull(code, "Should have received a one time authorization code");
|
||||
|
||||
}
|
||||
|
||||
private static UriComponents parseUrl(final String url) {
|
||||
assertNotNull(url);
|
||||
|
||||
String decodedUrl = URLDecoder.decode(url, StandardCharsets.UTF_8);
|
||||
return UriComponentsBuilder
|
||||
.fromUriString(decodedUrl)
|
||||
.build();
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
public static class TestConfig {
|
||||
public static final String CLIENT_ID = "client";
|
||||
public static final String CLIENT_SECRET = "secret";
|
||||
public static final String REDIRECT_URI = "http://localhost/login/oauth2/code/client";
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
// Required to override the default RegisteredClientRepository
|
||||
InMemoryRegisteredClientRepository testRegisteredClientRepository()
|
||||
{
|
||||
RegisteredClient registeredClient = RegisteredClient.withId("id")
|
||||
.clientId(CLIENT_ID)
|
||||
.clientSecret("{noop}" + CLIENT_SECRET)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUri(REDIRECT_URI)
|
||||
.clientSettings(ClientSettings.builder()
|
||||
.requireAuthorizationConsent(true)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
return new InMemoryRegisteredClientRepository(registeredClient);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user