Skip to content

Support multiple JWT issuer-uris #30108

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

Closed
ch4mpy opened this issue Mar 9, 2022 · 14 comments
Closed

Support multiple JWT issuer-uris #30108

ch4mpy opened this issue Mar 9, 2022 · 14 comments
Labels
status: declined A suggestion or change that we don't feel we should currently apply

Comments

@ch4mpy
Copy link

ch4mpy commented Mar 9, 2022

Enhancement:

spring.security.oauth2.resourceserver.jwt.issuer-uri configuration property currently accepts a single value.

In some cases (like multi-tenant environments), resource-servers accept identities issued by more than just one authorization-server.

It would ease spring-boot apps security configuration if spring.security.oauth2.resourceserver.jwt.issuer-uri could accept an array of issuers.
Currently I have to use a private property for that purpose and that leads to quite some confusion for developers used to spring-boot one.

Notes:

  • properties and yaml files syntax would allow such a modification (String to String[]) without breaking existing config files
  • OAuth2ResourceServerProperties$Jwt backward compatibility can be achieved with:
public class OAuth2ResourceServerProperties {
    public static class Jwt {
        // modified private property
        private String[] issuerUris;

        // untouched existing public interface
        /**
         * @deprecated Use {@link Jwt#getIssuerUris()} instead.
         */
        @Deprecated
        public String getIssuerUri() {
            if (this.issuerUris == null || this.issuerUris.length == 0) {
                return null;
            }
            if (this.issuerUris.length > 1) {
                throw new RuntimeException("Jwt#getIssuerUris() must be used when more than one issuer is configured");
            }
            return this.issuerUris[0];
        }

        // added functionnality
        public String[] getIssuerUris() {
            return this.issuerUris;
        }

        public void setIssuerUri(String... issuerUris) {
            this.issuerUris = issuerUris;
        }
    }
}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 9, 2022
@ch4mpy ch4mpy changed the title Turn spring.security.oauth2.resourceserver.jwt.issuer-uri into a String[] Turn spring.security.oauth2.resourceserver.jwt.issuer-uri into a String[] Mar 9, 2022
@ch4mpy ch4mpy changed the title Turn spring.security.oauth2.resourceserver.jwt.issuer-uri into a String[] Turn spring.security.oauth2.resourceserver.jwt.issuer-uri into an array Mar 9, 2022
@mhalbritter mhalbritter added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 9, 2022
@mhalbritter mhalbritter added this to the General Backlog milestone Mar 9, 2022
@mhalbritter mhalbritter changed the title Turn spring.security.oauth2.resourceserver.jwt.issuer-uri into an array Support multiple JWT issuer-uris Mar 9, 2022
@mhalbritter mhalbritter modified the milestones: General Backlog, 2.x Mar 10, 2022
@emisbalu
Copy link

I (we as a team) would like to take this up

@EduColnago
Copy link

Is this issue available? I would like to try to do this

@ch4mpy
Copy link
Author

ch4mpy commented Jun 21, 2022

https://spring.io/blog/2022/06/01/spring-security-oauth-reaches-end-of-life

I suggest you get a look at alternatives for spring-boot resource-servers auto-configuration. Maybe this one I wrote ?

@wilkinsona
Copy link
Member

@ch4mpy Spring Security itself provides OAuth support these days and Spring Boot provides auto-configuration for it. IMO, there's no need to look for an alternative.

@wilkinsona
Copy link
Member

@EduColnago thanks for the offer but we're not quite sure how to handle this one yet. Please watch the ideal-for-contribution label.

@wilkinsona
Copy link
Member

Given the Spring Security team's position on multiple issuer URIs and their decision to decline spring-projects/spring-security#10943, I'm not sure that we should do anything in Spring Boot. IMO, it does not make sense to allow multiple URIs to be configured when Spring Boot's auto-configuration will only use one of them. This wouldn't address the confusion described in the description above, it would just change its source. I also don't think it makes sense for Spring Boot to support multiple issuer URIs if that requires us to maintain a composite JwtDecoder implementation that delegates to decoders created from the issuer URIs. IMO, such a class should be maintained as part of Spring Security so that all users can benefit from it, not just those using Spring Boot.

Flagging for team attention to see what everyone else thinks.

/cc @jzheaux

@wilkinsona wilkinsona added the for: team-attention An issue we'd like other members of the team to review label Jun 21, 2022
@ch4mpy
Copy link
Author

ch4mpy commented Jun 21, 2022

@ch4mpy Spring Security itself provides OAuth support these days and Spring Boot provides auto-configuration for it. IMO, there's no need to look for an alternative.

@wilkinsona I had read the blog post too fast. My bad. But, actually I can find some reasons to look for alternatives to OpenID resource-server auto-configuration with spring-boot-starter-oauth2-resource-server:

  • it does not support multi issuers out of the box (reason for this ticket)
  • it populates security-context with JwtAuthenticationToken which has very limited interface for claims. Nowadays, most authorization servers being OIDC, an Authentication implementation exposing OpenID claims would be usefull (at least when what we provide in configuration is a .well-known/openid-configuration).
  • authorities parsing is made from scopeclaim, which is mostly useless and must be overriden in almost any app
  • CORS config must be added in the code. "Pure" resource-servers (REST APIs without embeded client) always need CORS. It would be convenient to do it from properties without any java config.
  • defaults to 302 (redirect to login) when authentication is missing or invalid. A probably better default for resource-servers would be 401 (unauthorized). Login management should be a client concern (not one of resource-server).
  • frequent resource-server flags must be set in the code too. It could be done from properties (without Java config). I'm thinking of:
    • anonymous enabling (for public resources)
    • stateless session-management (user state in the token)
    • CSRF disabling
    • maybe some permit-all routes (public resources accessible to anonymous) and a more restrictive default access (to isAuthenticated()?)

This are not just speculations, it is possible to achieve all of above with just a few properties: I do it there
with this app:

@SpringBootApplication
public class ResourceServerWithOAuthenticationApplication {

	public static void main(String[] args) {
		SpringApplication.run(ResourceServerWithOAuthenticationApplication.class, args);
	}

	@EnableGlobalMethodSecurity(prePostEnabled = true)
	public static class WebSecurityConfig {
	}

}

and that config

# shoud be set to where your authorization-server is
com.c4-soft.springaddons.security.token-issuers[0].location=https://localhost:9443/auth/realms/master

# shoud be configured with a list of private-claims this authorization-server puts user roles into
# below is default Keycloak conf for a `spring-addons` client with client roles mapper enabled
com.c4-soft.springaddons.security.token-issuers[0].authorities.claims=realm_access.roles,resource_access.spring-addons.roles

com.c4-soft.springaddons.security.cors[0].path=/greet/**
com.c4-soft.springaddons.security.permit-all=/actuator/health/readiness,/actuator/health/liveness,/v3/api-docs/**

# use IDE auto-completion or see SpringAddonsSecurityProperties javadoc for complete configuration properties list

Bootyful, isn't it?

As you could guess from com.c4-soft.springaddons.security.token-issuers being an array, it supports as many authorization-servers as needed, each with its own granted-authorities mapping: which claim(s) to get it from and with which basic processing (case and prefix).

composite JwtDecoder implementation that delegates to decoders created from the issuer URIs

I do it with JwtIssuerAuthenticationManagerResolver or ReactiveAuthenticationManagerResolver (credits go to spring-security team who told me that it is the expected way to do it, in the ticket you linked)

@jzheaux
Copy link
Contributor

jzheaux commented Jun 22, 2022

@ch4mpy is correct that Spring Security ships with a component that supports multiple issuer URIs, primarily to support multi-tenant applications. Hypothetically, Boot could publish a JwtIssuerAuthenticationManagerResolver instead of a JwtDecoder.

This would have wider-reaching consequences than simply allowing issuer-uri to be a list, though. In fact, it would likely lead to something that looks more like the OAuth2 Client Boot configuration since each issuer could easily have different algorithm and audience expectations. While again, that's possible, I'm inclined to keep the Boot properties focused on single-tenancy, which is the most common use case.

Note that the reason the OAuth2 Client properties allow for declaring more than one client is to support multiple identity providers, like Google and Facebook. The fact that they can be used for multi-tenancy is a nice additional benefit, but not the primary use case.

c4soft chooses a multi-tenant approach and I appreciate the fact that there is an open-source solution out there based on Spring Security's resource server that enables this for those who need it.

By the way, @ch4mpy, I appreciate the list of features you generated above, that's really helpful. You and I have discussed some of those already, and I'd welcome further collaboration to find what's a good fit for adding to Spring Security.

@philwebb
Copy link
Member

Thanks @jzheaux. Given the comments above I don't think we should try to support multiple JWT issuer URIs. Thanks anyway for the suggestion and thanks for open-sourcing your springaddons project.

@philwebb philwebb added status: declined A suggestion or change that we don't feel we should currently apply and removed type: enhancement A general enhancement for: team-attention An issue we'd like other members of the team to review labels Jun 22, 2022
@philwebb philwebb removed this from the 2.x milestone Jun 22, 2022
@ch4mpy
Copy link
Author

ch4mpy commented Jun 22, 2022

the reason the OAuth2 Client properties allow for declaring more than one client is to support multiple identity providers, like Google and Facebook

@jzheaux @philwebb how is the spring-boot app serving resources to such clients supposed to handle end-users identity with a single configured authorization-server in that case? Should it choose Google or Facebook?

I appreciate the list of features you generated above, that's really helpful. You and I have discussed some of those already, and I'd welcome further collaboration to find what's a good fit for adding to Spring Security.

Those features are mostly configuration of existing spring-security elements, reason for me listing it here in spring-boot.

There are only two things I use which are not from spring-security:

I'd welcome further collaboration to find what's a good fit for adding to Spring Security

Do not easitate to open tickets on https://github.com/ch4mpy/spring-addons if you find something useful for the comunity, I'll do my best to help you migrate corresponding code from this repo to spring-security or spring-boot ones.

@jzheaux
Copy link
Contributor

jzheaux commented Jun 22, 2022

Should it choose Google or Facebook?

Neither. Google's token is for protecting Google's resource servers. Or, IOW, if the resources being served are on Google resource servers, then you would use Google's token.

@ch4mpy
Copy link
Author

ch4mpy commented Jun 22, 2022

Google's token is for protecting Google's resource servers

Only? https://developers.google.com/identity/sign-in/web/backend-auth

Beyond what Google offers as OAuth2 authentication, there are online OIDC providers out there which allow to define audience(s).

I have quite a few (dozens) resource-servers at hand that have to accept various issuers either because

  • they serve clients from various editors, some sharing the same authorization-server and some having their own
  • they serve a single client with different groups of users, each group having it's own identity provider (internal and external users are stored in different databases and are not authorized by the same server)

@jzheaux
Copy link
Contributor

jzheaux commented Jun 22, 2022

Google's documentation refers to flowing the user's identity to a server, not to authorizing a client's request for resources protected by that server. If you haven't already, please see the link I posted in my previous comment. I also feel like the maintainers' comments in this thread are a good representation of my position. That said, we might be getting off-topic discussing how various providers interpret the spec.

The resource server arrangements you describe sound like classic multi-tenancy examples. I feel I already stated my position on changing Boot's properties from being single-tenant to multi-tenant, and I've got nothing more to add. Should multi-tenant resource servers become quite common, we can always take another look.

@jasonrberk
Copy link

jasonrberk commented Jul 13, 2022

spring-projects/spring-security#8648 (comment) mostly solved my issue with having multi tenant issuers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

9 participants