diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfiguration.java index c3e60f807e..5c36f2b462 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.security.oauth2.server.servlet; +import java.util.Set; + import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity; import org.springframework.boot.autoconfigure.security.SecurityProperties; @@ -23,6 +25,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; @@ -30,6 +33,8 @@ import org.springframework.security.oauth2.server.authorization.config.annotatio import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; import static org.springframework.security.config.Customizer.withDefaults; @@ -49,8 +54,8 @@ class OAuth2AuthorizationServerWebSecurityConfiguration { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(withDefaults()); http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults())); - http.exceptionHandling( - (exceptions) -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))); + http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor( + new LoginUrlAuthenticationEntryPoint("/login"), createRequestMatcher())); return http.build(); } @@ -61,4 +66,10 @@ class OAuth2AuthorizationServerWebSecurityConfiguration { return http.build(); } + private static RequestMatcher createRequestMatcher() { + MediaTypeRequestMatcher requestMatcher = new MediaTypeRequestMatcher(MediaType.TEXT_HTML); + requestMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL)); + return requestMatcher; + } + } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-oauth2-authorization-server/src/test/java/smoketest/oauth2/server/SampleOAuth2AuthorizationServerApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-oauth2-authorization-server/src/test/java/smoketest/oauth2/server/SampleOAuth2AuthorizationServerApplicationTests.java index ce2d7b75f0..0ecf890c9f 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-oauth2-authorization-server/src/test/java/smoketest/oauth2/server/SampleOAuth2AuthorizationServerApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-oauth2-authorization-server/src/test/java/smoketest/oauth2/server/SampleOAuth2AuthorizationServerApplicationTests.java @@ -17,6 +17,7 @@ package smoketest.oauth2.server; import java.net.URI; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -31,6 +32,7 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -118,4 +120,49 @@ class SampleOAuth2AuthorizationServerApplicationTests { .isEqualTo(OAuth2AccessToken.TokenType.BEARER.getValue()); } + @Test + void anonymousTokenRequestShouldReturnUnauthorized() { + HttpHeaders headers = new HttpHeaders(); + HttpEntity request = new HttpEntity<>(headers); + String requestUri = UriComponentsBuilder.fromUriString("/token") + .queryParam(OAuth2ParameterNames.CLIENT_ID, "messaging-client") + .queryParam(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) + .queryParam(OAuth2ParameterNames.SCOPE, "message.read+message.write") + .toUriString(); + ResponseEntity> entity = this.restTemplate.exchange(requestUri, HttpMethod.POST, request, + MAP_TYPE_REFERENCE); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } + + @Test + void anonymousTokenRequestWithAcceptHeaderAllShouldReturnUnauthorized() { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(List.of(MediaType.ALL)); + HttpEntity request = new HttpEntity<>(headers); + String requestUri = UriComponentsBuilder.fromUriString("/token") + .queryParam(OAuth2ParameterNames.CLIENT_ID, "messaging-client") + .queryParam(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) + .queryParam(OAuth2ParameterNames.SCOPE, "message.read+message.write") + .toUriString(); + ResponseEntity> entity = this.restTemplate.exchange(requestUri, HttpMethod.POST, request, + MAP_TYPE_REFERENCE); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } + + @Test + void anonymousTokenRequestWithAcceptHeaderTextHtmlShouldRedirectToLogin() { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(List.of(MediaType.TEXT_HTML)); + HttpEntity request = new HttpEntity<>(headers); + String requestUri = UriComponentsBuilder.fromUriString("/token") + .queryParam(OAuth2ParameterNames.CLIENT_ID, "messaging-client") + .queryParam(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) + .queryParam(OAuth2ParameterNames.SCOPE, "message.read+message.write") + .toUriString(); + ResponseEntity> entity = this.restTemplate.exchange(requestUri, HttpMethod.POST, request, + MAP_TYPE_REFERENCE); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(entity.getHeaders().getLocation()).isEqualTo(URI.create("http://localhost:" + this.port + "/login")); + } + }