Given https://github.com/spring-projects/spring-security/issues/11958 and https://github.com/spring-projects/spring-security/issues/11963, an application can change from default deny in Spring Security 6 by modifying their URL registry, post-processing the RequestMatcherDelegatingAuthorizationManager, or publishing one of their own.

However, these approaches may be too cumbersome for folks, especially in the midst of upgrading from 5 to 6. Consider the following program:

<http>
    <intercept-url pattern="/url/one/**" access="hasRole('ONE')"/>
    <intercept-url pattern="/url/two/**" access="hasAuthority('AUTHORITY_TWO')"/>
    <intercept-url pattern="/url/three/**" access="authenticated"/>
</http>

In Spring Security 5, the default for unmatched endpoints is to abstain (which has the effect of permitting the request), and the default in Spring Security 6 is to deny.

For an application to revert to the Spring Security 5 behavior, they can first do:

<http use-authorization-manager="false">
    <intercept-url pattern="/url/one/**" access="hasRole('ONE')"/>
    <intercept-url pattern="/url/two/**" access="hasAuthority('AUTHORITY_TWO')"/>
    <intercept-url pattern="/url/three/**" access="authenticated"/>
</http>

Or, second, they can permit all at the end of their definition:

<http>
    <intercept-url pattern="/url/one/**" access="hasRole('ONE')"/>
    <intercept-url pattern="/url/two/**" access="hasAuthority('AUTHORITY_TWO')"/>
    <intercept-url pattern="/url/three/**" access="authenticated"/>
    <intercept-url pattern="/**" access="permitAll"/>
</http>

(If they aren't using expressions, the point is moot since use-expressions="false" does not use AuthorizationManager. If that fact changes, the point still stands as an app can also do <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>)

If needed, the application can introduce a BeanPostProcessor to post-process the RequestMatcherDelegatingAuthorizationManager instance, or it can construct one of its own and replace the XML intercept-url elements with an authorization-manager-ref attribute in <http>.

Though the options are plentiful, it needs to be decided if they are sufficient. For example, one way that may be simpler is to introduce an XML attribute like so:

<http default-authorization-decision="abstain">
    <intercept-url pattern="/url/one/**" access="hasRole('ONE')"/>
    <intercept-url pattern="/url/two/**" access="hasAuthority('AUTHORITY_TWO')"/>
    <intercept-url pattern="/url/three/**" access="authenticated"/>
</http>

This attribute would imply use-authorization-manager="true". If use-authorization-manager="true", then default-authorization-decision would default to deny. authorization-manager-ref would supersede it.

A corollary can be made for Java. Consider the same application:

@Bean 
SecurityFilterChain app(HttpSecurity http) {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .mvcMatchers("/url/one/**").hasRole("ONE")
            .mvcMatchers("/url/two/**").hasAuthority("AUTHORITY_TWO")
            .mvcMatchers("/url/three/**).authenticated()
        );

    return http.build();
}

In Spring Security 5, the default for unmatched endpoints is to abstain (which has the effect of permitting the request), and the default in Spring Security 6 is to deny.

For an application to revert to the Spring Security 5 behavior, they can first do:

A corollary can be made for Java. Consider the same application:

@Bean 
SecurityFilterChain app(HttpSecurity http) {
    http
        .authorizeRequests((authorize) -> authorize
            .mvcMatchers("/url/one/**").hasRole("ONE")
            .mvcMatchers("/url/two/**").hasAuthority("AUTHORITY_TWO")
            .mvcMatchers("/url/three/**).authenticated()
        );

    return http.build();
}

Or, second, they can permit all at the end of their definition:

@Bean 
SecurityFilterChain app(HttpSecurity http) {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .mvcMatchers("/url/one/**").hasRole("ONE")
            .mvcMatchers("/url/two/**").hasAuthority("AUTHORITY_TWO")
            .mvcMatchers("/url/three/**).authenticated()
            .anyRequest().permitAll()
        );

    return http.build();
}

If needed, the application can introduce an ObjectPostProcessor to post-process the RequestMatcherDelegatingAuthorizationManager instance, or it can construct one of its own and replace the mvcMatchers call with a call to anyRequest().access(AuthorizationManager).

Though the options are plentiful, it needs to be decided if they are sufficient. For example, one way that may be simpler is to introduce a DSL property like so:

@Bean 
SecurityFilterChain app(HttpSecurity http) {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .mvcMatchers("/url/one/**").hasRole("ONE")
            .mvcMatchers("/url/two/**").hasAuthority("AUTHORITY_TWO")
            .mvcMatchers("/url/three/**).authenticated()
            .defaultAuthorizationDecision(new AuthorizationDecision(true))
        );

    return http.build();
}

Comment From: jzheaux

Having described the options above, I'll throw in my two cents here.

For my taste, a catch-all <intercept-url pattern="/**" access="permitAll"/> or anyRequest().permitAll() is a good balance between simplicity for the user and low maintenance for the team. I can't say that I've thought of every scenario, but this is the way that most of the tests were changed when applying https://github.com/spring-projects/spring-security/issues/11929. It's also nice because it aligns with our samples and demos that almost universally declare a catch-all rule.

I can see how default-authorization-decision is nice because it can be property-driven at that point.

Comment From: jgrandja

@jzheaux I believe this ticket is no longer valid since we decided to document the migration strategy instead. I'll leave this ticket for you to close.

For the migration guide, please document the recommended strategy:

To test Spring Security 6.0 compatibility in a Spring Security 5.8 (or earlier) application, add the authorization rule anyRequest().denyAll() or <intercept-url pattern="/**" access="denyAll"/>. If the application performs as expected, then the application is Spring Security 6.0 compatible. If certain requests are denied, then the user should incrementally add the specific authorization rule to resolve each of the denied requests. After all denied requests have been resolved, remove anyRequest().denyAll() or <intercept-url pattern="/**" access="denyAll"/>.

Alternatively, if the application has upgraded to Spring Security 6.0 and certain requests are denied, and is different behaviour before the upgrade, then add the authorization rule anyRequest().permitAll() or <intercept-url pattern="/**" access="permitAll"/>. This will restore the original behaviour before the upgrade. Now the user can incrementally add the specific authorization rule to resolve each of the denied requests. After all denied requests have been resolved, remove anyRequest().permitAll() or <intercept-url pattern="/**" access="permitAll"/>.

Comment From: jzheaux

Closed the ticket as team-attention. The purpose of the ticket was to bring together ideas that had been shared by each team member into one location, which it achieved.