提问人:Ronit Pandey 提问时间:8/1/2023 最后编辑:Mark RotteveelRonit Pandey 更新时间:10/20/2023 访问量:17911
Spring安全方法无法确定模式是MVC还是不是Spring Boot应用程序异常
Spring security method cannot decide pattern is MVC or not Spring Boot application exception
问:
当我尝试运行应用程序时,它无法启动并引发此异常。
此方法无法确定这些模式是否为 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。
答:
目前,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 应用程序进行此迁移时会存在危险。
评论
有一个小的配置,这将阻止我们:
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).
错误。 在配置迁移中明确指出了这一点
如果您在使用新方法时遇到问题,可以随时切换回您正在使用的实现。例如,如果您仍然想使用 和 实现,则可以使用接受实例的方法:
requestMatchers
RequestMatcher
AntPathRequestMatcher
RegexRequestMatcher
requestMatchers
RequestMatcher
好!在上面代码中提到的问题的上下文中,您应该尝试将参数添加到,如下所示: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();
}
希望它能解决您的问题。
由于漏洞 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
评论
上述问题已通过以下方法解决:
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,确切的解决方案应该有效。
我在尝试设置 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();
}
此方法无法确定这些模式是否是 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();
}
在将 spring security 与 h2 数据库一起使用时,我遇到了同样的错误。我试图禁用h2-console,或切换到MySQL,错误消失了。
所以我认为这个错误与 h2 数据库有关,目前我对这个 h2 数据库没有任何解决方案。我改用MySQL
我能够通过使用匹配器包装字符串模式来解决歧义。
以前:
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()
评论