Marc
Marc

Reputation: 93

H2-Database console not opening with Spring-Security

I'm using the H2-Database and Spring Security, but I'm unable to open it in the browser at http://localhost:8080/h2-console

Here my pom.xml (only the H2 entry)

<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
</dependency>

Here my application.properties

spring.datasource.url=jdbc:h2:file:/data/noNameDB
spring.h2.console.enabled=true
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=admin
spring.datasource.password=admin
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.path=/h2-console
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jackson.serialization.fail-on-empty-beans=false

And here is my SecurityConfig.java

import com.example.noName.security.JwtAuthenticationEntryPoint;
import com.example.noName.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;



@Configuration
@EnableWebSecurity
public class SecurityConfig {


    private static final String[] AUTH_WHITE_LIST = {
            "/v3/api-docs/**",
            "/swagger-ui/**",
            "/v2/api-docs/**",
            "/swagger-resources/**",
            "/h2-console/**",
            "/console/**",
            "/account/**"
    };

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }

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


    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }



    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf()
                .disable()
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests()
                .requestMatchers(AUTH_WHITE_LIST)
                .permitAll()
                .and()
                .headers()
                .frameOptions()
                .disable()
                .and()
                .authorizeHttpRequests()
                .anyRequest()
                .authenticated()
                .and()
                .httpBasic()
                .and()
                .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .httpBasic();

        return http.build();
    }

}

The following is shown in the console if I try to access the console via http://localhost:8080/h2-console

INFO 3664 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO 3664 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
INFO 3664 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms

I have already tried everything I could find on the Internet.

The funny thing is that the "exception handling" works for Swagger. If I try to access the database via:

http://localhost:8080/h2-console

I always get the error:

401 - Unauthorized

Each one is strange, because the access was allowed in the SecurityConfig.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .cors()
            .and()
            .csrf()
            .disable()
            .exceptionHandling()
            .authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeHttpRequests()
            .requestMatchers(AUTH_WHITE_LIST)
            .permitAll()
            .and()
            .headers()
            .frameOptions()
            .disable()
            .and()
            .authorizeHttpRequests()
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic()
            .and()
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .httpBasic();

    return http.build();
}

I can access the database through an internal database test. This is provided by Intellij.

However working/editing in the database is not possible through this.

AND:

If I change the AUTH_WHITE_LIST to this, it works.

private static final String[] AUTH_WHITE_LIST = {
       "/**"
};

Upvotes: 9

Views: 13612

Answers (9)

Mehdi Rahimi
Mehdi Rahimi

Reputation: 2566

For Spring Boot version 3+ (Spring Security version 6+) you can use a configuration file with SecurityFilterChain bean like this code snippet:

import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console;

@Configuration
public class ProjectConfig {
    @Bean
    public SecurityFilterChain chain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeHttpRequests(c -> c.requestMatchers(toH2Console()).permitAll())
                .csrf(c -> c.ignoringRequestMatchers(toH2Console()).disable())
                .headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));

        return httpSecurity.build();
    }
}

There are four parameters that should be considered:

  1. Import h2-console path (toH2Console).
  2. Permit this path requests using authorizeHttpRequests mehod.
  3. Disable csrf for this path using ignoringRequestMatchers method.
  4. Disable frame header options for the same origin using sameOrigin method.

Upvotes: 1

Artem Zubkov
Artem Zubkov

Reputation: 111

In Spring boot 3, you need to add the following configurations:

first

.csrf(csrf -> csrf
            .ignoringRequestMatchers(toH2Console())
            .disable())

second

.authorizeHttpRequests(auth -> auth
        .requestMatchers(toH2Console()).permitAll())

third

.headers(headers -> headers.frameOptions(FrameOptionsConfig::disable))

Upvotes: 11

e1st0rm
e1st0rm

Reputation: 115

Step 1: Do import in your SecurityConfig class

import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console;

Step 2: Add filter in to context. Pay attention to the lines containing the comment '<-'

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception 
    {
        http
            .authorizeHttpRequests((requests) -> requests
                // another matchers
                .requestMatchers(toH2Console()).permitAll() // <-
                // another matchers
                .anyRequest().authenticated()
            )
            .formLogin((login) -> login
                .loginPage("/login")
                .permitAll()
            )
            .logout((logout) -> logout
                .permitAll()
            )
            .csrf((protection) -> protection
                .ignoringRequestMatchers(toH2Console()) // <- 
            )
            .headers((header) -> header
                .frameOptions().sameOrigin()
            );
        return http.build();
    }
}

Upvotes: 2

h.z.
h.z.

Reputation: 17

Below is the snippet of my code. I use Spring Boot 3.0.6. It works.

@Configuration
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().requestMatchers(antMatcher("/h2-console/**")).permitAll()
                .and().csrf().ignoringRequestMatchers(antMatcher("/h2-console/**"))
                .and().headers().frameOptions().disable();

        return http.build();
    }
}

Upvotes: 2

Braklord
Braklord

Reputation: 21

It is enough to create a new AntPathRequestMatcher within:

.requestMatchers(new AntPathRequestMatcher("/h2-console/**")).permitAll()

To avoid problems of display the frames (X-Frame-Options header will be not added by Spring Security in the response):

http.headers().frameOptions().disable();

Upvotes: 2

Lemmy_Caution
Lemmy_Caution

Reputation: 139

I've found this article, where the question is well explained: h2-database-console-with-spring-security

Note: The article is not updated to Spring Security 6, due the known deprecation of the WebSecurityConfigurerAdapter class. The code should be updated to:

package com.myapp.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console;  
@Configuration
public class CustomSecurityConfiguration{
    
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
        .requestMatchers("/").permitAll()
        .requestMatchers(toH2Console()).permitAll()
        .and().csrf().ignoringRequestMatchers(toH2Console())
        .and().formLogin()
        .and().httpBasic();
        
        // Use them only in development or demo environments. NEVER in production !!!
        // http.csrf().disable();
        http.headers().frameOptions().disable();
        
        return http.build();
    }
}

Upvotes: 1

skryvets
skryvets

Reputation: 3049

You can also explicitly fallback to antMatcher inside requestMatchers:

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
....

.....
return http
            .authorizeHttpRequests()
              .requestMatchers(antMatcher("/h2-console/**")).permitAll()
              .anyRequest().authenticated()
            .and()
            .build();

Upvotes: 4

Adam Woźniak
Adam Woźniak

Reputation: 61

According to the post on spring blog about Spring Security without the WebSecurityConfigurerAdapter we can use WebSecurityCustomize for ignoring the whole endpoint from the security. The case fits perfectly to publish the h2 console, because we want to ignore only this particular endpoint in the case of security. The working example:

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring().requestMatchers(new AntPathRequestMatcher(H2_CONSOLE_PATH));
}

And that's all what we need, probably the cleanest solution to this problem.

Upvotes: 6

xerx593
xerx593

Reputation: 13289

I could reproduce with spring-boot:3.0.0(web, security, h2, ...) and:

return http
        .authorizeHttpRequests()
        .requestMatchers("/h2-console/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .and()
        .csrf().ignoringRequestMatchers("/h2-console/**")
        .and()
        .headers().frameOptions().sameOrigin()
        .and()
        .build();

(/h2-console is still protected!)

Fix:

(spring-boot-way):

import static //
org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; // !
...
  .requestMatchers(toH2Console()).permitAll()
  ...
  .csrf().ignoringRequestMatchers(toH2Console())

Refs:

It uses internally:

new AntPathRequestMatcher(h2ConsoleProperties.get().getPath() + "/**");

which seems to be different/better than the requestMatcher(String... paths) (AbstractRequestMatcherRegistry), which invokes this (with method==null):

public C requestMatchers(HttpMethod method, String... patterns) {
   List<RequestMatcher> matchers = new ArrayList<>();
   if (mvcPresent) {
      matchers.addAll(createMvcMatchers(method, patterns)); // <- we land here obviously
   } else {
      matchers.addAll(RequestMatchers.antMatchers(method, patterns));     
   }
  return requestMatchers(matchers.toArray(new RequestMatcher[0]));
}

... alternatives:

  • RequestMatchers.antMatcher(null, pathToH2+"/**").
  • AntPathRequestMatcher (for ignore case (and all http methods), prefer constructors to factories;)
  • MvcMatcher (is not possible for h2-console, since it is not mvc;(

BUT/AND

I would not do it (unsecured) in production!

Upvotes: 24

Related Questions