以下是在Spring-Cloud(Greenwich.RELEASE)集成OAuth2(2.3.3.RELEASE)过程中出现的一个问题,项目管理工具使用的gradle,学习用例产生的问题仅供参考(如学习有误,请及时打脸纠正嘴型)。
gradle引入的Spring-Cloud-OAuth2依赖:
implementation 'org.springframework.cloud:spring-cloud-starter-oauth2'
Application的入口类:
package com.hoku.account;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
@EnableOAuth2Client
@EnableFeignClients
@SpringCloudApplication
public class AccountApplication {
public static void main(String[] args) {
SpringApplication.run(AccountApplication.class);
}
}
application.yml oauth2部分配置信息:
security:
oauth2:
# XXXXXResourceDetails
client:
clientId: ${ACCOUNT_CLIENT_ID:client1}
clientSecret: ${ACCOUNT_CLIENT_PASSWORD:123456}
accessTokenUri: ${ACCESS_TOKEN_URI:http://localhost:9200/uaa/oauth/token}
grant-type: client_credentials
scope: server
#ResourceServerProperties
resource:
userInfoUri: ${USER_INFO_URI:http://localhost:9200/uaa/user/info}
在搭建好注册中心(Eureka),认证授权服务(OAuth2)之后,搭建基于认证授权服务的客户端时,启动出现如下错误:
The bean 'oauth2ClientContext', defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration$SingletonScopedConfiguration.class], could not be registered. A bean with that name has already been defined in BeanDefinition defined in class path resource [org/springframework/security/oauth2/config/annotation/web/configuration/OAuth2ClientConfiguration$OAuth2ClientContextConfiguration.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
错误信息提示bean名称为:oauth2ClientContext)已经在OAuth2ClientConfiguration中定义了,在OAuth2RestOperationsConfiguration$SingletonScopedConfiguration中无法再次定义,并且提示可以通过在application.yml中添加配置属性spring.main.allow-bean-definition-overridng=true解决这个问题,该属性是可以让后面定义的bean覆盖已经存在的同类型bean. 该方法可以解决问题启动报错问题,但是bean覆盖并不是合理的方式。
下面说一下另外一种方式:
首先根据报错提到的两个Configuration,OAuth2RestOperationsConfiguration$SingletonScopedConfiguration和OAuth2ClientConfiguration$OAuth2ClientContextConfiguration,一个来自与spring-security-oauth2-autoconfigure,另一个来自于spring-security-oauth2。
在EnableOAuth2Client注解中可以看到,OAuth2ClientConfiguration是定义导入的配置,所以OAuth2RestOperationsConfiguration应该是由于autoconfigure原因初始化的配置,所以想办法排除掉OAuth2RestOperationsConfiguration$SingletonScopedConfiguration配置应该是可以解决问题的。
@EnableOAuth2Client源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(OAuth2ClientConfiguration.class)
public @interface EnableOAuth2Client {
}
OAuth2ClientConfiguration源码:
@Configuration
public class OAuth2ClientConfiguration {
@Bean
public OAuth2ClientContextFilter oauth2ClientContextFilter() {
OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter();
return filter;
}
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
protected AccessTokenRequest accessTokenRequest(@Value("#{request.parameterMap}")
Map<String, String[]> parameters, @Value("#{request.getAttribute('currentUri')}")
String currentUri) {
DefaultAccessTokenRequest request = new DefaultAccessTokenRequest(parameters);
request.setCurrentUri(currentUri);
return request;
}
@Configuration
protected static class OAuth2ClientContextConfiguration {
@Resource
@Qualifier("accessTokenRequest")
private AccessTokenRequest accessTokenRequest;
//定义了一个session级的bean名字默认为oauth2ClientContext
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(accessTokenRequest);
}
}
}
OAuth2RestOperationsConfiguration$SingletonScopedConfiguration部分源码:
@Configuration
@ConditionalOnClass(EnableOAuth2Client.class)
public class OAuth2RestOperationsConfiguration {
@Configuration
@Conditional(ClientCredentialsCondition.class) //让这个条件不成立即可
protected static class SingletonScopedConfiguration {
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
@Primary
public ClientCredentialsResourceDetails oauth2RemoteResource() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
return details;
}
//此处oauth2ClientContext bean在OAuth2ClientConfiguration中已经定义
@Bean
public DefaultOAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest());
}
}
....
/**
* Condition to check for client credentials.
*/
static class ClientCredentialsCondition extends AnyNestedCondition {
ClientCredentialsCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
//application.yml中配置了这个grant-type属性,所以导致该条件是matched,
//所以让该条件不成立就不会启用SingletonScopedConfiguration 配置,就不会
//出现多个oauth2ClientContext的bean
@ConditionalOnProperty(prefix = "security.oauth2.client", name = "grant-type", havingValue = "client_credentials", matchIfMissing = false)
static class ClientCredentialsConfigured {
}
//spring-boot是个web程序所以这个条件不会matched
@ConditionalOnNotWebApplication
static class NoWebApplication {
}
}
}
下面才是具体解决方法:
1. 去掉application.yml 中的grant-type属性,其他不变。
2. 好了
再次启动,不会出现oauth2ClientContext重复定义问题了。