Skip to content

NimbusReactiveJwtDecoder Takes Reactive Processor #6499

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

Merged
merged 2 commits into from
Mar 18, 2019

Conversation

jzheaux
Copy link
Contributor

@jzheaux jzheaux commented Feb 1, 2019

No description provided.

@jzheaux jzheaux added New Feature in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) labels Feb 1, 2019

public interface ReactiveJWTProcessor {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like all the changes except for this part. I don't like that we have a public interface that has Nimbus types on it. There are a few ideas I had for this:

  • Start with making the changes to use the new Nimbus APIs, but do not make a public API available for others. I like this idea a lot (at least for a start since in my mind it is not controversial)
  • Move the builder and the interface into NimbusReactiveJwtDecoder since it is Nimbus specific. The class/interfaces would include Nimbus in the names since they are Nimbus specific
  • Remove the interface and use something more generic like Converter<Mono<JWTClaimsSet>,SignedJWT>
  • See if we can change the input and output to be something that is not Nimbus specific. For the input we could use a String. However, I'm not sure what we would do for the output. I'd like to avoid writing complex types of our own unless we are going to be using the types in other places.

I think it may be possible to use a combination of these approaches too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see the value in the first option, but I figure if we can come to a consensus quickly on the contract, we might as well include it in this PR.

I'd not prefer the second as it would be ideal if the structure were roughly similar to the imperative side of things. We've already seen examples in other places where users have an imperative implementation of something and want to adapt it into their reactive applications. The way I see it, if the builder were part of NimbusReactiveJwtDecoder, the output of the builder would be the decoder itself as opposed to a contract similar to Nimbus's JWTProcessor, making this kind of reuse trickier.

I like the third option. Just to be sure, though, I think that Converter<SignedJWT, Mono<JWTClaimSet>> is correct. This is similar enough to the imperative contract that the user could easily adapt any non-blocking imperative implementation that they already have. It's also a pattern that Spring Security follows in other places, so there is some familiarity with it.

I like the fourth option; however, I tried implementing it and ran into a snag. The NimbusReactiveJwtDecoder needs to know the header in order to create an instance of Jwt. This means that it needs to parse the String before handing that String off to the processor. When I tried a contract of <String, Map<String, Object>> (token -> claim set), I ended up parsing the token twice. Alternatively, I thought I might try <String, Jwt> so that the processor can hand back the header as well, but that felt odd since we are now basically repeating the same contract as NimbusReactiveJwtDecoder.

Given all of this, I've updated the PR in a separate commit which shows what I see as a working option 3.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second commit you did ends up pretty close to my PR #6367

However, I got an issue with the Converter<SignedJWT, Mono<JWTClaimSet>>

The code state:

@Override
public Mono<Jwt> decode(String token) throws JwtException {
	JWT jwt = parse(token);
	if (jwt instanceof SignedJWT) {
		return this.decode((SignedJWT) jwt);
	}
	throw new JwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
}

Now I personally don't use EncryptedJWT (yet????) but it exists. What would be the approach for the people using it ?

Specialize another NimbusReactiveJwtDecoder ? or extends this one ?
First my thinking was to create another specialized decoder, but then I though of what can happen on my side: Multiple authorization server. It means I cannot be 100% sure of what those providers will send me. This would end up copying/pasting a lot of spring code to make this possible.

Perhaps it is better to add the EncryptedJWT use case, and so what is the better approach to this ? a constructor with one converter (signed JWT) and another with 2 converter (signed / encrypted) ? a single interface ?

Copy link
Contributor Author

@jzheaux jzheaux Feb 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this PR is definitely inspired by your initial work, @GregoireW!

I like that you are thinking about these other items, but I think we should keep this PR focused on the intended use case. It's better to solve one problem at a time. Spring Security doesn't yet support encrypted JWTs nor Authorization Server multi-tenancy, so trying to add support for it in this PR would probably jump the gun.

However, yes, I believe it would be a new constructor parameter. In the meantime, folks would probably implement ReactiveJwtDecoder and write their own reactive encryption support since neither Spring nor Nimbus has yet a reactive JWE API. They can always delegate to Spring Security's reactive JWS support once the JWT is decrypted.

Copy link
Member

@rwinch rwinch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! I have provided comments inline

* @since 5.2
* @see NimbusJwtDecoder
*/
public class ReactiveJwtProcessors {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After our changes I believe this name should change since there is no ReactiveJwtProcessor now.

I'm still thinking it might be valuable to move this into NimbusReactiveJwtDecoder. If we foresee it being used somewhere else, then we would want to keep it externalized though.

the output of the builder would be the decoder itself as opposed to a contract similar to Nimbus's JWTProcessor, making this kind of reuse trickier.

I don't think this is a problem. User's would still be able to inject a Converter directly.

Copy link
Contributor Author

@jzheaux jzheaux Mar 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the builder returns a decoder and later on that builder moves out of NimbusReactiveJwtDecoder into its own class, the existing decoder constructors will need to be deprecated. I'm not so keen on their deprecation being tied to that refactor.

If we can, I'd prefer that the builder return the equivalent of a processor. (And if it comes down to naming, I have some thoughts below.)

Nevermind on this point - the decoder builder could expose a package-private method instead. Not saying that's the right choice, but it would allow those constructors to keep functioning just fine.

If we foresee it being used somewhere else ...

I think reuse is a very good point, and I struggle a bit with it because of JwtProcessors, which returns a JWTProcessor, a Nimbus-specific type. This builder has provided a lot of flexibility for NimbusJwtDecoder, making it easy to add other processing types without modifying the decoder. Will this pattern of building JWTProcessors be applied to other places than the NimbusJwtDecoder constructor? I would guess the answer to this is the answer here as well.

After our changes I believe this name should change since there is no ReactiveJwtProcessor now.

Again, this is sensible - JwtProcessors builds a JWTProcessor, JwtDecoders builds a JwtDecoder, etc.

We have an example of a converter whose parameter types don't alone describe what the converter is for: MappedJwtClaimSetConverter implements Converter<Map<String, Object>, Map<String, Object>>. Here, the name clarifies the semantics of the conversion and we don't want to call it MapMapConverter or similar.

Also, we have classes (not interfaces, AFAIK) that return Nimbus types, for example, JwtProcessors returns a JWTProcessor.

Given these, I believe it's sensible to either give a distinctive name to a certain type of converter, in order to clarify semantics -- have the interface ReactiveJwtProcessor extends Converter<SignedJWT, JWTClaimSet> (read: it's like Nimbus's JWTProcessor, but reactive) -- or to simply state what we are producing, even if the builder's name for a Converter<S,T> doesn't precisely match STConverter -- do ReactiveJwtProcessors since what is effectively produced is a reactive version of Nimbus's JWTProcessor.

Though, given that I'm saying "Nimbus's JWTProcessor" so much, it might be better to call it ReactiveNimbusJwtProcessors.

This has come about because of the absence of a reactive API for Nimbus, and I'm guessing it won't be the last time. Possibly, we could gauge Nimbus's interest in maintaining a reactive version of their API that depends on Project Reactor, though that seems unlikely. Given that, I don't think it's unreasonable to introduce types that are the reactive equivalent of Nimbus APIs and calling them such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a chat offline with @rwinch and will summarize here.

The main reason for my concern is symmetry between the servlet and reactive APIs. So, since neither is released for this feature, yet, we decided to actually change the servlet version so that it also internalized the decoder builder.

@jzheaux jzheaux removed the wip label Mar 5, 2019
@jzheaux jzheaux force-pushed the jwk-context branch 2 times, most recently from 73201b5 to c440f61 Compare March 5, 2019 17:23
@jzheaux jzheaux added this to the 5.2.0.M2 milestone Mar 5, 2019
Copy link
Member

@rwinch rwinch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks LGTM

@jzheaux jzheaux merged commit 55e8df1 into spring-projects:master Mar 18, 2019
@jzheaux jzheaux deleted the jwk-context branch March 18, 2019 18:32
@jzheaux jzheaux self-assigned this Apr 14, 2019
@rwinch rwinch added the type: enhancement A general enhancement label May 3, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants