mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-30 22:28:46 +00:00 
			
		
		
		
	BearerTokenServerAuthenticationEntryPoint
Issue: gh-5605
This commit is contained in:
		
							parent
							
								
									da73242d60
								
							
						
					
					
						commit
						4f417f01a7
					
				| @ -0,0 +1,121 @@ | ||||
| /* | ||||
|  * Copyright 2002-2018 the original author or authors. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package org.springframework.security.oauth2.server.resource.web.server; | ||||
| 
 | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.server.reactive.ServerHttpResponse; | ||||
| import org.springframework.security.core.AuthenticationException; | ||||
| import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | ||||
| import org.springframework.security.oauth2.core.OAuth2Error; | ||||
| import org.springframework.security.oauth2.server.resource.BearerTokenError; | ||||
| import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; | ||||
| import org.springframework.security.web.AuthenticationEntryPoint; | ||||
| import org.springframework.security.web.server.ServerAuthenticationEntryPoint; | ||||
| import org.springframework.util.StringUtils; | ||||
| import org.springframework.web.server.ServerWebExchange; | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * An {@link AuthenticationEntryPoint} implementation used to commence authentication of protected resource requests | ||||
|  * using {@link BearerTokenAuthenticationFilter}. | ||||
|  * <p> | ||||
|  * Uses information provided by {@link BearerTokenError} to set HTTP response status code and populate | ||||
|  * {@code WWW-Authenticate} HTTP header. | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 5.1 | ||||
|  * @see BearerTokenError | ||||
|  * @see <a href="https://tools.ietf.org/html/rfc6750#section-3" target="_blank">RFC 6750 Section 3: The WWW-Authenticate | ||||
|  * Response Header Field</a> | ||||
|  */ | ||||
| public final class BearerTokenServerAuthenticationEntryPoint implements | ||||
| 		ServerAuthenticationEntryPoint { | ||||
| 
 | ||||
| 	private String realmName; | ||||
| 
 | ||||
| 	public void setRealmName(String realmName) { | ||||
| 		this.realmName = realmName; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException authException) { | ||||
| 		HttpStatus status = getStatus(authException); | ||||
| 
 | ||||
| 		Map<String, String> parameters = createParameters(authException); | ||||
| 		String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters); | ||||
| 		ServerHttpResponse response = exchange.getResponse(); | ||||
| 		response.getHeaders().set(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate); | ||||
| 		response.setStatusCode(status); | ||||
| 		return response.setComplete(); | ||||
| 	} | ||||
| 
 | ||||
| 	private Map<String, String> createParameters(AuthenticationException authException) { | ||||
| 		Map<String, String> parameters = new LinkedHashMap<>(); | ||||
| 		if (this.realmName != null) { | ||||
| 			parameters.put("realm", this.realmName); | ||||
| 		} | ||||
| 
 | ||||
| 		if (authException instanceof OAuth2AuthenticationException) { | ||||
| 			OAuth2Error error = ((OAuth2AuthenticationException) authException).getError(); | ||||
| 
 | ||||
| 			parameters.put("error", error.getErrorCode()); | ||||
| 
 | ||||
| 			if (StringUtils.hasText(error.getDescription())) { | ||||
| 				parameters.put("error_description", error.getDescription()); | ||||
| 			} | ||||
| 
 | ||||
| 			if (StringUtils.hasText(error.getUri())) { | ||||
| 				parameters.put("error_uri", error.getUri()); | ||||
| 			} | ||||
| 
 | ||||
| 			if (error instanceof BearerTokenError) { | ||||
| 				BearerTokenError bearerTokenError = (BearerTokenError) error; | ||||
| 
 | ||||
| 				if (StringUtils.hasText(bearerTokenError.getScope())) { | ||||
| 					parameters.put("scope", bearerTokenError.getScope()); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return parameters; | ||||
| 	} | ||||
| 
 | ||||
| 	private HttpStatus getStatus(AuthenticationException authException) { | ||||
| 		if (authException instanceof OAuth2AuthenticationException) { | ||||
| 			OAuth2Error error = ((OAuth2AuthenticationException) authException).getError(); | ||||
| 			if (error instanceof BearerTokenError) { | ||||
| 				return ((BearerTokenError) error).getHttpStatus(); | ||||
| 			} | ||||
| 		} | ||||
| 		return HttpStatus.UNAUTHORIZED; | ||||
| 	} | ||||
| 
 | ||||
| 	private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) { | ||||
| 		String wwwAuthenticate = "Bearer"; | ||||
| 		if (!parameters.isEmpty()) { | ||||
| 			wwwAuthenticate += parameters.entrySet().stream() | ||||
| 					.map(attribute -> attribute.getKey() + "=\"" + attribute.getValue() + "\"") | ||||
| 					.collect(Collectors.joining(", ", " ", "")); | ||||
| 		} | ||||
| 
 | ||||
| 		return wwwAuthenticate; | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,97 @@ | ||||
| /* | ||||
|  * Copyright 2002-2018 the original author or authors. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package org.springframework.security.oauth2.server.resource.web.server; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.mock.http.server.reactive.MockServerHttpRequest; | ||||
| import org.springframework.mock.http.server.reactive.MockServerHttpResponse; | ||||
| import org.springframework.mock.web.server.MockServerWebExchange; | ||||
| import org.springframework.security.authentication.BadCredentialsException; | ||||
| import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | ||||
| import org.springframework.security.oauth2.core.OAuth2Error; | ||||
| import org.springframework.security.oauth2.core.OAuth2ErrorCodes; | ||||
| import org.springframework.security.oauth2.server.resource.BearerTokenError; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.*; | ||||
| 
 | ||||
| /** | ||||
|  * @author Rob Winch | ||||
|  * @since 5.1 | ||||
|  */ | ||||
| public class BearerTokenServerAuthenticationEntryPointTests { | ||||
| 	private BearerTokenServerAuthenticationEntryPoint entryPoint = new BearerTokenServerAuthenticationEntryPoint(); | ||||
| 
 | ||||
| 	private MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")); | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void commenceWhenNotOAuth2AuthenticationExceptionThenBearer() { | ||||
| 		this.entryPoint.commence(this.exchange, new BadCredentialsException("")).block(); | ||||
| 
 | ||||
| 		assertThat(getResponse().getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer"); | ||||
| 		assertThat(getResponse().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void commenceWhenRealmNameThenHasRealmName() { | ||||
| 		this.entryPoint.setRealmName("Realm"); | ||||
| 
 | ||||
| 		this.entryPoint.commence(this.exchange, new BadCredentialsException("")).block(); | ||||
| 
 | ||||
| 		assertThat(getResponse().getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer realm=\"Realm\""); | ||||
| 		assertThat(getResponse().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void commenceWhenOAuth2AuthenticationExceptionThenContainsErrorInformation() { | ||||
| 		OAuth2Error oauthError = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST); | ||||
| 		OAuth2AuthenticationException exception = new OAuth2AuthenticationException(oauthError); | ||||
| 
 | ||||
| 		this.entryPoint.commence(this.exchange, exception).block(); | ||||
| 
 | ||||
| 		assertThat(getResponse().getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer error=\"invalid_request\""); | ||||
| 		assertThat(getResponse().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void commenceWhenOAuth2ErrorCompleteThenContainsErrorInformation() { | ||||
| 		OAuth2Error oauthError = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "Oops", "https://example.com"); | ||||
| 		OAuth2AuthenticationException exception = new OAuth2AuthenticationException(oauthError); | ||||
| 
 | ||||
| 		this.entryPoint.commence(this.exchange, exception).block(); | ||||
| 
 | ||||
| 		assertThat(getResponse().getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer error=\"invalid_request\", error_description=\"Oops\", error_uri=\"https://example.com\""); | ||||
| 		assertThat(getResponse().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void commenceWhenBearerTokenThenErrorInformation() { | ||||
| 		OAuth2Error oauthError = new BearerTokenError(OAuth2ErrorCodes.INVALID_REQUEST, | ||||
| 				HttpStatus.BAD_REQUEST, "Oops", "https://example.com"); | ||||
| 		OAuth2AuthenticationException exception = new OAuth2AuthenticationException(oauthError); | ||||
| 
 | ||||
| 		this.entryPoint.commence(this.exchange, exception).block(); | ||||
| 
 | ||||
| 		assertThat(getResponse().getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer error=\"invalid_request\", error_description=\"Oops\", error_uri=\"https://example.com\""); | ||||
| 		assertThat(getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); | ||||
| 	} | ||||
| 
 | ||||
| 	private MockServerHttpResponse getResponse() { | ||||
| 		return this.exchange.getResponse(); | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user