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

Simplify Request Authorization Configuration #13057

Closed
jzheaux opened this issue Apr 15, 2023 · 2 comments
Closed

Simplify Request Authorization Configuration #13057

jzheaux opened this issue Apr 15, 2023 · 2 comments
Assignees
Labels
in: config An issue in spring-security-config status: duplicate A duplicate of another issue type: enhancement A general enhancement

Comments

@jzheaux
Copy link
Contributor

jzheaux commented Apr 15, 2023

Nearly every application needs to override Spring Security's default authorization rule that all requests require the they be authenticated.

Many applications have static resources that are permitted for example, and many applications want to permit Spring Boot's /error endpoint. Beyond that, many applications use RBAC, ABAC or some other form of authorization that requires corresponding Spring Security configuration.

This is quite simple in Spring Security already. For example, if I have a default Spring Security application, I can achieve a configuration identical to the default while changing only the authorization rules by doing the following:

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/my", "/static", "/resources").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authetnicated()
    )
    .formLogin(Customizer.withDefaults())
    .httpBasic(Customizer.withDefaults())

Some of the above does not have to do with authorization, though. And it would be nice to not have to specify that as well. For example, many applications do not need to specify authentication mechanisms as they are inferrable by Spring Boot.

This leads to confusion and potential misconfiguration issues when a user doesn't realize that even though they specified Boot properties, they still need to specify the mechanism in the DSL. For example, if I use spring-boot-starter-oauth2-resource-server and am using the Boot properties, I might easily assume that I can specify my authorization rules like so:

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/my", "/static", "/resources").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authetnicated()
    )

But, I would be wrong. Because I am overriding authorization rules, I also need to re-specify my authentication mechanisms. Because I don't know this, my application may end up less secure (since I've specified resource server configuration in Boot that is now no longer in effect).

Instead of having to specify authorization alongside authentication, it would be nice to be able to specify it separately.


One way to do this would be with a bean, like so:

@Bean 
Customizer<AuthorizeHttpRequestsConfigurer> authorization() {
    return (authorize) -> authorize
        .requestMatchers("/my", "/static", "/resources").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated();
}

In this case, HttpSecurityConfiguration would pick up the Customizer<AuthorizeHttpRequestConfigurer> bean and apply it before returning a HttpSecurity prototype instance.

In this case, Boot publishing a SecurityFilterChain would look something like:

@ConditionalOnMissingBean
@Bean 
SecurityFilterChain springSecurityFilterChain(HttpSecurity http) {
    http
        // ... no need to specify authorizeHttpRequests since was applied when prototype bean was created
        .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()))
        // ... or whatever authentication mechanisms are relevant
    return http.build();
}

The nice thing about this is that it is a pattern that could potentially be followed for other configurers, which may lead to being able to break up HttpSecurity configuration into smaller consumable chunks. The downside is that it's a departure from how Spring Security is traditionally configured.


Or publishing an AuthorizationManager bean maintains a more traditional relationship with HttpSecurity instances:

@Bean 
AuthorizationManager<RequestAuthorizationContext> authorization(RequestMatcherDelegatingAuthorizationManager.Builder builder) {
    return builder
        .requestMatchers("/my", "/static", "/resources").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated().build();
}

(RequestMatcherDelegatingAuthorizationManager DSL methods do not exist yet, but please reference MessageMatcherDelegatingAuthorizationManager as an example)

Another nice thing about using AuthorizationManager is the rest of the API fits in nicely:

@Bean 
AuthorizationManager<RequestAuthorizationContext> authorization() {
    return AuthenticatedAuthorizationManager.authenticated();
}

The main downside of this route is that it duplicates the DSL already found in AuthorizeHttpRequestConfigurer.

In that case, Boot publishing a SecurityFilterChain would look something like:

@ConditionalOnMissingBean
@Bean 
SecurityFilterChain springSecurityFilterChain(HttpSecurity http) {
    http
        .authorizeHttpRequests(Customizer.withDefaults())
        // ... the default would be to pick up an AuthorizationManager bean of the correct type
        .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()))
        // ... or whatever authentication mechanisms are relevant
    return http.build();
}

In both cases, because authorization configuration can be declared separately, an application can do so without having to know that they must re-specify authentication mechanisms.

At this point, I feel amenable to both routes, but I slightly prefer the first one because of the broader potential it has for the entire DSL (other Customizer beans, for example). While it's not terribly obvious to a coder that Customizer<AuthorizeHttpRequestsConfigurer> is the right bean to publish, this could be improved with a marker inferface like RequestAuthorizationCustomizer. Feedback is welcome.

I distantly recall that @rwinch had a concern, and, Rob, please forgive me, but could you add your concern (if you still have it) here to this ticket for the record so that I don't forget again? :)

@jzheaux jzheaux added in: config An issue in spring-security-config type: enhancement A general enhancement labels Apr 15, 2023
@jzheaux jzheaux changed the title Simplify Authorization Rule Configuration Simplify Request Authorization Configuration Apr 17, 2023
@jzheaux
Copy link
Contributor Author

jzheaux commented Apr 17, 2023

Alternatively, customizer beans for authentication mechanisms could be specified, for example:

@Bean 
Customizer<OAuth2ResourceServerConfigurer> resourceServer() {
    return (oauth2) -> oauth2.jwt((jwt) -> jwt.jwkSetUri("https://jwks.example.org"));
}

or perhaps even:

@Bean 
Customizer<JwtConfigurer> resourceServer() {
    return (jwt) -> jwt.jwkSetUri("https://jwks.example.org");
}

If Boot were to specify this bean, then when applications override authorization rules, they don't need to re-specify oauth2ResourceServer.

@jzheaux
Copy link
Contributor Author

jzheaux commented Dec 17, 2024

Closing in favor of #16258 as its scope is broader. This can be reopened to re-explore an AuthorizationManager bean in the event that #16258 not ultimately happen

@jzheaux jzheaux closed this as completed Dec 17, 2024
@jzheaux jzheaux added the status: duplicate A duplicate of another issue label Dec 17, 2024
@jzheaux jzheaux self-assigned this Dec 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: config An issue in spring-security-config status: duplicate A duplicate of another issue type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

1 participant