Spring Boot /h2-console throws 403 with Spring Security 1.5.2
Asked Answered
T

17

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.

Takishatakken answered 5/5, 2017 at 0:30 Comment(1)
have you referred this example on github on spring boot with securityTurtleneck
T
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.

Takishatakken answered 5/5, 2017 at 20:25 Comment(3)
you can see my comments in my answer.Carthusian
Doesn't this break the auth?Mccray
Authorization is enabled so if the user does not have the ROLE assigned, she will be denied accessTakishatakken
A
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.

Anterior answered 2/1, 2020 at 8:8 Comment(5)
Thanks a lot. This is the actual answer that works in 2020.Regionalism
Much easier than accepted answer, works well in v2.2.Chios
It seems this is the best answer.Dispersoid
Perfect answer, tested with spring boot version: 2.2.2.RELEASE, h2 version: 1.4.200Lyricism
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: https://mcmap.net/q/346870/-spring-boot-h2-console-throws-403-with-spring-security-1-5-2Erikerika
I
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. =)

Ichneumon answered 30/10, 2018 at 14:29 Comment(5)
shouldn't it be h2-console?Regionalism
actually it can be anything you had defined. I updated it to the standard way. Thanks @theprogrammer.Ichneumon
looks like @Para D's answer is now correct: web.ignoring().antMatchers("/h2-console/**")Intensify
@MetalRules, exactly, this one doesn't help.Allies
disabling frame headers worked for me!Bradley
S
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/**"));
    }
}
Sharmainesharman answered 5/5, 2023 at 19:0 Comment(1)
For those who thought you can ignore the new AntPathRequestMatcher() from this - DO NOT. It doesn't seem to work without it.Caudal
C
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..

Chacon answered 9/8, 2019 at 9:45 Comment(0)
Q
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/**");
}
Quassia answered 10/7, 2022 at 16:9 Comment(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.Catalinacatalo
H
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();
}
Heterogenous answered 13/2, 2023 at 21:57 Comment(0)
I
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
Intertwine answered 2/3, 2023 at 7:57 Comment(1)
Thanks ! This is the only config that works for me ;-)Tedra
C
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();
        }

    }
Carthusian answered 5/5, 2017 at 13:37 Comment(0)
T
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.

Takishatakken answered 5/5, 2017 at 20:25 Comment(3)
you can see my comments in my answer.Carthusian
Doesn't this break the auth?Mccray
Authorization is enabled so if the user does not have the ROLE assigned, she will be denied accessTakishatakken
C
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()
Clamber answered 18/6, 2020 at 12:43 Comment(0)
W
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);            
}
Wadsworth answered 9/11, 2021 at 14:31 Comment(0)
D
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();
    }
}
Disjointed answered 26/8, 2023 at 21:11 Comment(0)
P
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();
}
Patella answered 28/9, 2022 at 7:48 Comment(0)
B
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();
}
Brittni answered 17/12, 2023 at 12:2 Comment(0)
S
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
Seagraves answered 17/10, 2019 at 14:58 Comment(0)
L
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)

Leukemia answered 24/2, 2023 at 17:0 Comment(0)
L
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();
}
Lemley answered 10/10, 2023 at 13:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.