Spring安全方法无法确定模式是MVC还是不是Spring Boot应用程序异常

Spring security method cannot decide pattern is MVC or not Spring Boot application exception

提问人:Ronit Pandey 提问时间:8/1/2023 最后编辑:Mark RotteveelRonit Pandey 更新时间:10/20/2023 访问量:17911

问:

当我尝试运行应用程序时,它无法启动并引发此异常。

此方法无法确定这些模式是否为 Spring MVC 模式与否。如果此端点是 Spring MVC 端点,请使用 requestMatchers(MvcRequestMatcher);否则,请使用 requestMatchers(AntPathRequestMatcher)。

我是 Spring Security 的新手。请帮我解决这个错误。

这是我的 spring 安全配置类

package com.ronit.SpringSecurityTutorial.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
public class SecurityConfiguration {

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

    @Bean
    AuthenticationManager authManager(UserDetailsService detailsService) {
        DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
        daoProvider.setUserDetailsService(detailsService);
        return new ProviderManager(daoProvider);
    }

    @SuppressWarnings("removal")
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.csrf(csrf -> csrf.disable()).authorizeHttpRequests(auth -> {
            auth.anyRequest().authenticated();
            auth.requestMatchers("/auth/**").permitAll();
            auth.anyRequest().authenticated();
        }).httpBasic().and().build();
    }
}

这是 spring boot 应用程序

package com.ronit.SpringSecurityTutorial;

import java.util.HashSet;
import java.util.Set;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.ronit.SpringSecurityTutorial.models.ApplicationUser;
import com.ronit.SpringSecurityTutorial.models.Role;
import com.ronit.SpringSecurityTutorial.repository.RoleRepository;
import com.ronit.SpringSecurityTutorial.repository.UserRepository;

@SpringBootApplication
public class SpringSecurityTutorialApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityTutorialApplication.class,args);
    }

    @Bean
    CommandLineRunner run(RoleRepository roleRepository, UserRepository userRepository,
            PasswordEncoder passwordEncoder) {
        return args -> {
            if (roleRepository.findByAuthority("ADMIN").isPresent())
                return;
            Role adminRole = roleRepository.save(new Role("ADMIN"));
            roleRepository.save(new Role("USER"));

            Set<Role> roles = new HashSet<>();
            roles.add(adminRole);
            ApplicationUser admin = new 
            ApplicationUser(1, "Admin", passwordEncoder.encode("Password"), roles);
            userRepository.save(admin);
        };
    }
}

这些是 pom.xml 中的依赖项

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.properties 文件中没有与安全性相关的配置。

我在 Google 上搜索了一下,发现了一些 Stack Overflow 页面,但没有一个页面使用与我的配置类似的配置。

我看过一个在线教程并制作了这个。我正确地执行了每个步骤,但我的配置不起作用。

应用程序将立即停止并终止。

我在我的应用程序中使用 Spring Boot 3 和 Spring Security 6。

java spring-boot eclipse spring-security 后端

评论

0赞 Ronit Pandey 8/1/2023
我正在使用 youtube 教程来学习 spring 安全性。如果有人能告诉我其他资源以保持最新版本的 spring security 更新,那将不胜感激,感谢您阅读我的问题
0赞 Ronit Pandey 8/1/2023
我无法理解什么是 mvc 和非 mvc 模式,如果有人能向我解释,那将是一笔很大的交易
0赞 Ronit Pandey 8/1/2023
感谢您编辑詹姆斯,如果您能提供一些帮助会更好
2赞 Toerktumlare 8/1/2023
这解决了您的问题 spring.io/security/cve-2023-34035
0赞 Raster R 8/3/2023
@Toerktumlare是正确的,但答案可以更详细地阐述

答:

10赞 Raghu 8/3/2023 #1

目前,https://spring.io/security/cve-2023-34035 推荐了两个选项

第一个选项:
在 tomcat 中做了一个非常简单的 poc war Web 应用程序。 调试了此方法 -

org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.requestMatchers(HttpMethod method, String...模式)

学习 https://spring.io/security/cve-2023-34035

就在上述方法的以下行之前,我检查了注册对象中的内容。

Assert.isTrue(registrations.size() == 1, “这个方法不能确定这些模式是否是Spring MVC模式。如果此端点是 Spring MVC 端点,请使用 requestMatchers(MvcRequestMatcher);否则,请使用 requestMatchers(AntPathRequestMatcher).“);

发现它是注册的

org.apache.catalina.servlets.DefaultServlet against "default"
org.apache.jasper.servlet.JspServlet against "jsp"
org.springframework.web.servlet.DispatcherServlet against "dispatcher"

https://spring.io/security/cve-2023-34035 中,他们提到 “有时不需要这些额外的 servlet。例如,某些 Servlet 容器将添加一个 DefaultServlet,DispatcherServlet 可以有效地替换该 DefaultServlet。在许多情况下,可以从容器的全局配置中删除这样的 servlet”。

他们的意思是我们应该编辑apache-tomcat-10.1.10\conf\web.xml。

我们应该注释掉 org.apache.catalina.servlets.DefaultServlet 的 web.xml 中的注册, org.apache.jasper.servlet.JspServlet。 我们还应该删除同一 web.xml 中的映射条目

对于其他 servlet 容器,我们可以考虑类似的概念。

第二种选择:
另一种选择是按照他们在“有关缓解示例,请参阅以下 Spring Security 示例应用程序和相应的差异”中的建议执行操作。https://spring.io/security/cve-2023-34035 即遵循 https://github.com/spring-projects/spring-security-samples/commit/4e3bec904a5467db28ea33e25ac9d90524b53d66

换句话说,他们给出的例子是 取代:

    .requestMatchers("/login", "/resources/**").permitAll()   

    .requestMatchers(mvc.pattern("/login"),
         mvc.pattern("/resources/**")).permitAll()  

在 Web 应用程序中,您通常使用 mvc 匹配器,如下所示。 您也可以使用蚂蚁匹配器 - 使用蚂蚁匹配器有一点风险,如此处所述 - antMatcher 和 mvcMatcher 之间的区别最好避免使用蚂蚁匹配器,除非您愿意在开始使用它时进行必要的测试。在复杂的情况下也可以使用正则表达式 - 仅在需要时使用它(而不是在这种情况下,您只想解决当前问题)。

关于如何实现 mvc.pattern() 本身,以下是步骤。在某处声明一个 bean,如下所示。

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

在您的安全配置中,只需像这样自动连接它:

@Autowired
    MvcRequestMatcher.Builder mvc;

决策点:

如果要保留原始代码,请尝试编辑服务器(不是项目的)web.xml或类似配置(如果不是tomcat),而是其他一些servlet容器,以消除servlet容器中默认提供的额外servlet。
如果您愿意更改代码,请尝试第二个选项。

另请注意:

有时,如果它是遗留代码,并且您在代码中使用了 DispatcherServlet 方法,则必须消除它才能使用第一个选项,否则就选择第二个选项。

希望这会有所帮助

添加更多新信息

事实上,如果进一步从 spring-boot-starter-parent:3.1.2 迁移到 spring-boot-starter-parent:3.1.3,消息会更加清晰。 它说了问题: Servlet 上下文中有多个可映射的 Servlet:{org.apache.jasper.servlet.JspServlet=[*.jspx, *.jsp], org.springframework.web.servlet.DispatcherServlet=[/]}。

这就是为什么遵循 https://spring.io/security/cve-2023-34035 一个选项是禁用默认和 jsp 相关的 servlet。如果您不想更改代码,但可以更改服务器配置,则很有用。

第二种选择是,如果你走代码更改路线,总是被清楚地记录下来。 “对于每个 MvcRequestMatcher,调用 MvcRequestMatcher#setServletPath 以指示 servlet 路径。”只是作为“否则”,他们提到了 AntMatchers

如果您选择第二种选择,我将再次避免使用 AntMatcher,因为在使用 Web 应用程序进行此迁移时会存在危险。

评论

0赞 Christoph S 8/9/2023
但是我该如何将它与 Vaadin 一起使用呢?我只有 WebSecurity
0赞 Raghu 8/9/2023
@ChristophS很久没有使用Vaadin了。你是说你最近在Vaadin遇到了同样的问题吗?这是最近的 Spring Boot 最新问题。你在哪里用 Vaadin 使用 spring?
0赞 Christoph S 8/9/2023
是的,我正在将 Vaadin 与 Spring Boot 一起使用,升级到 spring boot 3.1.2 并在覆盖的 webSecurity 中遇到了这个问题,但无法使用 web.ignoring().requestMatchers(“/actuator/health/**”) 更改为 mvc matcher vaadin.com/docs/latest/security/enabling-security;
0赞 Raghu 8/15/2023
@ChristophS .很抱歉耽搁了。你能在 git 存储库上分享一些示例代码吗?我相信答案就在我分享的步骤中。也就是说,如果你把你的代码作为示例分享,我可以看一下,并尝试给你更具体的建议。
1赞 Raghu 8/18/2023
@ChristophS 还有一件事。请彻底测试安全表达式和结果,因为您选择了第二个选项。我相信在此问题之前,这已经解决了 mvcmatcher。过去,已知蚂蚁 mactchers 和 mvcmatchers 的工作方式存在细微差异。如果您选择了代码更改路由并使用 AntMatcher,请测试实际的安全性,尤其是关于实际 url 是否有尾随 / 的安全性。
2赞 Arshad Ali 8/5/2023 #2

有一个小的配置,这将阻止我们:

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).

错误。 在配置迁移中明确指出了这一点

如果您在使用新方法时遇到问题,可以随时切换回您正在使用的实现。例如,如果您仍然想使用 和 实现,则可以使用接受实例的方法:requestMatchersRequestMatcherAntPathRequestMatcherRegexRequestMatcherrequestMatchersRequestMatcher

好!在上面代码中提到的问题的上下文中,您应该尝试将参数添加到,如下所示:antMatcher(pattern)requestMatchers()

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.csrf(csrf -> csrf.disable())
           .authorizeHttpRequests(auth -> {
            auth.anyRequest().authenticated();
            auth.requestMatchers(antMatcher("/auth/**")).permitAll();
            auth.anyRequest().authenticated();
        }).httpBasic().and().build();
    }

希望它能解决您的问题。

25赞 Iman Hosseinzadeh 8/17/2023 #3

由于漏洞 CVE-2023-34035 而发生迁移。

如果出现如下错误:

此方法无法确定这些模式是否为 Spring MVC 模式与否。如果此端点是 Spring MVC 端点,请使用 ;否则,请使用 .requestMatchers(MvcRequestMatcher)requestMatchers(AntPathRequestMatcher)

您应该使用完整的 RequestMatcher

例如,如果应用程序将 Servlet 部署到 /my-servlet/*,并且正在授权该流量,如下所示:

@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers("/my-servlet/**").hasRole("USER")
            .requestMatchers("/spring-mvc-controller/**").hasRole("USER")
            .anyRequest().authenticated()
        )
        // ...
    return http.build();
}

然后,应用程序应改为执行以下操作:

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

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

@Bean
SecurityFilterChain appSecurity(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers(antMatcher("/my-servlet/*")).hasRole("USER")
            .requestMatchers(mvc.pattern("/spring-mvc-controller/**")).hasRole("USER")
            .anyRequest().authenticated()
        )
        // ...
    return http.build();
}

有关更多详细信息,请阅读 Spring 提供的此存储库:cve-2023-34035-mitigations

评论

0赞 Frischling 11/18/2023
优秀的帖子 - 我仍然不明白 docu 在这方面为什么没有更新。
1赞 Sanjit Shrestha 9/11/2023 #4

上述问题已通过以下方法解决:

String[] unSecuredPaths = new String[]{
            "/validate",
            "/"}

    private AntPathRequestMatcher[] getAntPathRequestMatchers() {
            AntPathRequestMatcher[] requestMatchers = new AntPathRequestMatcher[unSecuredPaths.length];
            for (int i = 0; i < unSecuredPaths.length; i++) {
                requestMatchers[i] = new AntPathRequestMatcher(unSecuredPaths[i]);
            }
            return requestMatchers;
        }
    
@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        contentSecurityConfig(http);

        AntPathRequestMatcher[] requestMatchers = getAntPathRequestMatchers();
        http.authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(**requestMatchers**).permitAll()
                        .anyRequest().authenticated()).httpBasic(withDefaults());
        return http.build();
    }

对于 MvcRequestMatcher,将 AntPathRequestMatcher 替换为 MvcRequestMatcher,确切的解决方案应该有效。

0赞 Krishnakumar R 10/5/2023 #5

我在尝试设置 OAuth2 资源服务器时遇到了类似的问题。我使用 h2 作为我的 REST 数据存储。所以它在抱怨

Servlet 上下文中有多个可映射的 Servlet: {org.h2.server.web.JakartaWebServlet=[/h2-console/*], org.springframework.web.servlet.DispatcherServlet=[/]}。

以下是我用来解决它的配置。忽略代码中的 OAuth2 部分。希望这会有所帮助。

@Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorizeRequests ->
                        authorizeRequests
                                .requestMatchers(antMatcher(("/h2-console/**")))
                                .permitAll()

                                .requestMatchers(antMatcher("/api/posts"), antMatcher(HttpMethod.POST))
                                .hasAuthority("SCOPE_createBlogPost")

                                .requestMatchers(antMatcher("/api/posts"), antMatcher(HttpMethod.DELETE))
                                .hasAuthority("SCOPE_deleteBlogPost")

                                .anyRequest()
                                .authenticated())
                .headers(headersConfigurer -> headersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.ignoringRequestMatchers(antMatcher("/h2-console/**")))
                .oauth2ResourceServer(oAuth2ResourceServerConfigurer ->
                        oAuth2ResourceServerConfigurer.jwt(withDefaults()));

        return http.build();
    }
0赞 sakthi 10/18/2023 #6

此方法无法确定这些模式是否是 Spring MVC 模式。

如果此端点是 Spring MVC 端点,请使用 ;否则,请使用 .requestMatchers(MvcRequestMatcher)requestMatchers(AntPathRequestMatcher)

我的用法如下:

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

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
            .csrf(csrf -> csrf.ignoringRequestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**")))
            .authorizeHttpRequests(requests -> requests.requestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**")).permitAll())
            .authorizeHttpRequests(requests -> requests.anyRequest().authenticated())
            .headers(headers -> headers.frameOptions().sameOrigin())
            .formLogin(withDefaults())
            .build();
}
0赞 Tạ Anh Tú 10/19/2023 #7

在将 spring security 与 h2 数据库一起使用时,我遇到了同样的错误。我试图禁用h2-console,或切换到MySQL,错误消失了。

所以我认为这个错误与 h2 数据库有关,目前我对这个 h2 数据库没有任何解决方案。我改用MySQL

1赞 Peter 10/20/2023 #8

我能够通过使用匹配器包装字符串模式来解决歧义。

以前:

http
    .authorizeHttpRequests()
        .requestMatchers("/dashboard/run/**").authenticated()
        .requestMatchers("/editor/**").authenticated()
        .requestMatchers("/**").permitAll()

后:

http
    .authorizeHttpRequests()
        .requestMatchers(AntPathRequestMatcher.antMatcher("/dashboard/run/**")).authenticated()
        .requestMatchers(AntPathRequestMatcher.antMatcher("/editor/**")).authenticated()
        .requestMatchers(AntPathRequestMatcher.antMatcher("/**")).permitAll()