P_impl55
P_impl55

Reputation: 148

Spring Security on GCP App Engine hangs 30+ seconds for each request then errors

I have a simple Spring Boot 2.3.1 app using Spring Security to manage user login/logout (using latest dependencies). The backend storage for the user/role domain model storage is Postgres (and I'm using JPA to access them). Everything works fine locally but when I deploy to GAE via gcloud app deploy, every page on the site--even the /login--pages try to load for 30 seconds then error out with the following 500 error message:

Error: Server Error
The server encountered an error and could not complete your request.

Please try again in 30 seconds.

Reviewing the GAE error logs with gcloud app logs tail -s default shows all normal behavior. The DB Connection to Postgres is fine, there are no WARN or ERROR blocks. I almost wonder if it's something higher-level than my code intercepting the request then stalling, like an ELB perhaps?

Here is my pom.xml:

   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.google.cloud.sql</groupId>
            <artifactId>postgres-socket-factory</artifactId>
            <version>1.0.16</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>

And the @Configuration class with the Spring Security overrides:


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MemberService memberService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        String authByUsernameQuery = "select m.username as principal, name from member m" +
                " inner join member_role mr on m.id = mr.member_id" +
                " inner join role r on mr.role_id = r.id" +
                " where m.username=?";

        auth.jdbcAuthentication().dataSource(dataSource)
                .usersByUsernameQuery("select username as principal, password as credentials, true from member where username=?")

                .authoritiesByUsernameQuery(authByUsernameQuery)
                .passwordEncoder(passwordEncoder()).rolePrefix("ROLE_");
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager customAuthenticationManager() throws Exception {
        return authenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        // force https -- does this need more work somehow?
        // Still doesn't work in GAE when commented out
        http.requiresChannel()
                .anyRequest().requiresSecure();

        http.authorizeRequests().antMatchers("/register", "/login", "/status").permitAll()
                .antMatchers("/index").hasAnyRole("USER, ADMIN")
                .and().formLogin().loginPage("/login").permitAll()
                .defaultSuccessUrl("/")
                // smart logout from spring (no need for explicit endpoint)
                .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login");
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(memberService).passwordEncoder(passwordEncoder());
    }
}

An example of a Controller with the login method is below, but I can't reach this permitted page due to the stalling:

@Controller
public class MemberController {

    private static final Logger log = LoggerFactory.getLogger(MemberController.class);

    @Autowired
    private MemberService memberService;

    @Autowired
    private AppConfig appConfig;

    @RequestMapping("/status")
    @GetMapping
    public String check() {
        return "OK";
    }

    @GetMapping("/login")
    public String showLoginForm(){
        return "views/loginForm";
    }

So those endpoints--and every other I try--hang until that error. The project UI pages are using Thymeleaf with a standard setup in resources/: enter image description here

I'm blocked at this point. I ran a simpler experiment to deploy another Java App without Spring Security and it worked just fine. Then I added Spring Security to this simpler project and it will worked because I was instantly prompted with Spring Security's default login screen. Yet my code everything hangs and I've never successfully loaded a page. I feel like it must be something I have yet to add for HTTPS/SSL/Cert but I can't figure out what.

Any ideas? Thanks in advance.

Upvotes: 0

Views: 328

Answers (1)

P_impl55
P_impl55

Reputation: 148

Fixed.

The timeouts had nothing to do with Spring Security or SSL. The default size for GCP instance F1 is 256MB RAM and that was causing enough resource strain that once I included Spring Security and a few other dependencies, the app was crashing trying to handle a request. There wasn't an explicit OutOfMemoryError like you'd normally see, but instead I found the Spring welcome text in the logs over and over again. I bumped up the instance size to F4 and it worked well.

This threw me for a loop but at least the security looks fine.

Upvotes: 3

Related Questions