Skip to content

Commit e8dd132

Browse files
edouardhuejzheaux
authored andcommitted
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
1 parent b153d92 commit e8dd132

File tree

5 files changed

+39
-346
lines changed

5 files changed

+39
-346
lines changed

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@
108108
import org.springframework.test.web.servlet.request.RequestPostProcessor;
109109
import org.springframework.util.LinkedMultiValueMap;
110110
import org.springframework.util.MultiValueMap;
111-
import org.springframework.util.StringUtils;
112111
import org.springframework.web.bind.annotation.GetMapping;
113112
import org.springframework.web.bind.annotation.PostMapping;
114113
import org.springframework.web.bind.annotation.RequestMapping;
@@ -397,7 +396,7 @@ public void getWhenUsingDefaultsWithInsufficientScopeThenInsufficientScopeError(
397396
this.mvc.perform(get("/requires-read-scope")
398397
.with(bearerToken(token)))
399398
.andExpect(status().isForbidden())
400-
.andExpect(insufficientScopeHeader(""));
399+
.andExpect(insufficientScopeHeader());
401400
}
402401

403402
@Test
@@ -411,7 +410,7 @@ public void getWhenUsingDefaultsWithInsufficientScpThenInsufficientScopeError()
411410
this.mvc.perform(get("/requires-read-scope")
412411
.with(bearerToken(token)))
413412
.andExpect(status().isForbidden())
414-
.andExpect(insufficientScopeHeader("message:write"));
413+
.andExpect(insufficientScopeHeader());
415414
}
416415

417416
@Test
@@ -497,7 +496,7 @@ public void getWhenUsingMethodSecurityWithInsufficientScopeThenInsufficientScope
497496
this.mvc.perform(get("/ms-requires-read-scope")
498497
.with(bearerToken(token)))
499498
.andExpect(status().isForbidden())
500-
.andExpect(insufficientScopeHeader(""));
499+
.andExpect(insufficientScopeHeader());
501500

502501
}
503502

@@ -512,7 +511,7 @@ public void getWhenUsingMethodSecurityWithInsufficientScpThenInsufficientScopeEr
512511
this.mvc.perform(get("/ms-requires-read-scope")
513512
.with(bearerToken(token)))
514513
.andExpect(status().isForbidden())
515-
.andExpect(insufficientScopeHeader("message:write"));
514+
.andExpect(insufficientScopeHeader());
516515
}
517516

518517
@Test
@@ -526,7 +525,7 @@ public void getWhenUsingMethodSecurityWithDenyAllThenInsufficientScopeError()
526525
this.mvc.perform(get("/ms-deny")
527526
.with(bearerToken(token)))
528527
.andExpect(status().isForbidden())
529-
.andExpect(insufficientScopeHeader("message:read"));
528+
.andExpect(insufficientScopeHeader());
530529
}
531530

532531
// -- Resource Server should not engage csrf
@@ -2230,12 +2229,11 @@ private static ResultMatcher invalidTokenHeader(String message) {
22302229
);
22312230
}
22322231

2233-
private static ResultMatcher insufficientScopeHeader(String scope) {
2232+
private static ResultMatcher insufficientScopeHeader() {
22342233
return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " +
22352234
"error=\"insufficient_scope\"" +
2236-
", error_description=\"The token provided has insufficient scope [" + scope + "] for this request\"" +
2237-
", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"" +
2238-
(StringUtils.hasText(scope) ? ", scope=\"" + scope + "\"" : ""));
2235+
", error_description=\"The request requires higher privileges than provided by the access token.\"" +
2236+
", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"");
22392237
}
22402238

22412239
private void mockWebServer(String response) {

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandler.java

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,21 @@
1616

1717
package org.springframework.security.oauth2.server.resource.web.access;
1818

19-
import java.io.IOException;
20-
import java.util.Arrays;
21-
import java.util.Collection;
22-
import java.util.LinkedHashMap;
23-
import java.util.Map;
24-
import java.util.stream.Collectors;
25-
import javax.servlet.ServletException;
26-
import javax.servlet.http.HttpServletRequest;
27-
import javax.servlet.http.HttpServletResponse;
28-
2919
import org.springframework.http.HttpHeaders;
3020
import org.springframework.http.HttpStatus;
3121
import org.springframework.security.access.AccessDeniedException;
3222
import org.springframework.security.core.Authentication;
3323
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
3424
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
3525
import org.springframework.security.web.access.AccessDeniedHandler;
36-
import org.springframework.util.StringUtils;
26+
27+
import javax.servlet.ServletException;
28+
import javax.servlet.http.HttpServletRequest;
29+
import javax.servlet.http.HttpServletResponse;
30+
import java.io.IOException;
31+
import java.util.LinkedHashMap;
32+
import java.util.Map;
33+
import java.util.stream.Collectors;
3734

3835
/**
3936
* Translates any {@link AccessDeniedException} into an HTTP response in accordance with
@@ -48,9 +45,6 @@
4845
*/
4946
public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler {
5047

51-
private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
52-
Arrays.asList("scope", "scp");
53-
5448
private String realmName;
5549

5650
/**
@@ -75,19 +69,9 @@ public void handle(
7569
}
7670

7771
if (request.getUserPrincipal() instanceof AbstractOAuth2TokenAuthenticationToken) {
78-
AbstractOAuth2TokenAuthenticationToken token =
79-
(AbstractOAuth2TokenAuthenticationToken) request.getUserPrincipal();
80-
81-
String scope = getScope(token);
82-
8372
parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE);
84-
parameters.put("error_description",
85-
String.format("The token provided has insufficient scope [%s] for this request", scope));
73+
parameters.put("error_description", "The request requires higher privileges than provided by the access token.");
8674
parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1");
87-
88-
if (StringUtils.hasText(scope)) {
89-
parameters.put("scope", scope);
90-
}
9175
}
9276

9377
String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters);
@@ -105,25 +89,6 @@ public final void setRealmName(String realmName) {
10589
this.realmName = realmName;
10690
}
10791

108-
private static String getScope(AbstractOAuth2TokenAuthenticationToken token) {
109-
110-
Map<String, Object> attributes = token.getTokenAttributes();
111-
112-
for (String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES) {
113-
Object scopes = attributes.get(attributeName);
114-
if (scopes instanceof String) {
115-
return (String) scopes;
116-
} else if (scopes instanceof Collection) {
117-
Collection coll = (Collection) scopes;
118-
return (String) coll.stream()
119-
.map(String::valueOf)
120-
.collect(Collectors.joining(" "));
121-
}
122-
}
123-
124-
return "";
125-
}
126-
12792
private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {
12893
String wwwAuthenticate = "Bearer";
12994
if (!parameters.isEmpty()) {

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandler.java

Lines changed: 10 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,21 @@
1616

1717
package org.springframework.security.oauth2.server.resource.web.access.server;
1818

19-
import java.util.Arrays;
20-
import java.util.Collection;
21-
import java.util.LinkedHashMap;
22-
import java.util.Map;
23-
import java.util.stream.Collectors;
24-
25-
import reactor.core.publisher.Mono;
26-
2719
import org.springframework.http.HttpHeaders;
2820
import org.springframework.http.HttpStatus;
2921
import org.springframework.security.access.AccessDeniedException;
3022
import org.springframework.security.core.Authentication;
3123
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
3224
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
3325
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
34-
import org.springframework.util.StringUtils;
3526
import org.springframework.web.server.ServerWebExchange;
27+
import reactor.core.publisher.Mono;
28+
29+
import java.util.Arrays;
30+
import java.util.Collection;
31+
import java.util.LinkedHashMap;
32+
import java.util.Map;
33+
import java.util.stream.Collectors;
3634

3735
/**
3836
* Translates any {@link AccessDeniedException} into an HTTP response in accordance with
@@ -63,8 +61,7 @@ public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denie
6361

6462
return exchange.getPrincipal()
6563
.filter(AbstractOAuth2TokenAuthenticationToken.class::isInstance)
66-
.cast(AbstractOAuth2TokenAuthenticationToken.class)
67-
.map(token -> errorMessageParameters(token, parameters))
64+
.map(token -> errorMessageParameters(parameters))
6865
.switchIfEmpty(Mono.just(parameters))
6966
.flatMap(params -> respond(exchange, params));
7067
}
@@ -78,21 +75,11 @@ public final void setRealmName(String realmName) {
7875
this.realmName = realmName;
7976
}
8077

81-
private static Map<String, String> errorMessageParameters(
82-
AbstractOAuth2TokenAuthenticationToken token,
83-
Map<String, String> parameters) {
84-
85-
String scope = getScope(token);
86-
78+
private static Map<String, String> errorMessageParameters(Map<String, String> parameters) {
8779
parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE);
88-
parameters.put("error_description",
89-
String.format("The token provided has insufficient scope [%s] for this request", scope));
80+
parameters.put("error_description", "The request requires higher privileges than provided by the access token.");
9081
parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1");
9182

92-
if (StringUtils.hasText(scope)) {
93-
parameters.put("scope", scope);
94-
}
95-
9683
return parameters;
9784
}
9885

@@ -103,25 +90,6 @@ private static Mono<Void> respond(ServerWebExchange exchange, Map<String, String
10390
return exchange.getResponse().setComplete();
10491
}
10592

106-
private static String getScope(AbstractOAuth2TokenAuthenticationToken token) {
107-
108-
Map<String, Object> attributes = token.getTokenAttributes();
109-
110-
for (String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES) {
111-
Object scopes = attributes.get(attributeName);
112-
if (scopes instanceof String) {
113-
return (String) scopes;
114-
} else if (scopes instanceof Collection) {
115-
Collection coll = (Collection) scopes;
116-
return (String) coll.stream()
117-
.map(String::valueOf)
118-
.collect(Collectors.joining(" "));
119-
}
120-
}
121-
122-
return "";
123-
}
124-
12593
private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {
12694
String wwwAuthenticate = "Bearer";
12795
if (!parameters.isEmpty()) {

0 commit comments

Comments
 (0)