-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for access token in body parameter as per rfc 6750 Sec. 2.2 #15819
base: main
Are you sure you want to change the base?
Conversation
@jonah1und1 Please sign the Contributor License Agreement! Click here to manually synchronize the status of this Pull Request. See the FAQ for frequently asked questions. |
@jonah1und1 Thank you for signing the Contributor License Agreement! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks much for the PR @jonah1und1. Apologies for the delay. I have some feedback inline. Please note that I will hold off merging until after the GA release, since we are in the RC phase of 6.4 and will need to wait to introduce new APIs (setAllowFormEncodedBodyParameter()
) until 6.5.
...uth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java
Show resolved
Hide resolved
return Flux.merge(resolveFromAuthorizationHeader(request.getHeaders()).map(s -> Tuples.of(s, TokenSource.HEADER)), | ||
resolveAccessTokenFromRequest(request).map(s -> Tuples.of(s, TokenSource.QUERY_PARAMETER)), | ||
resolveAccessTokenFromBody(exchange).map(s -> Tuples.of(s, TokenSource.BODY_PARAMETER))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to resolve without the creation of Tuple
s here? It is generally preferred to avoid creation of additional objects when possible (e.g. Stream
) and I think Tuple
would fall in the same category here. Instead of the TokenSource
, I believe Mono#switchIfEmpty
could be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sorry, I fail to see how to resolve this without some kind of information where the token is from, to then check whether it is supported. Could you please give me an hint on how to use Mono#switchIfEmpty
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My mistake. Since you're using Flux.merge()
, it would actually be Flux#switchIfEmpty
.
The new resolveAccessTokenFromBody()
checks a boolean
, method and content-type before returning a Mono
. The resolveAccessTokenFromRequest()
method can be refactored to do the same. In that case, Flux.merge()
will return some number of tokens (0-3) that don't require checking the source again. It would end up looking something like this:
@Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
return Mono.defer(() -> token(exchange));
}
private Mono<Authentication> token(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
return Flux.merge(resolveFromAuthorizationHeader(request.getHeaders()),
resolveAccessTokenFromRequest(exchange.getRequest()),
resolveAccessTokenFromBody(exchange))
.switchIfEmpty(Mono.error(new OAuth2AuthenticationException(invalidTokenError())))
.collectList()
.flatMap((accessTokens) -> {
if (accessTokens.size() > 1) {
BearerTokenError error = BearerTokenErrors
.invalidRequest("Found multiple bearer tokens in the request");
return Mono.error(new OAuth2AuthenticationException(error));
}
return Mono.just(new BearerTokenAuthenticationToken(accessTokens.get(0)));
});
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your answer!
Unfortunately, I think there might be two problems with this approach:
Firstly, if no token can be found the current implementation of ServerBearerTokenAuthenticationConverter#convert()
returns an empty mono. An invalidTokenError()
is only raised if the String containing the token is empty, but not if it's null
.
This could be mitigated with relative ease imo, by leaving convert()
in its current state and replacing switchIfEmpty()
by a second if-clause inside of .flatMap()
:
if (accessTokens.isEmpty()) {
return Mono.empty();
}
Secondly, the tests resolveWhenValidHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown()
and resolveWhenQueryParameterHasMultipleAccessTokensThenOAuth2AuthenticationException()
fail because they do not set allowUriQueryParameter
to true
first. While I am not quite sure about the expected behaviour here and whether adding this.converter.setAllowUriQueryParameter(true)
to both tests is acceptable, doing so might change the current behaviour.
resolveWhenValidQueryParameterIsPresentTogetherWithBodyParameterThenAuthenticationExceptionIsThrown()
, which was added by me, also fails because the query parameter access token is filtered due to the POST
-request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firstly, if no token can be found the current implementation of
ServerBearerTokenAuthenticationConverter#convert()
returns an empty mono. AninvalidTokenError()
is only raised if the String containing the token is empty, but not if it'snull
.
Hmm, fair point.
Secondly, the tests [...] do not set
allowUriQueryParameter
totrue
first.
Also a fair point. This is unfortunate, because it could be argued that leaving this false implies that such parameters should be ignored. The test and original implementation don't agree though and that makes this tricky. I think this comes down to whether it would be considered a bug to read the token from the request (e.g. query string or body parameters) and validate it when it is not explicitly enabled.
The spec states that
Resource servers MAY support this method.
for both body parameters and query string parameters. I don't see anything in the Error Codes section indicating that the request MUST be validated for these problems if the server doesn't support a particular method for resolving the token.
For those reasons, I'm inclined to open an bug to necessitate a change in behavior for both servlet and reactive implementations that would be a precursor to this enhancement. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late reply!
Sounds sound to open a dedicated bug ticket for that. Do you want me to implement a PR for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonah1und1 I opened gh-16038. I think it would be nice to get this fixed in the release this month. Feel free to open a PR if you have availability to work on it, or if you're busy let me know and I can submit a fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created a PR: #16039
Currently, the reactive stack does not allow for authentication via parameter in body of post requests.
RFC-6750 Sec. 2.2 allows this. It is also support by mvc stack.
Related ticket: gh-15818.