mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 09:12:14 +00:00
Fixed misleading OAuth2 error messages
Error messages sent by BearerTokenAccessDeniedHandler included information about the scopes of the rejected token instead of the scopes required by the resource. * Removal of token scopes from error_description attribute. * Removal of scope attribute from WWW-Authenticate response header. Fixes gh-7089
This commit is contained in:
parent
b153d92b23
commit
e8dd1325fd
@ -108,7 +108,6 @@ import org.springframework.test.web.servlet.ResultMatcher;
|
|||||||
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -397,7 +396,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
this.mvc.perform(get("/requires-read-scope")
|
this.mvc.perform(get("/requires-read-scope")
|
||||||
.with(bearerToken(token)))
|
.with(bearerToken(token)))
|
||||||
.andExpect(status().isForbidden())
|
.andExpect(status().isForbidden())
|
||||||
.andExpect(insufficientScopeHeader(""));
|
.andExpect(insufficientScopeHeader());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -411,7 +410,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
this.mvc.perform(get("/requires-read-scope")
|
this.mvc.perform(get("/requires-read-scope")
|
||||||
.with(bearerToken(token)))
|
.with(bearerToken(token)))
|
||||||
.andExpect(status().isForbidden())
|
.andExpect(status().isForbidden())
|
||||||
.andExpect(insufficientScopeHeader("message:write"));
|
.andExpect(insufficientScopeHeader());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -497,7 +496,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
this.mvc.perform(get("/ms-requires-read-scope")
|
this.mvc.perform(get("/ms-requires-read-scope")
|
||||||
.with(bearerToken(token)))
|
.with(bearerToken(token)))
|
||||||
.andExpect(status().isForbidden())
|
.andExpect(status().isForbidden())
|
||||||
.andExpect(insufficientScopeHeader(""));
|
.andExpect(insufficientScopeHeader());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,7 +511,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
this.mvc.perform(get("/ms-requires-read-scope")
|
this.mvc.perform(get("/ms-requires-read-scope")
|
||||||
.with(bearerToken(token)))
|
.with(bearerToken(token)))
|
||||||
.andExpect(status().isForbidden())
|
.andExpect(status().isForbidden())
|
||||||
.andExpect(insufficientScopeHeader("message:write"));
|
.andExpect(insufficientScopeHeader());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -526,7 +525,7 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
this.mvc.perform(get("/ms-deny")
|
this.mvc.perform(get("/ms-deny")
|
||||||
.with(bearerToken(token)))
|
.with(bearerToken(token)))
|
||||||
.andExpect(status().isForbidden())
|
.andExpect(status().isForbidden())
|
||||||
.andExpect(insufficientScopeHeader("message:read"));
|
.andExpect(insufficientScopeHeader());
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Resource Server should not engage csrf
|
// -- Resource Server should not engage csrf
|
||||||
@ -2230,12 +2229,11 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResultMatcher insufficientScopeHeader(String scope) {
|
private static ResultMatcher insufficientScopeHeader() {
|
||||||
return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " +
|
return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " +
|
||||||
"error=\"insufficient_scope\"" +
|
"error=\"insufficient_scope\"" +
|
||||||
", error_description=\"The token provided has insufficient scope [" + scope + "] for this request\"" +
|
", error_description=\"The request requires higher privileges than provided by the access token.\"" +
|
||||||
", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"" +
|
", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"");
|
||||||
(StringUtils.hasText(scope) ? ", scope=\"" + scope + "\"" : ""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mockWebServer(String response) {
|
private void mockWebServer(String response) {
|
||||||
|
@ -16,16 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.security.oauth2.server.resource.web.access;
|
package org.springframework.security.oauth2.server.resource.web.access;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
@ -33,7 +23,14 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
|
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates any {@link AccessDeniedException} into an HTTP response in accordance with
|
* Translates any {@link AccessDeniedException} into an HTTP response in accordance with
|
||||||
@ -48,9 +45,6 @@ import org.springframework.util.StringUtils;
|
|||||||
*/
|
*/
|
||||||
public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler {
|
public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler {
|
||||||
|
|
||||||
private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
|
|
||||||
Arrays.asList("scope", "scp");
|
|
||||||
|
|
||||||
private String realmName;
|
private String realmName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,19 +69,9 @@ public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request.getUserPrincipal() instanceof AbstractOAuth2TokenAuthenticationToken) {
|
if (request.getUserPrincipal() instanceof AbstractOAuth2TokenAuthenticationToken) {
|
||||||
AbstractOAuth2TokenAuthenticationToken token =
|
|
||||||
(AbstractOAuth2TokenAuthenticationToken) request.getUserPrincipal();
|
|
||||||
|
|
||||||
String scope = getScope(token);
|
|
||||||
|
|
||||||
parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE);
|
parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE);
|
||||||
parameters.put("error_description",
|
parameters.put("error_description", "The request requires higher privileges than provided by the access token.");
|
||||||
String.format("The token provided has insufficient scope [%s] for this request", scope));
|
|
||||||
parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1");
|
parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1");
|
||||||
|
|
||||||
if (StringUtils.hasText(scope)) {
|
|
||||||
parameters.put("scope", scope);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters);
|
String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters);
|
||||||
@ -105,25 +89,6 @@ public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler
|
|||||||
this.realmName = realmName;
|
this.realmName = realmName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getScope(AbstractOAuth2TokenAuthenticationToken token) {
|
|
||||||
|
|
||||||
Map<String, Object> attributes = token.getTokenAttributes();
|
|
||||||
|
|
||||||
for (String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES) {
|
|
||||||
Object scopes = attributes.get(attributeName);
|
|
||||||
if (scopes instanceof String) {
|
|
||||||
return (String) scopes;
|
|
||||||
} else if (scopes instanceof Collection) {
|
|
||||||
Collection coll = (Collection) scopes;
|
|
||||||
return (String) coll.stream()
|
|
||||||
.map(String::valueOf)
|
|
||||||
.collect(Collectors.joining(" "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {
|
private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {
|
||||||
String wwwAuthenticate = "Bearer";
|
String wwwAuthenticate = "Bearer";
|
||||||
if (!parameters.isEmpty()) {
|
if (!parameters.isEmpty()) {
|
||||||
|
@ -16,14 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.security.oauth2.server.resource.web.access.server;
|
package org.springframework.security.oauth2.server.resource.web.access.server;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
@ -31,8 +23,14 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
|
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
||||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
|
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates any {@link AccessDeniedException} into an HTTP response in accordance with
|
* Translates any {@link AccessDeniedException} into an HTTP response in accordance with
|
||||||
@ -63,8 +61,7 @@ public class BearerTokenServerAccessDeniedHandler implements ServerAccessDeniedH
|
|||||||
|
|
||||||
return exchange.getPrincipal()
|
return exchange.getPrincipal()
|
||||||
.filter(AbstractOAuth2TokenAuthenticationToken.class::isInstance)
|
.filter(AbstractOAuth2TokenAuthenticationToken.class::isInstance)
|
||||||
.cast(AbstractOAuth2TokenAuthenticationToken.class)
|
.map(token -> errorMessageParameters(parameters))
|
||||||
.map(token -> errorMessageParameters(token, parameters))
|
|
||||||
.switchIfEmpty(Mono.just(parameters))
|
.switchIfEmpty(Mono.just(parameters))
|
||||||
.flatMap(params -> respond(exchange, params));
|
.flatMap(params -> respond(exchange, params));
|
||||||
}
|
}
|
||||||
@ -78,21 +75,11 @@ public class BearerTokenServerAccessDeniedHandler implements ServerAccessDeniedH
|
|||||||
this.realmName = realmName;
|
this.realmName = realmName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, String> errorMessageParameters(
|
private static Map<String, String> errorMessageParameters(Map<String, String> parameters) {
|
||||||
AbstractOAuth2TokenAuthenticationToken token,
|
|
||||||
Map<String, String> parameters) {
|
|
||||||
|
|
||||||
String scope = getScope(token);
|
|
||||||
|
|
||||||
parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE);
|
parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE);
|
||||||
parameters.put("error_description",
|
parameters.put("error_description", "The request requires higher privileges than provided by the access token.");
|
||||||
String.format("The token provided has insufficient scope [%s] for this request", scope));
|
|
||||||
parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1");
|
parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1");
|
||||||
|
|
||||||
if (StringUtils.hasText(scope)) {
|
|
||||||
parameters.put("scope", scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,25 +90,6 @@ public class BearerTokenServerAccessDeniedHandler implements ServerAccessDeniedH
|
|||||||
return exchange.getResponse().setComplete();
|
return exchange.getResponse().setComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getScope(AbstractOAuth2TokenAuthenticationToken token) {
|
|
||||||
|
|
||||||
Map<String, Object> attributes = token.getTokenAttributes();
|
|
||||||
|
|
||||||
for (String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES) {
|
|
||||||
Object scopes = attributes.get(attributeName);
|
|
||||||
if (scopes instanceof String) {
|
|
||||||
return (String) scopes;
|
|
||||||
} else if (scopes instanceof Collection) {
|
|
||||||
Collection coll = (Collection) scopes;
|
|
||||||
return (String) coll.stream()
|
|
||||||
.map(String::valueOf)
|
|
||||||
.collect(Collectors.joining(" "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {
|
private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {
|
||||||
String wwwAuthenticate = "Bearer";
|
String wwwAuthenticate = "Bearer";
|
||||||
if (!parameters.isEmpty()) {
|
if (!parameters.isEmpty()) {
|
||||||
|
@ -16,14 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.security.oauth2.server.resource.web.access;
|
package org.springframework.security.oauth2.server.resource.web.access;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.assertj.core.util.Maps;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
@ -31,6 +25,9 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
|
||||||
@ -81,7 +78,7 @@ public class BearerTokenAccessDeniedHandlerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleWhenTokenHasNoScopesThenInsufficientScopeError()
|
public void handleWhenOAuth2AuthenticatedThenStatus403AndAuthHeaderWithInsufficientScopeErrorAttribute()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
@ -94,132 +91,10 @@ public class BearerTokenAccessDeniedHandlerTests {
|
|||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(403);
|
assertThat(response.getStatus()).isEqualTo(403);
|
||||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " +
|
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " +
|
||||||
"error_description=\"The token provided has insufficient scope [] for this request\", " +
|
"error_description=\"The request requires higher privileges than provided by the access token.\", " +
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"");
|
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasScopeAttributeThenInsufficientScopeErrorWithScopes()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scope", "message:read message:write");
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
request.setUserPrincipal(token);
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(request, response, null);
|
|
||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(403);
|
|
||||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " +
|
|
||||||
"scope=\"message:read message:write\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasEmptyScopeAttributeThenInsufficientScopeError()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scope", "");
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
request.setUserPrincipal(token);
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(request, response, null);
|
|
||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(403);
|
|
||||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasScpAttributeThenInsufficientScopeErrorWithScopes()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write"));
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
request.setUserPrincipal(token);
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(request, response, null);
|
|
||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(403);
|
|
||||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " +
|
|
||||||
"scope=\"message:read message:write\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasEmptyScpAttributeThenInsufficientScopeError()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scp", Collections.emptyList());
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
request.setUserPrincipal(token);
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(request, response, null);
|
|
||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(403);
|
|
||||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasBothScopeAndScpAttributesTheInsufficientErrorBasedOnScopeAttribute()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write"));
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
request.setUserPrincipal(token);
|
|
||||||
attributes.put("scope", "missive:read missive:write");
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(request, response, null);
|
|
||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(403);
|
|
||||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [missive:read missive:write] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " +
|
|
||||||
"scope=\"missive:read missive:write\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasScopeAttributeAndRealmIsSetThenInsufficientScopeErrorWithScopesAndRealm()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scope", "message:read message:write");
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
request.setUserPrincipal(token);
|
|
||||||
|
|
||||||
this.accessDeniedHandler.setRealmName("test");
|
|
||||||
this.accessDeniedHandler.handle(request, response, null);
|
|
||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(403);
|
|
||||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer realm=\"test\", " +
|
|
||||||
"error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " +
|
|
||||||
"scope=\"message:read message:write\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setRealmNameWhenNullRealmNameThenNoExceptionThrown() {
|
public void setRealmNameWhenNullRealmNameThenNoExceptionThrown() {
|
||||||
assertThatCode(() -> this.accessDeniedHandler.setRealmName(null))
|
assertThatCode(() -> this.accessDeniedHandler.setRealmName(null))
|
||||||
|
@ -16,15 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.security.oauth2.server.resource.web.access.server;
|
package org.springframework.security.oauth2.server.resource.web.access.server;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.assertj.core.util.Maps;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
|
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
@ -32,6 +25,11 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
@ -78,7 +76,7 @@ public class BearerTokenServerAccessDeniedHandlerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleWhenTokenHasNoScopesThenInsufficientScopeError() {
|
public void handleWhenOAuth2AuthenticatedThenStatus403AndAuthHeaderWithInsufficientScopeErrorAttribute() {
|
||||||
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(Collections.emptyMap());
|
Authentication token = new TestingOAuth2TokenAuthenticationToken(Collections.emptyMap());
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
||||||
@ -90,121 +88,10 @@ public class BearerTokenServerAccessDeniedHandlerTests {
|
|||||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||||
assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo(
|
assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo(
|
||||||
Arrays.asList("Bearer error=\"insufficient_scope\", " +
|
Arrays.asList("Bearer error=\"insufficient_scope\", " +
|
||||||
"error_description=\"The token provided has insufficient scope [] for this request\", " +
|
"error_description=\"The request requires higher privileges than provided by the access token.\", " +
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""));
|
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasScopeAttributeThenInsufficientScopeErrorWithScopes() {
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scope", "message:read message:write");
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
|
||||||
when(exchange.getPrincipal()).thenReturn(Mono.just(token));
|
|
||||||
when(exchange.getResponse()).thenReturn(new MockServerHttpResponse());
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(exchange, null).block();
|
|
||||||
|
|
||||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
|
||||||
assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo(
|
|
||||||
Arrays.asList("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " +
|
|
||||||
"scope=\"message:read message:write\""));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasEmptyScopeAttributeThenInsufficientScopeError() {
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scope", "");
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
|
||||||
when(exchange.getPrincipal()).thenReturn(Mono.just(token));
|
|
||||||
when(exchange.getResponse()).thenReturn(new MockServerHttpResponse());
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(exchange, null).block();
|
|
||||||
|
|
||||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
|
||||||
assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo(
|
|
||||||
Arrays.asList("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasScpAttributeThenInsufficientScopeErrorWithScopes() {
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write"));
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
|
||||||
when(exchange.getPrincipal()).thenReturn(Mono.just(token));
|
|
||||||
when(exchange.getResponse()).thenReturn(new MockServerHttpResponse());
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(exchange, null).block();
|
|
||||||
|
|
||||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
|
||||||
assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo(
|
|
||||||
Arrays.asList("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " +
|
|
||||||
"scope=\"message:read message:write\""));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasEmptyScpAttributeThenInsufficientScopeError() {
|
|
||||||
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scp", Collections.emptyList());
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
|
||||||
when(exchange.getPrincipal()).thenReturn(Mono.just(token));
|
|
||||||
when(exchange.getResponse()).thenReturn(new MockServerHttpResponse());
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(exchange, null).block();
|
|
||||||
|
|
||||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
|
||||||
assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo(
|
|
||||||
Arrays.asList("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasBothScopeAndScpAttributesTheInsufficientErrorBasedOnScopeAttribute() {
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write"));
|
|
||||||
attributes.put("scope", "missive:read missive:write");
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
|
||||||
when(exchange.getPrincipal()).thenReturn(Mono.just(token));
|
|
||||||
when(exchange.getResponse()).thenReturn(new MockServerHttpResponse());
|
|
||||||
|
|
||||||
this.accessDeniedHandler.handle(exchange, null).block();
|
|
||||||
|
|
||||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
|
||||||
assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo(
|
|
||||||
Arrays.asList("Bearer error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [missive:read missive:write] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " +
|
|
||||||
"scope=\"missive:read missive:write\""));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleWhenTokenHasScopeAttributeAndRealmIsSetThenInsufficientScopeErrorWithScopesAndRealm() {
|
|
||||||
Map<String, Object> attributes = Maps.newHashMap("scope", "message:read message:write");
|
|
||||||
Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes);
|
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
|
||||||
when(exchange.getPrincipal()).thenReturn(Mono.just(token));
|
|
||||||
when(exchange.getResponse()).thenReturn(new MockServerHttpResponse());
|
|
||||||
|
|
||||||
this.accessDeniedHandler.setRealmName("test");
|
|
||||||
this.accessDeniedHandler.handle(exchange, null).block();
|
|
||||||
|
|
||||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
|
||||||
assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate"))
|
|
||||||
.isEqualTo(Arrays.asList("Bearer realm=\"test\", " +
|
|
||||||
"error=\"insufficient_scope\", " +
|
|
||||||
"error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " +
|
|
||||||
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " +
|
|
||||||
"scope=\"message:read message:write\""));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setRealmNameWhenNullRealmNameThenNoExceptionThrown() {
|
public void setRealmNameWhenNullRealmNameThenNoExceptionThrown() {
|
||||||
assertThatCode(() -> this.accessDeniedHandler.setRealmName(null))
|
assertThatCode(() -> this.accessDeniedHandler.setRealmName(null))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user