SpringBoot2.6 + SpringSecurity + Swagger2升级报错处理
SpringBoot2.6 + SpringSecurity + Swagger2升级报错处理
问题描述
这两天把项目用到的SpringBoot和Swagger2都做了版本升级,SpringBoot升级到2.7.3,Swagger2升级到了3.0。
maven如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
这里主要是将原来swagger的依赖由springfox-swagger2 和 springfox-swagger-ui 替换为 springfox-boot-starter
在重新编译后有以下报错:
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.22.jar:5.3.22]
at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.3.jar:2.7.3]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-2.7.3.jar:2.7.3]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.3.jar:2.7.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-2.7.3.jar:2.7.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.7.3.jar:2.7.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-2.7.3.jar:2.7.3]
at com.dmt.systemManagement.SystemManagementApplication.main(SystemManagementApplication.java:13) ~[classes/:na]
据: 博主@Fisher3652所说,是因为:
在SpringBoot2.6之后,Spring MVC 处理程序映射匹配请求路径的默认策略已从 AntPathMatcher 更改为PathPatternParser。如果需要切换为AntPathMatcher,官方给出的方法是配置spring.mvc.pathmatch.matching-strategy=ant_path_matcher
但是本人按照此博主的配置依然没有解决问题。
解决方法
在经过一系列折腾后,最终解决,方法如下:
1、swagger配置文件
在常规的Docket配置之外,需要注入BeanPostProcessor的bean。
完整config代码如下:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author Ayuski
* @Description:
* @Create: 2022/9/15 5:17 PM
*/
@Configuration
@EnableOpenApi
@EnableWebMvc
public class SwaggerConfig {
@Bean
public Docket productApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.enable(true)
.groupName("组名")
.select()
.apis(RequestHandlerSelectors.basePackage("com.XXX"))
//.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) //添加ApiOperiation注解的被扫描
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("titel").description("xxxx文档")
.contact(new Contact("ayuski", "http://www.yourwebsite.com", "yourmail@mail.com"))
.version("1.0").build();
}
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}
}
注意几点:
1:@EnableSwagger2 变为 @EnableOpenApi
2:加上 @EnableWebMvc 注解
3:DocumentationType.SWAGGER_2 改为 DocumentationType.OAS_30
问题迎刃而解!!!
2、SpringSecurity放行
因项目引入了SpringSecurity,还需要对swagger的路径放行。
完整代码如下:
package com.dmt.common.config;
import com.dmt.common.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @Author Ayuski
* @Description:
* @Create: 2022/9/23 11:05 AM
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
private static final String[] AUTH_WHITELIST = {
// -- swagger ui
"/swagger-resources/**",
"/swagger-ui.html",
"/swagger-ui/",
"/v3/api-docs",
"/swagger-ui/**"
"/webjars/**",
"/images/**",
"/js/**",
"/webjars/**",
};
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
//对于登录接口允许匿名访问
.antMatchers("/login").anonymous()
//除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated().and().authenticationManager(getAuthenticationManager(http));
//添加过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
http.cors();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager getAuthenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
return authenticationManager;
}
}
主要是把
"/swagger-resources/**",
"/swagger-ui.html",
"/swagger-ui/",
"/v3/api-docs",
这几个路径放行。
如果有用swagger 2.* 版本的小伙伴,需要把 /v3/api-docs 变为 /v2/api-docs
当然如果以上不放行或者部分放行的话,会出现Unable to render this definition错误
配置完成后,浏览器输入 http://localhost:8080/swagger-ui/index.html 进入页面