1. 程式人生 > 其它 >oauth2客戶端登入報錯BeanCurrentlyInCreationException

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();
    }
}