๐Ÿ› [Spring Security] H2 console ํ™œ์„ฑํ™” ์‹œ UnsatisfiedDependencyException ๋ฐœ์ƒ ์ด์Šˆ ํ•ด๊ฒฐ

์ด์„œยท2023๋…„ 10์›” 17์ผ
0

๊ฐœ์š”

spring.h2.console.enabled: true๋กœ ์„ค์ •ํ•˜๋ฉด์„œ Bean์ด ์ •์ƒ์ ์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š” ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒ๋˜์—ˆ์–ด์š”.

Spring Boot๊ฐ€ 3์ ๋Œ€๋กœ ๋ฒ„์ „์ด ์—…๊ทธ๋ ˆ์ด๋“œ ๋˜๋ฉด์„œ Spring Security ์˜์กด์„ฑ ๋ฒ„์ „๋„ ํ•จ๊ป˜ ์˜ฌ๋ผ๊ฐ”์–ด์š”. 2.7.X ๊ธฐ์ค€์œผ๋กœ Spring Security๋Š” 5.7.11 ๋ฒ„์ „์ด์—ˆ์ง€๋งŒ, Spring Boot 3.1.4 ๊ธฐ์ค€์œผ๋กœ Spring Security๋Š” 6.1.4 ๋ฒ„์ „์„ ์˜์กด์„ฑ ์ฃผ์ž…๋ฐ›๊ณ  ์žˆ์–ด์š”.

Spring Boot 2.7.X Dependency Versions

Spring Boot 3.1.4 Dependency Versions



์ด์— ๋”ฐ๋ผ Security์—๋„ ๊ธฐ์กด์˜ ์ฝ”๋“œ๋“ค์ด Deprecated๊ฐ€ ๋œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์œผ๋ฉฐ, Security์˜ ๊ฐœ๋…์€ ๊ฐ™์ง€๋งŒ ๋ฉ”์„œ๋“œ๋ช… ํ˜น์€ ์ƒ์†๋ฐ›๋Š” ๊ตฌ์กฐ ๋“ฑ ๋งŽ์€ ๋ถ€๋ถ„์—์„œ ๋ณ€ํ™”๊ฐ€ ์ƒ๊ฒผ์–ด์š”. ์ด์— ๊ธฐ์กด 2์ ๋Œ€ ๋ฒ„์ „์—์„œ๋Š” ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋˜ ๊ฒƒ๋“ค๋„ 3๋ฒ„์ „๋Œ€๋กœ ๊ฐ€๋ฉด์„œ ์ƒˆ๋กญ๊ฒŒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•ด์ฃผ์–ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์ด ์ƒ๊ฒผ์–ด์š”.

H2 Console ํ™œ์„ฑํ™”

spring:
  h2:
    console:
      enabled: true

๊ฐœ๋ฐœ ํ™˜๊ฒฝ

  • Spring Boot: 3.1.4
  • Spring Security: 6.1.4
  • Language: Java 17
  • Build Tool: Gradle 8.3

์—๋Ÿฌ ์ฝ”๋“œ

@EnableMethodSecurity
@EnableWebSecurity
@Configuration
public class SecurityConfig {
    private static final String[] WHITE_LIST = {
            "/helloWorld",
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .headers(header -> header.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
                .authorizeHttpRequests(request -> request
                        .requestMatchers(PathRequest.toH2Console()).permitAll()
                        .requestMatchers(WHITE_LIST).permitAll()
                        .anyRequest().authenticated())
                .build();
    }
}

์—๋Ÿฌ ๋กœ๊ทธ

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChains' parameter 0: Error creating bean with name 'securityFilterChain' defined in class path resource [com/demo/springdemo/global/security/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'securityFilterChain' threw exception with message: This method cannot decide whether these patterns are Spring MVC patterns or not. If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); otherwise, please use requestMatchers(AntPathRequestMatcher).

This is because there is more than one mappable servlet in your servlet context: {org.h2.server.web.JakartaWebServlet=[/h2-console/*], org.springframework.web.servlet.DispatcherServlet=[/]}.

For each MvcRequestMatcher, call MvcRequestMatcher#setServletPath to indicate the servlet path.
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:875) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:828) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:492) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1416) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.12.jar:6.0.12]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:942) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.4.jar:3.1.4]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) ~[spring-boot-3.1.4.jar:3.1.4]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.1.4.jar:3.1.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-3.1.4.jar:3.1.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1309) ~[spring-boot-3.1.4.jar:3.1.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1298) ~[spring-boot-3.1.4.jar:3.1.4]
	at com.demo.springdemo.SpringDemoApplication.main(SpringDemoApplication.java:10) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.1.4.jar:3.1.4]

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityFilterChain' defined in class path resource [com/demo/springdemo/global/security/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'securityFilterChain' threw exception with message: This method cannot decide whether these patterns are Spring MVC patterns or not. If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); otherwise, please use requestMatchers(AntPathRequestMatcher).

์›์ธ

org.h2.server.web.JakartaWebServlet๊ณผ org.springframework.web.servlet.DispatcherServlet ๋‘ ๊ฐœ์˜ Servlet์ด Servlet Context์— ๋“ฑ๋ก๋˜์–ด ์–ด๋–ค Servlet์„ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š”์ง€ ๋ช…์‹œ๋˜์–ด ์žˆ์ง€ ์•Š์•„ ๋ฐœ์ƒํ–ˆ์–ด์š”.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋กœ๊ทธ๋ฅผ ํ™•์ธ ํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‘˜ ์ค‘ ํ•˜๋‚˜์˜ RequestMatcher๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ช…์‹œํ•ด ๋‹ฌ๋ผ๊ณ  ์นœ์ ˆํ•˜๊ฒŒ ์•Œ๋ ค์ฃผ๊ณ  ์žˆ์–ด์š”.

This method cannot decide whether these patterns are Spring MVC patterns or not. If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); otherwise, please use requestMatchers(AntPathRequestMatcher).

Spring MVC ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด MvcRequestMatcher๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” AntPathRequestMatcher๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ์š”.

์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ๋œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ๋‹จ์ˆœํ•˜๊ฒŒ String ๋ฐฐ์—ด๋กœ RequestMatcher๋ฅผ ๋ช…์‹œํ•ด์ฃผ์ง€ ์•Š๊ณ  ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์š”.

์—๋Ÿฌ ๋ฐœ์ƒ ๋ถ€๋ถ„: .requestMatchers(WHITE_LIST).permitAll()

๋”ฐ๋ผ์„œ ๋‹จ์ˆœํ•˜๊ฒŒ String ๋ฐฐ์—ด์„ ์ธ์ž๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์šฐ๋ฆฌ๋Š” Spring MVC ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— MvcRequestMatcher๋ฅผ ์ธ์ž๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด ๋ผ์š”.

@EnableMethodSecurity
@EnableWebSecurity
@Configuration
public class SecurityConfig {
    private static final String[] WHITE_LIST = {
            "/helloWorld",
    };

    @Bean
    public MvcRequestMatcher.Builder mvcRequestMatcherBuilder(HandlerMappingIntrospector introspector) {
        return new MvcRequestMatcher.Builder(introspector);
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .headers(header -> header.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
                .authorizeHttpRequests(request -> request
                        .requestMatchers(PathRequest.toH2Console()).permitAll()
                        .requestMatchers(this.createMvcRequestMatcherForWhitelist(mvc)).permitAll()
                        .anyRequest().authenticated())
                .build();
    }

    private MvcRequestMatcher[] createMvcRequestMatcherForWhitelist(MvcRequestMatcher.Builder mvc) {
        return Stream.of(WHITE_LIST).map(mvc::pattern).toArray(MvcRequestMatcher[]::new);
    }
}
  1. HandlerMappingIntrospector ๋นˆ์„ ์ฃผ์ž… ๋ฐ›๊ณ , MvcRequestMatcher.Builder ์ธ์ž๋กœ ๋„˜๊ฒจ์ฃผ์–ด ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ด์š”.
  2. createMvcRequestMatcherForWhitelist ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด์„œ WHITE_LIST String[]์„ MvcRequestMathcer[]๋กœ ๋งคํ•‘ํ•ด์š”.
  3. requestMatchers์— String[]์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ createMvcRequestMatcherForWhitelist ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด MvcRequestMathcer[]์„ ์ „๋‹ฌํ•ด์„œ MvcRequestMatcher ์‚ฌ์šฉ์„ ๋ช…์‹œํ•˜์—ฌ ์ด๋ฒˆ ์ด์Šˆ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด์š”.
profile
๐ŸŽ๏ธ๐Ÿ’จ Beep Beep

0๊ฐœ์˜ ๋Œ“๊ธ€