Fix redirect to login page for token requests

Fixes an issue where auto-configuration for Spring Authorization Server
was overriding the default exception handling (AuthenticationEntryPoint)
resulting in anonymous requests to the token endpoint being redirected
to the Spring Security login page instead of returning 401 Unauthorized.

Auto-configuration now registers a defaultAuthenticationEntryPointFor
that is added to any other entry points already configured.

See gh-35368
pull/35417/head
Steve Riesenberg 2 years ago committed by Moritz Halbritter
parent 19b81c247c
commit 10feecbd08

@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.security.oauth2.server.servlet; 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.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity; import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.autoconfigure.security.SecurityProperties; 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.context.annotation.Configuration;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; 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.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; 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; import static org.springframework.security.config.Customizer.withDefaults;
@ -49,8 +54,8 @@ class OAuth2AuthorizationServerWebSecurityConfiguration {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(withDefaults()); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(withDefaults());
http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults())); http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults()));
http.exceptionHandling( http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(
(exceptions) -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))); new LoginUrlAuthenticationEntryPoint("/login"), createRequestMatcher()));
return http.build(); return http.build();
} }
@ -61,4 +66,10 @@ class OAuth2AuthorizationServerWebSecurityConfiguration {
return http.build(); return http.build();
} }
private static RequestMatcher createRequestMatcher() {
MediaTypeRequestMatcher requestMatcher = new MediaTypeRequestMatcher(MediaType.TEXT_HTML);
requestMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
return requestMatcher;
}
} }

@ -17,6 +17,7 @@
package smoketest.oauth2.server; package smoketest.oauth2.server;
import java.net.URI; import java.net.URI;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -31,6 +32,7 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
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.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AccessToken;
@ -118,4 +120,49 @@ class SampleOAuth2AuthorizationServerApplicationTests {
.isEqualTo(OAuth2AccessToken.TokenType.BEARER.getValue()); .isEqualTo(OAuth2AccessToken.TokenType.BEARER.getValue());
} }
@Test
void anonymousTokenRequestShouldReturnUnauthorized() {
HttpHeaders headers = new HttpHeaders();
HttpEntity<Object> 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<Map<String, Object>> 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<Object> 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<Map<String, Object>> 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<Object> 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<Map<String, Object>> 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"));
}
} }

Loading…
Cancel
Save