Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Support custom OAuth2AuthenticatedPrincipal in Jwt-based authentication flow #17191

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

Open
therepanic wants to merge 1 commit into spring-projects:main
base: main
Choose a base branch
Loading
from therepanic:gh-6237

Conversation

Copy link
Contributor

@therepanic therepanic commented Jun 1, 2025

This PR implements a simpler approach, as suggested by @jzheaux, to support OAuth2AuthenticatedPrincipal injection into JwtAuthenticationToken

Resolves: #6237

Copy link
Contributor Author

therepanic commented Jun 1, 2025
edited
Loading

I think the current solution can't be merged now since at the very least we don't have tests. I would like to hear feedback if I have understood the solution to the current problem correctly.

Also I guess we can't add a constructor to (Jwt jwt, Object principal, Collection<GrantedAuthority> authorities) as written in #32 because we already have a constructor with 3 arguments (Jwt jwt, Collection<? extends GrantedAuthority> authorities, String name). Because of this we have a build failing now.

@jgrandja jgrandja added type: enhancement A general enhancement in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged labels Jun 11, 2025
@jzheaux jzheaux self-assigned this Jun 17, 2025
Copy link
Contributor

@jzheaux jzheaux 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 PR, @therepanic! I've left some feedback inline that I hope will also address your question about the new JwtAuthenticationToken constructor.

After you review my comments, if we are agreed, will you also please add tests that confirm the new setter methods work?

* Sets the {@link Converter Converter&lt;Jwt, Collection&lt;OAuth2AuthenticatedPrincipal&gt;&gt;}
* to use.
* @param jwtPrincipalConverter The converter
* @since 6.5.0
Copy link
Contributor

@jzheaux jzheaux Jun 2, 2025

Choose a reason for hiding this comment

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

This should be @since 7.0 as that's the current feature release (now that 6.5 is released, it will only take bug fixes).

@@ -42,8 +45,26 @@ public class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthen
public final AbstractAuthenticationToken convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);

String principalClaimValue = jwt.getClaimAsString(this.principalClaimName);
return new JwtAuthenticationToken(jwt, authorities, principalClaimValue);
if (this.jwtPrincipalConverter == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

With the help of a private inner class, I believe we can adapt Jwt into an OAuth2AuthenticatedPrincipal so that this if statement is not necessary. Consider adding a class like this:

private static final class JwtAuthenticatedPrincipal extends Jwt implements OAuth2AuthenticatedPrincipal {
	private final String principalClaimName;
	JwtAuthenticatedPrincipal(Jwt jwt) {
		this(jwt, JwtClaimNames.SUB);
	}
	JwtAuthenticatedPrincipal(Jwt jwt, String principalClaimName) {
		super(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getHeaders(), jwt.getClaims());
		this.principalClaimName = principalClaimName;
	}
	@Override
	public Map<String, Object> getAttributes() {
		return getClaims();
	}
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return List.of();
	}
	@Override
	public String getName() {
		return getClaimAsString(this.principalClaimName);
	}
}

Then, I believe jwtPrincipalConverter can default to JwtAuthenticatedPrincipal::new and principalClaimName can be removed by having setPrincipalClaimName use JwtAuthenticatedPrincipal as well.

@@ -58,4 +60,16 @@ public AbstractAuthenticationToken convert(Jwt jwt) {
return new BearerTokenAuthentication(principal, accessToken, authorities);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should use the principal provided by the principal conversion instead of constructing a new DefaultOAuth2AuthenticatedPrincipal.

It might be a little simpler at this point to change this class to have its own Converter<Jwt, OAuth2AuthenticatedPrincipal> and Converter<Jwt, Collection<GrantedAuthority>> references.

Copy link
Contributor Author

@therepanic therepanic Jul 31, 2025
edited
Loading

Choose a reason for hiding this comment

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

Adding your own links to converters will indeed be easier, but I have a question. Do we want to create the same jwtPrincipalConverter in JwtBearerTokenAuthenticationConverter as in JwtAuthenticationConverter? That is, by default with the type JwtAuthenticatedPrincipal::new? If so, then in the version you suggested, this class is private, and therefore we will not be able to create a converter by default. Should we make JwtAuthenticatedPrincipal package-private? Or we can just create getters for converters in JwtAuthenticationConverter and use them?

Copy link
Contributor

@jzheaux jzheaux Aug 1, 2025

Choose a reason for hiding this comment

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

I'd recommend that the principal converter in JwtBearerTokenAuthenticationConverter be one that creates a DefaultOAuth2AuthenticatedPrincipal in order to preserve existing behavior.

And I'm not sure, but I believe that may require calling the authorities converter twice, once to populate DefaultOAuth2AuthenticatedPrincipal and once to populate BearerTokenAuthentication; however, I don't think that's a big issue as the point is to allow the strategy to be overridden.

* Sets the {@link Converter Converter&lt;Jwt, Collection&lt;OAuth2AuthenticatedPrincipal&gt;&gt;}
* to use.
* @param jwtPrincipalConverter The converter
* @since 6.5.0
Copy link
Contributor

Choose a reason for hiding this comment

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

Will you please update this to @since 7.0?

@@ -58,4 +60,16 @@ public AbstractAuthenticationToken convert(Jwt jwt) {
return new BearerTokenAuthentication(principal, accessToken, authorities);
}

/**
* Sets the {@link Converter Converter&lt;Jwt, Collection&lt;OAuth2AuthenticatedPrincipal&gt;&gt;}
* to use.
Copy link
Contributor

Choose a reason for hiding this comment

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

Will you please state the default value as well? For example:

* <p>By default, constructs a {@link DefaultOAuth2AuthenticatedPrincipal} based on the claims and authorities derived from the {@link Jwt}.

therepanic reacted with thumbs up emoji
Closes spring-projectsgh-6237
Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@jzheaux jzheaux jzheaux requested changes

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
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

Support UserDetailsService components in OAuth2 Resource Server flows

AltStyle によって変換されたページ (->オリジナル) /