升级Spring Boot 3遇BeanCreationException:javax.servlet.ServletException缺失
问题描述
我有一个运行完全正常的极简OAuth2 JWT Spring Boot 2.7.3项目,依赖如下:
<dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> </dependency>
项目包含三个核心配置类:
WebSecurityConfig
@Configuration @EnableWebSecurity public class WebSecurityConfig { @Value("${jwt.secret}") private String jwtSecret; @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter(); tokenConverter.setSigningKey(jwtSecret); return tokenConverter; } @Bean public JwtTokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } }
AuthorizationServerConfig
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Value("${security.oauth2.client.client-id}") private String clientId; @Value("${security.oauth2.client.client-secret}") private String clientSecret; @Value("${jwt.duration}") private Integer jwtDuration; @Autowired private BCryptPasswordEncoder passwordEncoder; @Autowired private JwtAccessTokenConverter accessTokenConverter; @Autowired private JwtTokenStore tokenStore; @Autowired private AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient(clientId) .secret(passwordEncoder.encode(clientSecret)) .scopes("read", "write") .authorizedGrantTypes("password") .accessTokenValiditySeconds(jwtDuration); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .tokenStore(tokenStore) .accessTokenConverter(accessTokenConverter); } }
ResourceServerConfig
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Value("${cors.origins}") private String corsOrigins; @Autowired private Environment env; @Autowired private JwtTokenStore tokenStore; private static final String[] PUBLIC = { "/oauth/token", "/h2-console/**" }; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenStore(tokenStore); } @Override public void configure(HttpSecurity http) throws Exception { // H2 if (Arrays.asList(env.getActiveProfiles()).contains("test")) { http.headers().frameOptions().disable(); } http.authorizeRequests() .antMatchers(PUBLIC).permitAll() .anyRequest().authenticated(); http.cors().configurationSource(corsConfigurationSource()); } @Bean public CorsConfigurationSource corsConfigurationSource() { String[] origins = corsOrigins.split(","); CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.setAllowedOriginPatterns(Arrays.asList(origins)); corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH")); corsConfig.setAllowCredentials(true); corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfig); return source; } @Bean public FilterRegistrationBean<CorsFilter> corsFilter() { FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(corsConfigurationSource())); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; } }
升级到Spring Boot 3.0.0-SNAPSHOT后,项目抛出BeanCreationException:创建名称为'authorizationEndpoint'的Bean失败,堆栈跟踪显示异常根源为ClassNotFoundException: javax.servlet.ServletException。
解决方法
1. 核心原因
Spring Boot 3基于Jakarta EE 9,所有javax.*包已替换为jakarta.*;同时旧的Spring Security OAuth2授权服务器(@EnableAuthorizationServer)已被官方弃用,不再兼容Spring Boot 3。
2. 依赖调整
移除旧的OAuth2自动配置依赖,添加Spring官方推荐的Spring Authorization Server和Spring Security OAuth2资源服务器依赖:
<!-- 移除旧依赖 --> <!-- <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> </dependency> --> <!-- 添加新依赖 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>1.1.0</version> <!-- 匹配Spring Boot 3版本 --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency>
3. 重构配置类
(1)WebSecurityConfig 重构
移除旧的JWT相关Bean,调整为Spring Security 6的配置方式:
@Configuration @EnableWebSecurity public class WebSecurityConfig { @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .requestMatchers("/h2-console/**").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()) .csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**")) .headers(headers -> headers.frameOptions(frame -> frame.disable())); // 兼容H2控制台 return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } }
(2)替换AuthorizationServerConfig
创建新的授权服务器配置类,使用Spring Authorization Server:
@Configuration public class AuthorizationServerConfig { @Value("${security.oauth2.client.client-id}") private String clientId; @Value("${security.oauth2.client.client-secret}") private String clientSecret; @Value("${jwt.duration}") private Integer jwtDuration; @Autowired private BCryptPasswordEncoder passwordEncoder; @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId(clientId) .clientSecret(passwordEncoder.encode(clientSecret)) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.PASSWORD) .scope("read") .scope("write") .tokenSettings(tokenSettings()) .build(); return new InMemoryRegisteredClientRepository(registeredClient); } @Bean public TokenSettings tokenSettings() { return TokenSettings.builder() .accessTokenTimeToLive(Duration.ofSeconds(jwtDuration)) .build(); } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().build(); } // 配置JWT签名 @Bean public JWKSource<SecurityContext> jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; } }
(3)ResourceServerConfig 重构
移除@EnableResourceServer,改用Spring Security 6的资源服务器配置:
@Configuration public class ResourceServerConfig { @Value("${cors.origins}") private String corsOrigins; @Autowired private Environment env; @Bean public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { // 兼容H2控制台 if (Arrays.asList(env.getActiveProfiles()).contains("test")) { http.headers(headers -> headers.frameOptions(frame -> frame.disable())); } http.authorizeHttpRequests(auth -> auth .requestMatchers("/oauth2/token").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .cors(cors -> cors.configurationSource(corsConfigurationSource())); return http.build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { String[] origins = corsOrigins.split(","); CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.setAllowedOriginPatterns(Arrays.asList(origins)); corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH")); corsConfig.setAllowCredentials(true); corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfig); return source; } }
4. 配置文件调整
确保配置文件中的属性适配新的依赖,示例如下:
# JWT过期时间 jwt.duration=86400 # OAuth2客户端配置 security.oauth2.client.client-id=myclient security.oauth2.client.client-secret=mysecret # CORS允许来源 cors.origins=http://localhost:3000,http://localhost:8080
内容的提问来源于stack exchange,提问作者Nelio Alves
相关产品推荐
相关产品推荐

