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。

posted @ 2022-05-16 11:23  mediocrep  阅读(267)  评论(0编辑  收藏  举报
既然选择了远方,便只顾风雨兼程!