oauth2客户端登录报错BeanCurrentlyInCreationException
1 问题描述
源码如下:
package com.ian.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.web.client.RestTemplate;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* @author Witt
* @version 1.0.0
* @date 2022/5/16
*/
@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
// .oauth2Login(withDefaults());
.oauth2Login(oauth2Login -> oauth2Login
.authorizationEndpoint(withDefaults())
.redirectionEndpoint(withDefaults())
.tokenEndpoint(withDefaults())
.userInfoEndpoint(withDefaults()));
}
/*@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}*/
@Bean
// @Lazy
public ClientRegistrationRepository clientRegistrationRepository() {
// 该构造器中可以放置多个 ClientRegistration
return new InMemoryClientRegistrationRepository(giteeClientRegistration());
}
private ClientRegistration githubClientRegistration() {
return ClientRegistration.withRegistrationId("github")
.clientId("810cb8e57c48403ba804")
.clientSecret("b4c405151bf0dea5c8be46548fec730dcb339172")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) // default value
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // default value
// .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") // default value
// .scope("openid", "profile", "email", "address", "phone")
.scope("read:user") // default value
.authorizationUri("https://github.com/login/oauth/authorize") // default value
.tokenUri("https://github.com/login/oauth/access_token") // default value
.userInfoUri("https://api.github.com/user") // default value
// .userNameAttributeName(IdTokenClaimNames.SUB)
.userNameAttributeName("id") // default value
// .jwkSetUri("")
.clientName("Github") // default value
.build();
}
private ClientRegistration giteeClientRegistration() {
return ClientRegistration.withRegistrationId("gitee")
.clientId("de91bdadc6b6b3599bebfa8755850fca0844aae44f13cc5e076110e961d4b59e")
.clientSecret("6baa392358c93949b4f5e9e443dfa646746eb40c67ffa3eb87decb408d500b77")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) // default value
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // default value
// .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
// .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") // default value
.redirectUri("http://localhost:8050/authorization_code") // 自定义方法
// .scope("openid", "profile", "email", "address", "phone")
// .scope("user") // default value,但是报错:存在错误,请求范围无效、未知或格式不正确
.authorizationUri("https://gitee.com/oauth/authorize") // default value
.tokenUri("https://gitee.com/oauth/token") // default value
.userInfoUri("https://gitee.com/api/v5/user") // default value
// .userNameAttributeName(IdTokenClaimNames.SUB)
.userNameAttributeName("id") // default value
// .jwkSetUri("")
.clientName("Gitee") // default value
.build();
}
}
报错内容:
2022-05-16 10:56:44.869 WARN 13271 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'OAuth2LoginSecurityConfig': Unsatisfied dependency expressed through method 'setContentNegotationStrategy' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration': Unsatisfied dependency expressed through method 'setClientRegistrationRepository' parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'OAuth2LoginSecurityConfig': Requested bean is currently in creation: Is there an unresolvable circular reference?
2022-05-16 10:56:44.872 INFO 13271 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2022-05-16 10:56:44.885 INFO 13271 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-05-16 10:56:44.900 ERROR 13271 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| OAuth2LoginSecurityConfig
↑ ↓
| org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration
↑ ↓
| org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
2 原因
原因是 OAuth2LoginSecurityConfig 在加载 ClientRegistration 对象时,ClientRegistrationRepository还没有注入到Spring容器,然后内部一些加载机制,产生了依赖循环。
关于如何解决依赖循环,请参考这篇:https://blog.csdn.net/skh2015java/article/details/120957652
3 解决方案
我尝试将 clientRegistrationRepository() 上的 @Bean 改为 @Lazy,程序是能正常启动了,但是该 clientRegistrationRepository()返回的ClientRegistrationRepository对象,还是没有注入到Spring容器中。
方案1:于是我将 ClientRegistrationRepository 的注入,单独放到一个配置类中。解决问题。
方案2:以 ClientRegistrationRepository 的注入为主,创建配置类 OAuth2LoginConfig ,然后将 OAuth2LoginSecurityConfig 作为静态内部类,放到该配置类OAuth2LoginConfig中。详细代码如下:
package com.ian.config;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.web.client.RestTemplate;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* 覆盖默认的oauth2配置,以及覆盖了 application.yml 中的配置
* 注:该类可以不存在,也能正常使用oauth2
*
* register a ClientRegistrationRepository @Bean
* @author Witt
* @version 1.0.0
* @date 2022/5/5
*/
@Configuration
public class OAuth2LoginConfig {
/**
* 覆盖默认的oauth2代码配置,以及覆盖了 application.yml 中的配置
* enable OAuth 2.0 login through httpSecurity.oauth2Login()
* 注:该类可以不存在,也能正常使用oauth2
*
* @author Witt
* @version 1.0.0
* @date 2022/5/5
*/
@EnableWebSecurity
public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
// .oauth2Login(withDefaults());
.oauth2Login(oauth2Login -> oauth2Login
.authorizationEndpoint(withDefaults())
.redirectionEndpoint(withDefaults())
.tokenEndpoint(withDefaults())
.userInfoEndpoint(withDefaults()));
}
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
// 该构造器中可以放置多个 ClientRegistration
return new InMemoryClientRegistrationRepository(giteeClientRegistration());
}
private ClientRegistration githubClientRegistration() {
return ClientRegistration.withRegistrationId("github")
.clientId("810cb8e57c48403ba804")
.clientSecret("b4c405151bf0dea5c8be46548fec730dcb339172")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) // default value
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // default value
// .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") // default value
// .scope("openid", "profile", "email", "address", "phone")
.scope("read:user") // default value
.authorizationUri("https://github.com/login/oauth/authorize") // default value
.tokenUri("https://github.com/login/oauth/access_token") // default value
.userInfoUri("https://api.github.com/user") // default value
// .userNameAttributeName(IdTokenClaimNames.SUB)
.userNameAttributeName("id") // default value
// .jwkSetUri("")
.clientName("Github") // default value
.build();
}
private ClientRegistration giteeClientRegistration() {
return ClientRegistration.withRegistrationId("gitee")
.clientId("de91bdadc6b6b3599bebfa8755850fca0844aae44f13cc5e076110e961d4b59e")
.clientSecret("6baa392358c93949b4f5e9e443dfa646746eb40c67ffa3eb87decb408d500b77")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) // default value
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // default value
// .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
// .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") // default value
.redirectUri("http://localhost:8050/authorization_code") //
// .scope("openid", "profile", "email", "address", "phone")
// .scope("user") // default value,但是报错:存在错误,请求范围无效、未知或格式不正确
.authorizationUri("https://gitee.com/oauth/authorize") // default value
.tokenUri("https://gitee.com/oauth/token") // default value
.userInfoUri("https://gitee.com/api/v5/user") // default value
// .userNameAttributeName(IdTokenClaimNames.SUB)
.userNameAttributeName("id") // default value
// .jwkSetUri("")
.clientName("Gitee") // default value
.build();
}
}
4 拓展
和oauth2相关的 bean,最好不要 放到 public class SecurityConfig extends WebSecurityConfigurerAdapter {...}
这个类中注入,因为会产生循环引用,从而导致依赖循环。
可以单独再创建一个类来注入这些 bean。