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
This commit is contained in:
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…
Reference in New Issue