grails / grails-spring-security-core

Grails Spring Security Core Plugin
Apache License 2.0
259 stars 222 forks source link

How to wire in custom WebSecurityConfigurer in grails 5 #706

Open ShurikAg opened 2 years ago

ShurikAg commented 2 years ago

Issue description

I decided to post here, as I am not getting anything from the Stackoverflow post.

As part of moving our environments to Kubernetes, we are using Ambassador as the ingress service. The app itself (the API side) is using Grails spring-security plugin. Having these two together, the preflight requests are not passing as the implementation will deny these requests. (see: https://www.getambassador.io/docs/edge-stack/latest/topics/using/cors/#authservice-and-cross-origin-resource-sharing).

I was digging for a solution for quite some time now, and the one that stands out in many places is to create a custom WebSecurityConfigurer (also, as suggested by Ambassador).

We I created the following:

package priz.api.security

import grails.compiler.GrailsCompileStatic
import org.springframework.boot.autoconfigure.security.SecurityProperties
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource

import javax.servlet.http.HttpServletRequest

@GrailsCompileStatic
@Configuration
@EnableWebSecurity
@Order(SecurityProperties.DEFAULT_FILTER_ORDER)
class SecurityConfig extends WebSecurityConfigurerAdapter {

    public void configure(final HttpSecurity http) throws Exception {
        http
                .cors().configurationSource(new PermissiveCorsConfigurationSource()).and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("**").permitAll();
    }

    private static class PermissiveCorsConfigurationSource implements CorsConfigurationSource {
        /**
         * Return a {@link CorsConfiguration} based on the incoming request.
         *
         * @param request
         * @return the associated {@link CorsConfiguration}, or {@code null} if none
         */
        @Override
        public CorsConfiguration getCorsConfiguration(final HttpServletRequest request) {
            final CorsConfiguration configuration = new CorsConfiguration();
            configuration.setAllowCredentials(true);
            configuration.setAllowedHeaders(Collections.singletonList("*"));
            configuration.setAllowedMethods(Collections.singletonList("*"));
            configuration.setAllowedOrigins(Collections.singletonList("*"));
            return configuration;
        }
    }
}

However, this config is not getting picked up. Is there anything additional that I have to do to register it? My expectation was that the annotations are taking care of that. Also tried @EnableGlobalMethodSecurity instead, the same result.

Thanks for the advice.

I could also create an interceptor, but Grails interceptors cannot intercept the endpoints provided by the Spring Security core/rest plugins since the priority of their interceptors are higher than that of Grails interceptors

codeconsole commented 1 month ago

It won't get picked up unless you have @ComponentScan on your Application class.

@CompileStatic
@ComponentScan
class Application {

    static void main(String[] args) {
        Grails.run(Application, args)
    }
}

https://guides.grails.org/grails-configuration-properties/guide/index.html

rainboyan commented 1 month ago

In Grace, this is no longer needed, when the Application.groovy compiled, the @SpringBootApplication annotation will be added, just like Spring Boot does.

codeconsole commented 1 month ago

@rainboyan in some cases developers might not want @ComponentScan or @EnableAutoConfiguration enabled by default with @SpringBootApplication. An alternative for the above configuration would be to enable it specifically:

@CompileStatic
@Import(SecurityConfig)
class Application {
    static void main(String[] args) {
        Grails.run(Application, args)
    }
}
rainboyan commented 1 month ago

@codeconsole I don't quite understand what you said about the special case, I can think of using @Import to load internal Configurations, or to test specific Configurations. I prefer to use @Import on the *AutoConfiguration, instead of the main entry Application. In the *AutoConfiguration classes, you can use @Conditional @AutoConfigureOrder @Order to constrain when the auto-configuration should apply. In some case, if you find that specific auto-configuration classes that you do not want are being applied, you can use the exclude attribute of @SpringBootApplication to disable them, as shown in the following example:

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class MyApplication {

}
codeconsole commented 1 month ago

@rainboyan if you used @ComponentScan, it scans all your class files. @Import will just load the individual configuration. Using @Import is also more predictable behavior. Here we are only talking about loading a single Java Config. Using any other annotation might be overkill and end up with undesired behavior.

codeconsole commented 1 month ago

In Grace, this is no longer needed, when the Application.groovy compiled, the @SpringBootApplication annotation will be added, just like Spring Boot does.

@rainboyan using Grace 2023.0.0-M6 I created a sample app and still needed to add @Import in order to get the configuration to load.

rainboyan commented 1 month ago

@codeconsole I have checkout your code to run, and I can confirm that you should register some OAuth2 clients and providers under the spring.security.oauth2.client prefix in the application.yml first, otherwise OAuth2ClientRegistrationRepositoryConfiguration won't be applied, that's what I said earlier about the benefits of using Spring Boot's AutoConfiguration.

---
spring:
    security:
        oauth2:
            client:
                registration:
                    github:
                        clientId: github-client-id
                        clientSecret: github-client-secret

https://docs.spring.io/spring-boot/reference/web/spring-security.html#web.security.oauth2

codeconsole commented 1 month ago

@rainboyan I have my registrations in application.properties (since they are private). That is why I have application.properties in .gitignore