浏览代码

Use HttpBasicConfigurer's Conneg Strategy

Closes gh-9100
Josh Cummings 4 年之前
父节点
当前提交
4602e9a661

+ 32 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

@@ -16,12 +16,15 @@
 
 
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.resource;
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.resource;
 
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
 
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContext;
 import org.springframework.core.convert.converter.Converter;
 import org.springframework.core.convert.converter.Converter;
+import org.springframework.http.MediaType;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManagerResolver;
 import org.springframework.security.authentication.AuthenticationManagerResolver;
@@ -48,8 +51,15 @@ import org.springframework.security.oauth2.server.resource.web.DefaultBearerToke
 import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
 import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.access.AccessDeniedHandler;
 import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.web.util.matcher.AndRequestMatcher;
+import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
+import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
+import org.springframework.security.web.util.matcher.OrRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
+import org.springframework.web.accept.ContentNegotiationStrategy;
+import org.springframework.web.accept.HeaderContentNegotiationStrategy;
 
 
 /**
 /**
  *
  *
@@ -130,6 +140,9 @@ import org.springframework.util.Assert;
 public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<H>>
 public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<H>>
 		extends AbstractHttpConfigurer<OAuth2ResourceServerConfigurer<H>, H> {
 		extends AbstractHttpConfigurer<OAuth2ResourceServerConfigurer<H>, H> {
 
 
+	private static final RequestHeaderRequestMatcher X_REQUESTED_WITH = new RequestHeaderRequestMatcher(
+			"X-Requested-With", "XMLHttpRequest");
+
 	private final ApplicationContext context;
 	private final ApplicationContext context;
 
 
 	private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
 	private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
@@ -273,7 +286,25 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 	private void registerDefaultEntryPoint(H http) {
 	private void registerDefaultEntryPoint(H http) {
 		ExceptionHandlingConfigurer<H> exceptionHandling = http.getConfigurer(ExceptionHandlingConfigurer.class);
 		ExceptionHandlingConfigurer<H> exceptionHandling = http.getConfigurer(ExceptionHandlingConfigurer.class);
 		if (exceptionHandling != null) {
 		if (exceptionHandling != null) {
-			exceptionHandling.defaultAuthenticationEntryPointFor(this.authenticationEntryPoint, this.requestMatcher);
+			ContentNegotiationStrategy contentNegotiationStrategy = http
+					.getSharedObject(ContentNegotiationStrategy.class);
+			if (contentNegotiationStrategy == null) {
+				contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
+			}
+			MediaTypeRequestMatcher restMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
+					MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON,
+					MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA,
+					MediaType.TEXT_XML);
+			restMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
+			MediaTypeRequestMatcher allMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy, MediaType.ALL);
+			allMatcher.setUseEquals(true);
+			RequestMatcher notHtmlMatcher = new NegatedRequestMatcher(
+					new MediaTypeRequestMatcher(contentNegotiationStrategy, MediaType.TEXT_HTML));
+			RequestMatcher restNotHtmlMatcher = new AndRequestMatcher(
+					Arrays.<RequestMatcher>asList(notHtmlMatcher, restMatcher));
+			RequestMatcher preferredMatcher = new OrRequestMatcher(
+					Arrays.asList(this.requestMatcher, X_REQUESTED_WITH, restNotHtmlMatcher, allMatcher));
+			exceptionHandling.defaultAuthenticationEntryPointFor(this.authenticationEntryPoint, preferredMatcher);
 		}
 		}
 	}
 	}
 
 

+ 40 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

@@ -89,6 +89,10 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
+import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
 import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
 import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2Error;
@@ -1108,7 +1112,8 @@ public class OAuth2ResourceServerConfigurerTests {
 		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
 		JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
 		given(decoder.decode(anyString())).willThrow(JwtException.class);
 		given(decoder.decode(anyString())).willThrow(JwtException.class);
 		// @formatter:off
 		// @formatter:off
-		MvcResult result = this.mvc.perform(get("/authenticated"))
+		MvcResult result = this.mvc.perform(get("/authenticated")
+				.header("Accept", "text/html"))
 				.andExpect(status().isFound())
 				.andExpect(status().isFound())
 				.andExpect(redirectedUrl("http://localhost/login"))
 				.andExpect(redirectedUrl("http://localhost/login"))
 				.andReturn();
 				.andReturn();
@@ -1122,6 +1127,15 @@ public class OAuth2ResourceServerConfigurerTests {
 		assertThat(result.getRequest().getSession(false)).isNull();
 		assertThat(result.getRequest().getSession(false)).isNull();
 	}
 	}
 
 
+	@Test
+	public void unauthenticatedRequestWhenFormOAuth2LoginAndResourceServerThenNegotiates() throws Exception {
+		this.spring.register(OAuth2LoginAndResourceServerConfig.class, JwtDecoderConfig.class).autowire();
+		this.mvc.perform(get("/any").header("X-Requested-With", "XMLHttpRequest")).andExpect(status().isUnauthorized());
+		this.mvc.perform(get("/any").header("Accept", "application/json")).andExpect(status().isUnauthorized());
+		this.mvc.perform(get("/any").header("Accept", "text/html")).andExpect(status().is3xxRedirection());
+		this.mvc.perform(get("/any").header("Accept", "image/jpg")).andExpect(status().is3xxRedirection());
+	}
+
 	@Test
 	@Test
 	public void requestWhenDefaultAndResourceServerAccessDeniedHandlersThenMatchedByRequest() throws Exception {
 	public void requestWhenDefaultAndResourceServerAccessDeniedHandlersThenMatchedByRequest() throws Exception {
 		this.spring
 		this.spring
@@ -1721,6 +1735,31 @@ public class OAuth2ResourceServerConfigurerTests {
 
 
 	}
 	}
 
 
+	@EnableWebSecurity
+	static class OAuth2LoginAndResourceServerConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeRequests((authz) -> authz
+					.anyRequest().authenticated()
+				)
+				.oauth2Login(withDefaults())
+				.oauth2ResourceServer((oauth2) -> oauth2
+					.jwt()
+				);
+			// @formatter:on
+		}
+
+		@Bean
+		ClientRegistrationRepository clients() {
+			ClientRegistration registration = TestClientRegistrations.clientRegistration().build();
+			return new InMemoryClientRegistrationRepository(registration);
+		}
+
+	}
+
 	@EnableWebSecurity
 	@EnableWebSecurity
 	static class JwtHalfConfiguredConfig extends WebSecurityConfigurerAdapter {
 	static class JwtHalfConfiguredConfig extends WebSecurityConfigurerAdapter {