报错如下:
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:227) ~[spring-security-core-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:501) ~[spring-security-config-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at com.test.oauth.service.impl.CustomTokenServices.refreshAccessToken(CustomTokenServices.java:141) ~[classes/:na]
at com.test.oauth.service.impl.CustomTokenServices$$FastClassBySpringCGLIB$$a7dec4f7.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor$$Lambda$1311/534220233.proceedWithInvocation(Unknown Source) ~[na:na]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367) ~[spring-tx-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.7.RELEASE.jar:5.2.7.RELEASE]
......
解决方案:
只需把PreAuthenticatedAuthenticationProvider添加到authenticationManager中就可以了。可通过AuthenticationManagerBuilder添加到authenticationManager中。
这里记录下两种添加方式:
第一种:在WebSecurityConfigurerAdapter.class里添加
import com.test.oauth.service.impl.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthenticatedAuthenticationProvider();
preAuthenticatedAuthenticationProvider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(customUserDetailsService));
return preAuthenticatedAuthenticationProvider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
}
}
第二种:在GlobalAuthenticationConfigurerAdapter.class里添加
import com.test.oauth.service.impl.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
@Configuration
public class GlobalAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthenticatedAuthenticationProvider();
preAuthenticatedAuthenticationProvider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(customUserDetailsService));
return preAuthenticatedAuthenticationProvider;
}
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
}
}
然后再自己实现CustomUserDetailsService.loadUserByUsername(String userName)
import com.test.oauth.domain.User;
import com.test.oauth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.Collections;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) {
User user = userService.queryUserByName(username);
if (user == null) {
throw new AuthenticationServiceException("username.notFound");
}
return new org.springframework.security.core.userdetails.User(user.getName(), user.getPassword(), Collections.emptyList());
}
}
注意:如果有多个provider不能分开在WebSecurityConfigurerAdapter子类WebSecurityConfig 和 GlobalAuthenticationConfigurerAdapter子类GlobalAuthenticationConfig里配,
因为,AuthenticationManager是以最后一次WebSecurityConfigurerAdapter类调用authenticationManager() 为准的。每次调用authenticationManager()方法都是按@order配置的顺序调用一次本类或子类的configure(AuthenticationManagerBuilder auth)方法,并根据属性disableLocalConfigureAuthenticationBldr为true时调用GlobalAuthenticationConfigurerAdapter的init(AuthenticationManagerBuilder auth)装配builder并赋值AuthenticationManager,为false时使用子类的configure(AuthenticationManagerBuilder auth)装配的builder赋值AuthenticationManager。
authenticationManager()方法源码如下:(在org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter里)
// 按@Order配置的顺序加载AuthenticationManager,以最后一次为准
protected AuthenticationManager authenticationManager() throws Exception {
if (!this.authenticationManagerInitialized) {
// 调用WebSecurityConfigurerAdapter本类或其子类的configure,
// 子类的configure装配的builder会放到this.localConfigureAuthenticationBldr对象里
// 本类的configure不装配builder,而是设置disableLocalConfigureAuthenticationBldr属性,让GlobalAuthenticationConfigurerAdapter装配
this.configure(this.localConfigureAuthenticationBldr);
if (this.disableLocalConfigureAuthenticationBldr) {
// 会调用GlobalAuthenticationConfigurerAdapter的init方法装配builder
this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager();
} else {
// 使用子类装配的builder
this.authenticationManager = (AuthenticationManager)this.localConfigureAuthenticationBldr.build();
}
this.authenticationManagerInitialized = true;
}
return this.authenticationManager;
}
具体BUG原因分析请参考避坑指南(一):Spring Security Oauth2中refresh_token刷新access_token异常_银河架构师的博客-CSDN博客_oauth2 refreshtoken 用法