50

We recently upgraded from Spring Boot 1.4.1 to 1.5.2. One of the features of 1.5.2 is that if Spring Security is part of the package then it is protected by basic auth. I am unable to access the /h2-console even after basic auth. It throws 403 forbidden.

application.yml:

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:file:../app-db/app_db;AUTO_SERVER=TRUE
    username: sa
    password: sa
    initialize: false
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: true
    database-platform: org.hibernate.dialect.H2Dialect
  h2:
    console:
      enabled: true
      settings:
        web-allow-others: true
  allowed:
    resources: /h2-console/**

I have even explicitly allowed /h2-console/**

 httpSecurity.authorizeRequests()
                .antMatchers(allowedResources)                  
                .permitAll()

I keep getting 403 when trying to access localhost:8080/h2-console. I tried many settings as well as putting:

management.security.enabled=true
security.basic.enabled=true

But I am unable to access the h2-console.

1
  • have you referred this example on github on spring boot with security Commented May 5, 2017 at 0:59

17 Answers 17

100

Since H2 has it's own authentication provider, you can skip the Spring Security for the path of h2 console entirely in the same way that you do for your static content.

In order to do that, in your Spring security config, you have to override the configuration method which takes an instance of org.springframework.security.config.annotation.web.builders.WebSecurity as a parameter instead of the one which takes an instance of org.springframework.security.config.annotation.web.builders.HttpSecurity

    @Override
    public void configure(WebSecurity web) throws Exception {
        web
            .ignoring()
            .antMatchers("/h2-console/**");
    }

If you're using h2 in a production environment, make sure you set up the proper security measures (things like, setting a non-obvious path, good password, ip white list) for your h2 console.

5
  • 15
    Thanks a lot. This is the actual answer that works in 2020. Commented Jan 29, 2020 at 20:35
  • 5
    Much easier than accepted answer, works well in v2.2.
    – Ava
    Commented Feb 2, 2020 at 18:17
  • 2
    It seems this is the best answer.
    – mazend
    Commented May 25, 2021 at 13:31
  • Perfect answer, tested with spring boot version: 2.2.2.RELEASE, h2 version: 1.4.200
    – IuR
    Commented Dec 6, 2021 at 14:42
  • 3
    This alone is not adequate. We also need to set X-Frame-Options otherwise modern browsers will refuse to render the frames in h2-console. This answer is more complete: stackoverflow.com/a/69899965/476712
    – lorefnon
    Commented Mar 13, 2022 at 6:52
88

Spring security blocks /h2-console (or the path you configured in your application.yaml) path for H2 database.

To access the H2 console just add the below code to your WebSecurityConfigurerAdapter.

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/h2-console/**").permitAll();

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

Don't use this configuration in a production environment. =)

5
  • shouldn't it be h2-console? Commented Jan 29, 2020 at 20:32
  • actually it can be anything you had defined. I updated it to the standard way. Thanks @theprogrammer.
    – argoth
    Commented Jan 31, 2020 at 11:26
  • 7
    looks like @Para D's answer is now correct: web.ignoring().antMatchers("/h2-console/**")
    – MetalRules
    Commented Apr 29, 2020 at 22:13
  • @MetalRules, exactly, this one doesn't help.
    – mate00
    Commented Oct 31, 2021 at 9:57
  • disabling frame headers worked for me! Commented Aug 5, 2022 at 18:04
11

With Spring Boot 3, the following works for me:

@Configuration 
class securityConfig {
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers(new AntPathRequestMatcher("/h2-console/**"));
    }
}
1
  • For those who thought you can ignore the new AntPathRequestMatcher() from this - DO NOT. It doesn't seem to work without it.
    – Navneeth S
    Commented Jul 19, 2023 at 8:58
5

I want to provide configuration similar to what is proposed by @argoth, but a bit more production ready :)

@Profile("h2") // to make sure it is active only if h2 profile is active
@Configuration
@ConditionalOnProperty( //to make sure it is active if console is enabled
    value="spring.h2.console.enabled", 
    havingValue = "true", 
    matchIfMissing = false)
public class H2SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // this may not be required, depends on your app configuration
        http.authorizeRequests()
                // we need config just for console, nothing else             
                .antMatchers("/h2_console/**").permitAll();
        // this will ignore only h2-console csrf, spring security 4+
        http.csrf().ignoringAntMatchers("/h2-console/**");
        //this will allow frames with same origin which is much more safe
        http.headers().frameOptions().sameOrigin();
    }
}

In fact there was similar configuration done in boot 1.3 which was called H2ConsoleSecurityConfiguration, but now it's gone: Old class

github discussion

Upd. very important note here! When you have multiple WebSecurityConfigurerAdapter they may conflict with each other, so if you have another WebSecurityConfigurerAdapter in your code, you will need to somehow merge them. To give you more details on why there will be a conflict, it will happen due to each adapter setting up it's own filter chain, and every request will have to pass both filter chains. If one of the chains forbids frameOptions and other doesn't the request won't pass the first chain.. That said, please, be careful with multiple configurers..

5

Though the top voted answer is correct.

As of now WebSecurityConfigurerAdapter is deprecated in newer spring security version and the way to go about it is creating Bean for WebSecurityCustomizer. Below Bean in your security configuration class can do the trick.

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring().antMatchers("/h2-console/**");
}
1
  • 👋🏻 from 2023: You are asking Spring Security to ignore Mvc [pattern='/h2-console/**']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
    – maksimov
    Commented Jul 8, 2023 at 19:49
5

As WebSecurityConfigurerAdapter is deprecated from Spring Security 5.7.0-M2, a new way to do this is to use permitAll via HttpSecurity#authorizeHttpRequests as recommended in this Spring blog. You also need to disable CSRF but only for H2-console. And then allow X-Frame options for frames from the same origin of the page. It is still not recommended to use this in production but with this, you can continue to test your security with CSRF enable for the rest of the site while still having access to H2-console. Updated for Spring Security 6.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http.authorizeHttpRequests()
            .requestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**")).permitAll()
            .and()
            .csrf().ignoringRequestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**"))
            .and()
            .headers(headers -> headers.frameOptions().sameOrigin())
            .build();
}
5

With SpringBoot version 3.0.3 and Java 17, the below code works for me:

@Configuration
public class SecurityConfiguration {

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().anyRequest();
    }
}

Application.properties file contains below configuration:

spring.application.name=order-service
server.port=8080
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:dbapp
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.security.user.name=user
spring.security.user.password=password
1
  • Thanks ! This is the only config that works for me ;-)
    – Jeremie
    Commented Apr 26 at 5:48
3

I enabled debug logging and saw this:

o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /h2-console/; Attributes: [hasAnyRole('ROLE_USER','ROLE_ACTUATOR')]
2017-05-05 13:16:09.304 DEBUG 90365 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@33d2af72: Principal: org.springframework.security.ldap.userdetails.LdapUserDetailsImpl@7371d5f4: Dn: cn=XYZ,ou=XYZ,ou=Active,ou=ABC_USERS,dc=internal,dc=organization,dc=com; Username: uname; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; CredentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 86EF50EF548ED4DBCE4D661AEC93F88C; Granted Authorities: ROLE_ADMIN
2017-05-05 13:16:09.305 DEBUG 90365 --- [nio-8080-exec-2] o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@51d3d69, returned: -1
2017-05-05 13:16:09.305 DEBUG 90365 --- [nio-8080-exec-2] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is not anonymous); delegating to AccessDeniedHandler

I realize that my user does not have the ROLE_USER. I was assuming ROLE_ADMIN > ROLE_USER but I still need to understand this a little better.

I updated my settings to:

security:
  basic:
    enabled: true
    authorize-mode: NONE

I am able to access the /h2-console/** now.

3
  • you can see my comments in my answer.
    – Chao Luo
    Commented May 6, 2017 at 0:02
  • Doesn't this break the auth? Commented Mar 6, 2018 at 19:43
  • Authorization is enabled so if the user does not have the ROLE assigned, she will be denied access Commented Mar 6, 2018 at 23:38
3
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", matchIfMissing = true)
static class H2ConsoleSecurityConfiguration 

As you can see the source code in spring boot, if you enable the basic, the spring boot will load spring security configuration H2ConsoleSecurityConfigurer with order SecurityProperties.BASIC_AUTH_ORDER - 10, and the authentication is base on your configuration in security. This is the default security configuration:

public void configure(HttpSecurity http) throws Exception {
            String path = this.console.getPath();
            String antPattern = path.endsWith("/")?path + "**":path + "/**";
            HttpSecurity h2Console = http.antMatcher(antPattern);
            h2Console.csrf().disable();
            h2Console.httpBasic();
            h2Console.headers().frameOptions().sameOrigin();
            // the default role is `USER` and `management.security.roles`
            String[] roles = (String[])this.security.getUser().getRole().toArray(new String[0]);
           // this value is base `security.basic.authorize-mode`, `role`, 'authenticated' and `none`
            SecurityAuthorizeMode mode = this.security.getBasic().getAuthorizeMode();
            if(mode != null && mode != SecurityAuthorizeMode.ROLE) {
                if(mode == SecurityAuthorizeMode.AUTHENTICATED) {
                    ((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated();
                }
            } else {
                ((AuthorizedUrl)http.authorizeRequests().anyRequest()).hasAnyRole(roles);
            }

        }

and you can also create a new configuration to override the default one.

@Configuration
// before the default configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 11)
class CustomH2ConsoleSecurityConfigurer extends WebSecurityConfigurerAdapter {

        @Autowired
        private H2ConsoleProperties console;

        @Override
        public void configure(HttpSecurity http) throws Exception {
            String path = this.console.getPath();
            String antPattern = (path.endsWith("/") ? path + "**" : path + "/**");
            HttpSecurity h2Console = http.antMatcher(antPattern);
            h2Console.csrf().disable();
            h2Console.httpBasic();
            h2Console.headers().frameOptions().sameOrigin();
            // config as you like
            http.authorizeRequests().anyRequest().permitAll();
        }

    }
0
2

I also encountered the same problem when I'm using spring security. Please note the below configuration in the application.properties

spring.h2.console.enabled=true
spring.h2.console.path=/h2

spring.datasource.url=jdbc:h2:file:~/test
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver

In the security configuration under the configure method I've included the following and I'm able to access the h2 console.

        .antMatchers( "/h2/**").permitAll()
0
2

With respect to WebSecurityConfigurerAdapter I think more appropriate and well explained answer is available here Although I have added sample code and it's works fine for me, not only for h2-console but also for Swagger-UI.

private static final String[] AUTH_WHITELIST = {
        // -- Swagger UI v2
        "/v2/api-docs",
        "/swagger-resources",
        "/swagger-resources/**",
        "/configuration/ui",
        "/configuration/security",
        "/swagger-ui.html",
        "/webjars/**",
        // -- Swagger UI v3 (OpenAPI)
        "/v3/api-docs/**",
        "/swagger-ui/**",
        // other public endpoints
        "/h2-console/**",
 };
@Override
protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests().antMatchers("/hello").hasAuthority("USER")
                .and().authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll().anyRequest().authenticated()
                .and().headers().frameOptions().sameOrigin()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);            
}
0
2

Here is an updated config example for spring security 6

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    MvcRequestMatcher.Builder matcher(HandlerMappingIntrospector handlerMappingIntrospector) {
        return new MvcRequestMatcher.Builder(handlerMappingIntrospector);
    }


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvcMatcher) throws Exception {

        http.authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers(mvcMatcher.pattern("/endpoint1")).permitAll() // just an example of an endpoint
                        .requestMatchers(mvcMatcher.servletPath("/h2-console").pattern("**")).permitAll()
                        .anyRequest().authenticated()
                )
                .csrf(AbstractHttpConfigurer::disable)
                .headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));
        return http.build();
    }
}
1

Please note these settings are not recommended for Production:

To enable access to the h2 console, which spring security disallows, do this:

  1. Allow request to "/console/*" in your SecurityFilterChain bean.
  2. Disable Cross Site Request Forgery protection.
  3. Disable X-Frame-Options.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
            .authorizeRequests()
                .antMatchers("/", "/**", "/console/**").permitAll()             
                .and()
                .csrf().disable()
                .headers().frameOptions().disable()             
                .build();
}
1

For Spring Security +6.2 and 7

@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((requests) -> requests
            .requestMatchers("/test/**").authenticated()
            .requestMatchers("/h2-console/**").permitAll());
    http.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll());
    http.formLogin(Customizer.withDefaults());
    http.httpBasic(Customizer.withDefaults());
    http.csrf(AbstractHttpConfigurer::disable);
    http.headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));
    return (SecurityFilterChain) http.build();
}
0

This also helps for me

  #H2 database
    datasource:
      url: jdbc:h2:mem:mytestdb;INIT=RUNSCRIPT FROM 'classpath:/data.sql'
      driverClassName: org.h2.Driver
      username: sa
      password: sa
    main:
        allow-bean-definition-overriding: true
    h2:
      console:
        enabled: true
        path: /h2-console
        settings:
          web-allow-others: true
    allowed:
      resources: /h2-console/**
    security:
      basic:
        enabled: true
        authorize-mode: NONE
0

Spring Boot's PathRequest class has a method toH2Console() that returns RequestMatcher. It reads H2 console path from H2ConsoleProperties, so if you have modified spring.h2.console.path property, you'll get a corresponding matcher

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
            .csrf().ignoringRequestMatchers(PathRequest.toH2Console()).and()
            .headers().frameOptions().sameOrigin().and()
            // ...

You could use same matcher in http.authorizeHttpRequests(...)


Also if you wonder why .csrf().ignoringRequestMatchers("h2-console/**") doesn't work, that's because MvcRequestMatcher treats requests as contextPath + pathWithinApplication and tries to match pattern with the latter, e.g. /h2-console/login.do has contextPath = h2-console and pathWithinApplication = login.do, and "h2-console/**" pattern doesn't match login.do.

So you would have to write something like this

public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector handlerMappingIntrospector) throws Exception {
    MvcRequestMatcher mvcRequestMatcher = new MvcRequestMatcher.Builder(handlerMappingIntrospector)
            .servletPath("/h2-console")
            .pattern("/**");

    return http
            .csrf().ignoringRequestMatchers(mvcRequestMatcher).and()
            .headers().frameOptions().sameOrigin().and()
            // ...

(but still I would recommend using .toH2Console() option)

0

the only reason mine wasn't working because i had not used the @Bean annotation and hence the springsecurity didn't new about my method

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth->auth.anyRequest().permitAll());
    
    //in case of direct page visit show the login page
    http.formLogin(withDefaults());
    
    http.csrf(csrf->csrf.disable());
    http.headers(headers->headers.frameOptions(frameoptions->frameoptions.disable()));
    
    return http.build();
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.