alibaba / druid

阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池
https://github.com/alibaba/druid/wiki
Apache License 2.0
27.9k stars 8.57k forks source link

springboot3.1.5 + druid-spring-boot-3-starter 启动异常 #5493

Closed zouzg7829 closed 9 months ago

zouzg7829 commented 10 months ago

使用的druid-spring-boot-3-starter版本号为1.2.20

appliction.yml配置如下:

server:  
  port: 8080  
  servlet:  
    context-path: /  
  tomcat:  
    uri-encoding: UTF-8  
    accept-count: 1000  
    threads:  
      max: 800  
      min-spare: 100
spring:  
  datasource:  
    driverClassName: com.mysql.cj.jdbc.Driver  
    url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8  
    username: root  
    password: root  
    druid:  
      statViewServlet:  
        enabled: true  
        url-pattern: /druid/*  
        # 控制台管理用户名和密码  
        login-username: ruoyi  
        login-password: 123456

项目采用了SpringSecurity安全配置

@Configuration
public class SecurityConfig
{
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .authorizeHttpRequests(authorizeRequests -> authorizeRequests
                        .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js").permitAll()
                        .requestMatchers("/druid/**").permitAll()
                        .anyRequest().authenticated())
                .build();
    }
}

启动不了,抛出如下异常:

2023-11-21T13:31:29.758+08:00  INFO 2680 --- [  restartedMain] com.free.AdminServerApplication          : Starting AdminServerApplication using Java 21 with PID 2680 (E:\Projects\admin\admin-server\all-in\target\classes started by Thinkpad in E:\Projects\admin\admin-server)
2023-11-21T13:31:29.762+08:00  INFO 2680 --- [  restartedMain] com.free.AdminServerApplication          : No active profile set, falling back to 1 default profile: "default"
2023-11-21T13:31:29.895+08:00  INFO 2680 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2023-11-21T13:31:29.895+08:00  INFO 2680 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2023-11-21T13:31:32.733+08:00  INFO 2680 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8081 (http)
2023-11-21T13:31:32.755+08:00  INFO 2680 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-11-21T13:31:32.755+08:00  INFO 2680 --- [  restartedMain] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.15]
2023-11-21T13:31:32.888+08:00  INFO 2680 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-11-21T13:31:32.889+08:00  INFO 2680 --- [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2993 ms
2023-11-21T13:31:33.400+08:00  WARN 2680 --- [  restartedMain] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: 031c5675-2dbb-4a30-a6cf-365ec05540e1

This generated password is for development use only. Your security configuration must be updated before running your application in production.

2023-11-21T13:31:33.546+08:00  WARN 2680 --- [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'filterChain' defined in class path resource [com/free/security/SecurityConfiguration.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'filterChain' 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.springframework.web.servlet.DispatcherServlet=[/], com.alibaba.druid.support.jakarta.StatViewServlet=[/druid/*]}.

For each MvcRequestMatcher, call MvcRequestMatcher#setServletPath to indicate the servlet path.
2023-11-21T13:31:33.551+08:00  INFO 2680 --- [  restartedMain] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2023-11-21T13:31:33.616+08:00  INFO 2680 --- [  restartedMain] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2023-11-21T13:31:33.669+08:00 ERROR 2680 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'filterChain' defined in class path resource [com/free/security/SecurityConfiguration.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'filterChain' 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.springframework.web.servlet.DispatcherServlet=[/], com.alibaba.druid.support.jakarta.StatViewServlet=[/druid/*]}.

For each MvcRequestMatcher, call MvcRequestMatcher#setServletPath to indicate the servlet path.
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:654) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:642) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1332) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:950) ~[spring-context-6.0.13.jar:6.0.13]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:616) ~[spring-context-6.0.13.jar:6.0.13]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.5.jar:3.1.5]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:738) ~[spring-boot-3.1.5.jar:3.1.5]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:440) ~[spring-boot-3.1.5.jar:3.1.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-3.1.5.jar:3.1.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.5.jar:3.1.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-3.1.5.jar:3.1.5]
    at com.free.AdminServerApplication.main(AdminServerApplication.java:11) ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.1.5.jar:3.1.5]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'filterChain' 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.springframework.web.servlet.DispatcherServlet=[/], com.alibaba.druid.support.jakarta.StatViewServlet=[/druid/*]}.

For each MvcRequestMatcher, call MvcRequestMatcher#setServletPath to indicate the servlet path.
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.0.13.jar:6.0.13]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:650) ~[spring-beans-6.0.13.jar:6.0.13]
    ... 22 common frames omitted
Caused by: java.lang.IllegalArgumentException: 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.springframework.web.servlet.DispatcherServlet=[/], com.alibaba.druid.support.jakarta.StatViewServlet=[/druid/*]}.

For each MvcRequestMatcher, call MvcRequestMatcher#setServletPath to indicate the servlet path.
    at org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.requestMatchers(AbstractRequestMatcherRegistry.java:208) ~[spring-security-config-6.1.5.jar:6.1.5]
    at org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.requestMatchers(AbstractRequestMatcherRegistry.java:276) ~[spring-security-config-6.1.5.jar:6.1.5]
    at com.free.security.SecurityConfiguration.lambda$filterChain$0(SecurityConfiguration.java:27) ~[classes/:na]
    at org.springframework.security.config.annotation.web.builders.HttpSecurity.authorizeHttpRequests(HttpSecurity.java:1466) ~[spring-security-config-6.1.5.jar:6.1.5]
    at com.free.security.SecurityConfiguration.filterChain(SecurityConfiguration.java:25) ~[classes/:na]
    at com.free.security.SecurityConfiguration$$SpringCGLIB$$0.CGLIB$filterChain$0(<generated>) ~[classes/:na]
    at com.free.security.SecurityConfiguration$$SpringCGLIB$$FastClass$$1.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.0.13.jar:6.0.13]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-6.0.13.jar:6.0.13]
    at com.free.security.SecurityConfiguration$$SpringCGLIB$$0.filterChain(<generated>) ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ~[spring-beans-6.0.13.jar:6.0.13]
    ... 23 common frames omitted

Process finished with exit code 0

异常信息中,重点如下:

This is because there is more than one mappable servlet in your servlet context: {org.springframework.web.servlet.DispatcherServlet=[/], com.alibaba.druid.support.jakarta.StatViewServlet=[/druid/*]}.

但是如果去掉appliction.yml文件中的statViewServlet配置,则可以启动, 也即application.yml:

```yml 
server:  
  port: 8080  
  servlet:  
    context-path: /  
  tomcat:  
    uri-encoding: UTF-8  
    accept-count: 1000  
    threads:  
      max: 800  
      min-spare: 100
spring:  
  datasource:  
    driverClassName: com.mysql.cj.jdbc.Driver  
    url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8  
    username: root  
    password: root  
    druid:  
#      statViewServlet:
#        enabled: true
#        url-pattern: /druid/*
#        # 控制台管理用户名和密码
#        login-username: ruoyi
#        login-password: 123456
hqy309 commented 10 months ago

+1

zrlw commented 10 months ago

搜一下CVE-2023-22602漏洞,使用SpringSecurity新版本需要改写代码的,简单一点是改成这样:

        return httpSecurity
                 .authorizeHttpRequests(authorizeRequests -> authorizeRequests
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/*.html")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/**.html")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/**.css")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/**.js")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher("/druid/**")).permitAll()
                         .anyRequest().authenticated())
                 .build();

更严谨的做法是改成MvcRequestMatcher

zouzg7829 commented 10 months ago

搜一下CVE-2023-22602漏洞,使用SpringSecurity新版本需要改写代码的,简单一点是改成这样:

        return httpSecurity
                 .authorizeHttpRequests(authorizeRequests -> authorizeRequests
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/*.html")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/**.html")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/**.css")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/**.js")).permitAll()
                         .requestMatchers(AntPathRequestMatcher.antMatcher("/druid/**")).permitAll()
                         .anyRequest().authenticated())
                 .build();

更严谨的做法是改成MvcRequestMatcher

按照你写的代码修改,运行Application,同样抛出之前的错误, 异常信息中,重点如下: This is because there is more than one mappable servlet in your servlet context: {org.springframework.web.servlet.DispatcherServlet=[/], com.alibaba.druid.support.jakarta.StatViewServlet=[/druid/*]}.

zrlw commented 10 months ago

我的能正常启动,我用的springboot3.1.5+ spring security config6.1.5 + spring security web6.1.5,改写之前应用启动时和你一样的提示,按照提示修改了就启动成功了

zrlw commented 10 months ago

这个问题和druid没什么关系,你还是去spring security社区找找吧,看看升级之后代码需要怎么改写。

zrlw commented 10 months ago

我用eclipse + jdk17对当前master分支代码重新做了一下验证,在druid-spring-boot-3-starter模块的测试package里加上你的这个SecurityConfig,确认不改写就抛异常,改写了就ok。 当然现在的master分支的core模块在jdk17上编译有错误,还要改pom和代码,主要需要修改的内容如下:

  1. core模块pom干掉hive-jdbc依赖,这个东西依赖tools.jar,jdk17下没有了,扫了代码和测试类都没有用到它,应该只是个残留垃圾。
  2. 替换@Resource注解,jdk17没有这个注解,需要替换成@Autowired@Qualifier,改了StatAnnotationBeanPostProcessor.java。
  3. 在druid-spring-boot-3-starter测试package填加SecurityConfig,修改pom添加SecurityConfig需要的spring-security:

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <scope>test</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <scope>test</scope>
        </dependency>
  4. 父pom的springboo3版本改成3.1.5,以验证你碰到的问题。
    <springboot3.version>3.1.5</springboot3.version>
  5. 确认eclipse配置Content Types - Text里的java Properties File缺省编码配的是UTF-8,这个配置坑了我一下,eclipse这个选项默认是ISO-8859-1,内容有汉字的properties文件并不会被加载。为了避免出现此类问题,大多数开源软件要求默认的配置文件必须使用英文注释,但是druid配置文件一眼看去全是汉字,可能druid的定位只是面向国内用户吧。 因为druid的pom加了checkstyle校验,但有些测试类并不符合要求,我懒得改了,用jdk17执行clean package -B重新打包全部模块时skip排除掉了单元测试,所有模块全部打包成功后再单独mvn test构建druid-spring-boot-3-starter的测试类,以便测试DemoApplication。 测试jre我直接用了eclipse自带的jdk17,debug确认应用先执行了DruidStatViewServletConfiguration,证明application.properties的statViewServlet启用配置生效了,然后执行到了SecurityConfig,如果用你写的方式,就会抛异常: image 停掉应用,把SecurityConfig改写成下面这个样子后,重新debug确认SecurityConfig执行成功,应用也随之启动成功: image
zouzg7829 commented 9 months ago

to @zrlw : 按照你的步骤,能显示druid的登陆页面,但是输入正确的用户名和密码登陆不上去,Http Response返回403. image application.yml文件中也删除了所有的中文字符,采用UTF-8格式。

zrlw commented 9 months ago

配置spring-security默认是开启csrf,403是被CsrfFilter阻断了,druid是restful,post请求都不带csrfToken,目前druid并没有提供这些功能,用security意味着你自己要定制。 如果你不担心跨站攻击,可以直接禁用csrf了事,否则你就要写很多代码了,如果不熟悉相关内容,应该还有很多问号或者惊叹号在前面等着你。

zouzg7829 commented 9 months ago

配置spring-security默认是开启csrf,403是被CsrfFilter阻断了,druid是restful,post请求都不带csrfToken,目前druid并没有提供这些功能,用security意味着你自己要定制。 如果你不担心跨站攻击,可以直接禁用csrf了事,否则你就要写很多代码了,如果不熟悉相关内容,应该还有很多问号或者惊叹号在前面等着你。

Thanks!

zhyfy commented 9 months ago

AbstractRequestMatcherRegistry.java:208行的原因,idea中下载源码,debug一下就知道了,所有pattern都要显示声明为antPattern或者mvcPattern

Waylon-Firework commented 9 months ago

配置spring-security默认是开启csrf,403是被CsrfFilter阻断了,druid是restful,post请求都不带csrfToken,目前druid并没有提供这些功能,用security意味着你自己要定制。 如果你不担心跨站攻击,可以直接禁用csrf了事,否则你就要写很多代码了,如果不熟悉相关内容,应该还有很多问号或者惊叹号在前面等着你。

403加这个就行了 .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.ignoringRequestMatchers( antMatchers("/druid/**") ))

Waylon-Firework commented 9 months ago

to @zrlw : 按照你的步骤,能显示druid的登陆页面,但是输入正确的用户名和密码登陆不上去,Http Response返回403. image application.yml文件中也删除了所有的中文字符,采用UTF-8格式。

403加这个就行了 .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.ignoringRequestMatchers( antMatchers("/druid/**") ))