ClassNotFoundException: org.springframework.security.oauth2.



我在使用Spring Security oauth2 vs Keycloak验证期间获得相关错误。我不认为它在我的依赖项中缺失。

误差

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChains' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'filterChain' defined in class path resource [com/framework/security/SecurityConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'filterChain' threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/security/oauth2/server/resource/web/BearerTokenResolver
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-5.3.23.jar:5.3.23]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.23.jar:5.3.23]
at 
Caused by: java.lang.ClassNotFoundException: org.springframework.security.oauth2.server.resource.web.BearerTokenResolver
at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[?:?]

My Security class

@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login/**").hasRole("role-name").anyRequest().authenticated().and().oauth2ResourceServer().jwt();
return http.build();
}
}

pom.xml

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
</dependencies>

application.yml

security:
oauth2:
resource-server:
jwt:
issuer-uri: http://keycloak-url/realms/realm-id
jwk-set-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/certs
client:
registration:
client-ui:
provider: keycloak
client-id: client-ui
client-secret: abcdefgh
authorization-grant-type: authorization_code
scope: openid
provider:
client-ui:
authorization-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/auth
token-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/token
user-info-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/userinfo
jwk-set-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/certs
user-name-attribute: preferred_username

我试着做了很多编辑,但我找不到问题。我怎样才能解决这个问题呢?

首先,你应该决定你的应用程序主要是一个OAuth2客户端还是一个OAuth2资源服务器。在这里,您将客户机和资源服务器配置混合在同一个安全过滤器链中。这行不通。我写了一篇"OAuth2 essentials"。部分作为我的教程的介绍,它应该可以帮助你决定。

Servlet资源服务器(REST API)

完整教程。

资源服务器不关心客户端关心的登录和OAuth2流。它只关心请求是否被授权(有一个有效的访问令牌),这个令牌是否应该被自省或解码,以及它是否应该根据令牌声明授予对所请求资源的访问权。

使用Postman或任何OAuth2客户端可以授权其请求并发送任何类型的请求(不仅仅是GET,还包括POST, PUT和DELETE)来尝试运行您的API。

<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
@SpringBootApplication
public class ResourceServerMinimalApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceServerMinimalApplication.class, args);
}
@Order(Ordered.HIGHEST_PRECEDENCE)
@Bean
SecurityFilterChain resourceServerSecurityFilterChain(
HttpSecurity http,
@Value("${resource-server.cors.allowed-origins:}#{T(java.util.Collections).emptyList()}") List<String> allowedOrigins)
throws Exception {
http.oauth2ResourceServer(oauth2 -> oauth2.jwt());
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.csrf(csrf -> csrf.disable());
http.exceptionHandling(handeling -> handeling.authenticationEntryPoint((request, response, authException) -> {
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm="Restricted Content"");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
}));
http.authorizeHttpRequests().anyRequest().authenticated();
http.cors(cors -> {
if (allowedOrigins.isEmpty()) {
cors.disable();
} else {
cors.configurationSource(corsConfig(allowedOrigins));
}
});
return http.build();
}
CorsConfigurationSource corsConfig(List<String> allowedOrigins) {
final var source = new UrlBasedCorsConfigurationSource();
final var configuration = new CorsConfiguration();
configuration.setAllowedOrigins(allowedOrigins);
configuration.setAllowedMethods(List.of("*"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of("*"));
source.registerCorsConfiguration("/**", configuration);
return source;
}
@RestController
@RequestMapping("/api/v1")
public static class GreetingController {
@GetMapping("/greet")
public ResponseEntity<GreetingDto> getGreeting(Authentication auth) {
return ResponseEntity.ok(new GreetingDto("Hello %s!".formatted(auth.getName())));
}
static record GreetingDto(String message) {
}
}
}
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://keycloak-url/realms/realm-id
resource-server:
cors:
allowed-origins:
- http://localhost:4200

Servlet客户端(服务thyymleaf模板)

完整教程。

<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
@SpringBootApplication
public class ClientMinimalApplication {
public static void main(String[] args) {
SpringApplication.run(ClientMinimalApplication.class, args);
}
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
@Bean
SecurityFilterChain clientSecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception {
http.oauth2Login();
final var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
http.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler));
http.cors().disable();
// sessions are required (enabled by default)
// CSRF protection is required (enabled by default) because security is based on sessions
http.authorizeHttpRequests().requestMatchers("/login/**", "/oauth2/**").permitAll() // this is required for unauthenticated users to login
.anyRequest().authenticated();
return http.build();
}
@Controller
@RequestMapping
public static class UiController {
@GetMapping("/")
public RedirectView getIndex() throws URISyntaxException {
return new RedirectView("/ui/greet");
}
@GetMapping("/ui/greet")
public String getGreeting(Authentication auth, Model model) throws URISyntaxException {
model.addAttribute("message", "Hello %s!".formatted(auth.getName()));
return "greet";
}
}
}
spring:
security:
oauth2:
client:
provider:
keycloak-realm-id:
issuer-uri: http://keycloak-url/realms/realm-id
registration:
keycloak-confidential-user:
authorization-grant-type: authorization_code
client-name: a local Keycloak instance
client-id: client-ui
client-secret: ${keycloak-secret}
provider: keycloak-realm-id
scope: openid,profile,email,offline_access

/src/main/resources/templates/greet.html:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Greetings!</title>
</head>
<body>
<h1 th:utext="${message}">..!..</h1>
</body>

合并客户端和资源服务器

如果你的应用既暴露了OAuth2客户端使用的REST API,又暴露了一些其他资源供浏览器查询(不需要像Angular、React或Vue这样的框架的OAuth2客户端库的帮助),并且只有在这种情况下,定义上述两个不同的过滤器链,并在第一个过滤器链上添加一个securityMatcher,以便将其限制为应该保护的路由。

在上面提供的示例中,顺序最低的安全过滤器链是资源服务器链。添加像http.securityMatcher("/api/**");这样的东西会做到这一点:所有其他路由将与客户端过滤链一起安全,这是在尝试之后,并将拦截所有尚未被资源服务器过滤链截获的请求。

完整教程。

可能,您在pom.xml中缺少oauth2-resource-server依赖项

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
<version>6.0.2</version>
</dependency>

实际上自动附加所需的DefaultBearerTokenResolverBean,并且应该开箱即用。

相关内容

最新更新