Skip to content
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

Introduce JWT Flow API in Test Support #6634

Closed
jzheaux opened this issue Mar 21, 2019 · 6 comments
Closed

Introduce JWT Flow API in Test Support #6634

jzheaux opened this issue Mar 21, 2019 · 6 comments
Assignees
Labels
in: test An issue in spring-security-test type: enhancement A general enhancement
Milestone

Comments

@jzheaux
Copy link
Contributor

jzheaux commented Mar 21, 2019

It would be handy in tests to be able to specify a Jwt authentication in tests:

this.mockMvc.perform(get("/")
        .with(jwt()))
        .andExcept(status().isOk());

Some features we should consider:

  • Providing a completely instantiated Jwt:
Jwt jwt = ...;
this.mockMvc.perform(get("/")
        .with(jwt(jwt)))
        ...
  • Providing authorities:
Jwt jwt = ...;
this.mockMvc.perform(get("/")
        .with(jwt(jwt).authorities("SCOPE_message:read"))
        ...
  • Providing claims:
this.mockMvc.perform(get("/")
        .with(jwt().claim(SUB, "the-subject")))
        ...
  • Providing headers:
this.mockMvc.perform(get("/")
        .with(jwt().header("alg", RS256)))
        ...

These would result in the SecurityContext containing an instance of JwtAuthenticationToken.

Also, we should introduce its reactive equivalent.

@jzheaux jzheaux added in: test An issue in spring-security-test New Feature in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) labels Mar 21, 2019
@jzheaux jzheaux added this to the 5.2.0.M2 milestone Mar 21, 2019
@ch4mpy
Copy link
Contributor

ch4mpy commented Apr 6, 2019

Using mockJwt() instead of jwt() to be consistent with existing mockUser(). It also make things consistent with annotations: with(mockJwt()) <=> @WithMockJwt

Missed the part of the spec with the Jwt instance. Will fix that soon.

Went further this spec as I propose equivalents for Bearer access-token and OidcId token.

All is based on commons I initiated for annotations equivalents (#6557)

Branches are ordered as follow (so PRs are streamed as follow):

  1. gh-6634--jwt-servlet-flow mockJwt() flow API for MockMvc
  2. gh-6634--access-token-servlet-flow mockAccessToken() flow API for MockMvc
  3. gh-6634--oidcid-servlet-flow mockOidcId() flow API for MockMvc
  4. gh-6557--jwt-reactive-flow mockJwt() flow API for reactive server
  5. gh-6557--access-token-reactive-flow augment reactive flow with mockOidcId()
  6. gh-6557--oidcid-reactive-flow mockOidcId() flow API for reactive server

@ch4mpy
Copy link
Contributor

ch4mpy commented Apr 9, 2019

@jgrandja, @rwinch, could you please confirm your intention is not cutting the merges after JWT flow APIs (servlet & reactive)?
I got this feeling because of the comments asking to drop some abstractions.

To me, limiting features to "flow API for Jwt secured @controllers, without scope claim support" is more of a gadget than an "Spring5 OAuth2 unit-test support":

  • doesn't allow for secured "services" testing (annotations are required for that)
  • tests will throw cast exceptions when @AuthenticationPrincipal is expected to return something else than a Jwt. Bearer access-token and OpenId implementations seem a minimum
  • as far as I understood source code, scope claims are processed as standard by spring-security . Test lib should be concistent and do the same
  • most standard claims are useless at @components unit-test time. To me, claims support comes to be intersting with custom ones only: if one puts custom claim in a token, it is likely to use it from business code (I mean somewhere in a controller or service). As so, removing this support seams important loss.

I tried to correct as much as possible before going at see again but am quite unsatisfied with my push:

  • Applied modifications on a gh-6557--reviews branch containing everything to benefit IDE refactoring features but run out of time to report on gh-6634--jwt-reactive-flow branch which is the origin of the PR you are commenting
  • I pulled master and rebased on it but can't compile any more as some new dependencies won't download from Cuba => this gh-6557--reviews mentioned above is in a very dirty state
    Will improve the situation from Jamaïca probably next week-end.

@ch4mpy
Copy link
Contributor

ch4mpy commented Apr 22, 2019

@jzheaux first, please forgive me for my last comment typo, It's you who I intented to ping at first rank as you are the one reviewing the PR.
Do you have request changes I missed, still needing change? I'll be available frequently next week, but offline again mot of first half of May.

@rwinch rwinch added type: enhancement A general enhancement and removed New Feature labels May 3, 2019
@jzheaux
Copy link
Contributor Author

jzheaux commented May 14, 2019

There's been some discussion about the contract on #6748, and I'd like to update the ticket here with some changes to the original proposal found in the ticket's description.

It will likely be more flexible to separate Jwt configuration from any remaining authentication configuration, so let's do:

this.mockMvc.perform(get("/")
        .with(jwt()))
        .andExcept(status().isOk());

as already stated.

Let's also support:

  • Providing claims:
this.mockMvc.perform(get("/")
        .with(jwt(jwt -> jwt.claim(SUB, "the-subject"))))
        ...
  • Providing headers:
this.mockMvc.perform(get("/")
        .with(jwt(jwt -> jwt.header("alg", RS256))))
        ...
  • Providing authorities:
this.mockMvc.perform(get("/")
        .with(jwt().authorities("SCOPE_message:read")))
        ...

// or

this.mockMvc.perform(get("/")
        .with(jwt(jwt -> jwt.claim("scope", "message:read"))))
        ...

// or

this.mockMvc.perform(get("/")
        .with(jwt().authorities(jwt -> toGrantedAuthorities(jwt))))
        ...

We can easily leverage the output of #6851 to make this a simple introduction of SecurityMockServerConfigurers.JwtMutator and SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor.

@ch4mpy
Copy link
Contributor

ch4mpy commented May 17, 2019

In addition to what was written above by @jzheaux (and knowing following are points we do not agree on):

Regarding the JwtAuthenticationToken.Builder, but this applies to any OAuth2 authentication builders (being a MockMvc request post processor, WebTestClient configurer or annotation SecurityContextFactory delegate) contract:

  1. I think it would be strange for a builder to have immutable values. Having the most important property in an OAuth2 authentication (the token) immutable would be surprising. IMO, all OAuth2 authentication builders should expose a token(Consumer<TOKEN_TYPE.Builder> tokenBuilderConsumer) method (TOKEN_TYPE being Jwt or alike)
  2. I repeat here what I wrote several times: I think there should be an "authoritiesConverter" setter instead of an "authorities" one on OAuth2 authentication builder because authorities can be derived from tokens using converters but token can't be updated from authorities (and per spec, in OAuth2, the tokens are the reference regarding authorization). Setting authorities independently of the token will lead for sure to incoherence between token and granted authorities. Given that spring security expressions can be about anything including the tokens and not just authorities, this can cause very subtle test bugs.

Regarding token builders, when there are standard claims (Jwt, Introspection, OpenId, ...), it should expose setters for each. Applications using private claims, can then extend framework builders to support their own claims. So with a Jwt Builder consumer as from @jzheaux sample, this would mean:

jwt().token(jwt -> jwt.subject("user").expiresAt(Instant.now)); // etc.

"scope" is OAuth2 tokens meta-data (not a claim but part of the successful authorization response for the token). As there is no class I'm aware of in authentication stack that holds both the token and it's meta-data, it would make sens to also expose a method to add well-formatted (conform to the OAuth2 RFC) scope claim to authentication builders:

jwt().scopes("message:read", "message:write");

Most of this is more or less implemented there: https://github.com/ch4mpy/spring-addons/tree/master/spring-security-test-oauth2-addons

P.S. @jzheaux unfortunately, I'm timed up. I have to go at sea again to reach Panama, might be quite occupied solving formalities to use the canal there and will be at sea for more than a month after before I reach French Polynesia. I think you can modify My repo to make the PR at your wish. You can also copy / paste whatever you like to a new repo, I'd don't really mind being an author, even if I put a lot of energy in this (and time and money...)

@jzheaux
Copy link
Contributor Author

jzheaux commented May 20, 2019

I think you can modify My repo to make the PR at your wish. You can also copy / paste whatever you like to a new repo, I'd don't really mind being an author, even if I put a lot of energy in this (and time and money...)

Your contribution is much appreciated, we certainly wouldn't have jwt test support without it, and you deserve the credit for that. I'll consult with the team to see what is the right maneuver here.

jzheaux added a commit that referenced this issue May 22, 2019
Simplified the initial support to introduce fewer classes and only the
features described in the ticket.

Changed tests to align with existing patterns in the repository.

Added JavaDoc to remaining public methods introduced for this feature.

Issue: gh-6634
Issue: gh-6851
@jzheaux jzheaux removed the in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) label Aug 5, 2020
@jzheaux jzheaux self-assigned this Aug 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test An issue in spring-security-test type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants