refresh_token grant_type error: UserDetailsService is required. But I dont want to specify one
refresh_token grant_type error: UserDetailsService is required. But I dont want to specify one
我正在尝试使用 spring 引导和依赖项创建 Oauth authentication/authorization 服务器
* spring-security-oauth2-autoconfigure
* 雨云-jose-jwt
问题是我不想指定 UserDetailsService,因为有关用户帐户的信息位于另一个不公开密码的服务中。该服务只有一个 API,其中输入为 user/pass,输出为用户信息(如果用户 exists/credentials 正确)。
所以我的code/configuration和文档有点偏差
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//injections
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(jwtTokenStore)
.accessTokenConverter(accessTokenConverter)
.authenticationManager(authenticationManager);
}
}
和
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//injections
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(travelerAuthenticationProvider); //my custom // authentication provider that calls the other service for checking credentials
}
}
和
@Component
public class TravelerAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TravelerAuthenticationProvider.class);
private OrderTravelerProfileClient travelerProfileClient;
public TravelerAuthenticationProvider(OrderTravelerProfileClient travelerProfileClient) {
this.travelerProfileClient = travelerProfileClient;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getName() == null || (authentication.getCredentials().toString().isEmpty())) {
return null;
}
var username = authentication.getName();
var password = authentication.getCredentials().toString();
try {
travelerProfileClient.authenticate(username, password);
} catch (Exception e) {
LOGGER.error("checking traveler {} credentials failed", username, e);
throw new BadCredentialsException("wrong traveler credentials");
}
var authorities = Set.of(new SimpleGrantedAuthority("traveler"));
var updatedAuthentication = new UsernamePasswordAuthenticationToken(username, password, authorities);
return updatedAuthentication;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
与 client_credentials 和密码流程相关的一切都有效,但是当我尝试使用 refresh_token 流程时,它会抱怨 UserDetailsService is required
。我应该如何在不定义 UserDetailsService 并仅依赖我的自定义身份验证提供程序的情况下解决问题?
更新:
显然 refresh_token 流程重新检查了身份验证(凭据),这需要另一个类型为 PreAuthenticatedAuthenticationToken.class 的身份验证提供程序。
所以我像这样创建了一个新的身份验证提供程序:
@Component
public class TravelerRefreshTokenBasedAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TravelerRefreshTokenBasedAuthenticationProvider.class);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var currentAuthentication = (PreAuthenticatedAuthenticationToken) authentication;
//.....
return updatedAuthentication;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
}
并将我的安全配置更新为:
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//injections
//this bean will be more configured by the method below and it will be used by spring boot
//for authenticating requests. Its kind of an equivalent to userDetailsService
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(travelerUserPassBasedAuthenticationProvider);
authenticationManagerBuilder.authenticationProvider(travelerRefreshTokenBasedAuthenticationProvider);
}
}
问题是 spring 在 refresh_token 流程中无法识别我的身份验证提供程序并尝试使用默认提供程序。默认的是尝试使用不存在的 UserDetailsService。
我也觉得我不需要创建另一个提供程序,我可以重用以前的提供程序。因为 spring 未能使用我的自定义提供程序的检查是针对 user/pass 的检查;我在以前的身份验证提供程序中所做的。
所以总而言之,直到现在,我觉得我必须以不同的方式将我的自定义提供程序介绍给 spring refresh_token 流与密码流
您的 AuthenticationProvider
实现仅支持 UsernamePasswordAuthenticationToken
,用于 username/password 身份验证,而 refresh_token
流程尝试使用 PreAuthenticatedAuthenticationToken
更新身份验证(参见 DefaultTokenServices.java)。
因此您需要为 PreAuthenticatedAuthenticationToken
创建另一个 AuthenticationProvider
并将其添加到 AuthenticationManagerBuilder
。
更新:
我发现 AuthorizationServerEndpointsConfigurer
会创建一个新的 DefaultTokenServices
实例,如果 none 被赋值,这又会创建一个 PreAuthenticatedAuthenticationProvider
的新实例并执行不使用提供的 AuthenticationManager
。为避免这种情况,您可以创建自己的 DefaultTokenServices
实例并将其传递给 AuthorizationServerEndpointsConfigurer
:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(jwtTokenStore)
.accessTokenConverter(accessTokenConverter)
.tokenEnhancer(accessTokenConverter)
.authenticationManager(authenticationManager)
.tokenServices(createTokenServices(endpoints, authenticationManager));
}
private DefaultTokenServices createTokenServices(AuthorizationServerEndpointsConfigurer endpoints, AuthenticationManager authenticationManager) {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}
我正在尝试使用 spring 引导和依赖项创建 Oauth authentication/authorization 服务器 * spring-security-oauth2-autoconfigure * 雨云-jose-jwt
问题是我不想指定 UserDetailsService,因为有关用户帐户的信息位于另一个不公开密码的服务中。该服务只有一个 API,其中输入为 user/pass,输出为用户信息(如果用户 exists/credentials 正确)。
所以我的code/configuration和文档有点偏差
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//injections
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(jwtTokenStore)
.accessTokenConverter(accessTokenConverter)
.authenticationManager(authenticationManager);
}
}
和
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//injections
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(travelerAuthenticationProvider); //my custom // authentication provider that calls the other service for checking credentials
}
}
和
@Component
public class TravelerAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TravelerAuthenticationProvider.class);
private OrderTravelerProfileClient travelerProfileClient;
public TravelerAuthenticationProvider(OrderTravelerProfileClient travelerProfileClient) {
this.travelerProfileClient = travelerProfileClient;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getName() == null || (authentication.getCredentials().toString().isEmpty())) {
return null;
}
var username = authentication.getName();
var password = authentication.getCredentials().toString();
try {
travelerProfileClient.authenticate(username, password);
} catch (Exception e) {
LOGGER.error("checking traveler {} credentials failed", username, e);
throw new BadCredentialsException("wrong traveler credentials");
}
var authorities = Set.of(new SimpleGrantedAuthority("traveler"));
var updatedAuthentication = new UsernamePasswordAuthenticationToken(username, password, authorities);
return updatedAuthentication;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
与 client_credentials 和密码流程相关的一切都有效,但是当我尝试使用 refresh_token 流程时,它会抱怨 UserDetailsService is required
。我应该如何在不定义 UserDetailsService 并仅依赖我的自定义身份验证提供程序的情况下解决问题?
更新: 显然 refresh_token 流程重新检查了身份验证(凭据),这需要另一个类型为 PreAuthenticatedAuthenticationToken.class 的身份验证提供程序。
所以我像这样创建了一个新的身份验证提供程序:
@Component
public class TravelerRefreshTokenBasedAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TravelerRefreshTokenBasedAuthenticationProvider.class);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var currentAuthentication = (PreAuthenticatedAuthenticationToken) authentication;
//.....
return updatedAuthentication;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
}
并将我的安全配置更新为:
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//injections
//this bean will be more configured by the method below and it will be used by spring boot
//for authenticating requests. Its kind of an equivalent to userDetailsService
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(travelerUserPassBasedAuthenticationProvider);
authenticationManagerBuilder.authenticationProvider(travelerRefreshTokenBasedAuthenticationProvider);
}
}
问题是 spring 在 refresh_token 流程中无法识别我的身份验证提供程序并尝试使用默认提供程序。默认的是尝试使用不存在的 UserDetailsService。
我也觉得我不需要创建另一个提供程序,我可以重用以前的提供程序。因为 spring 未能使用我的自定义提供程序的检查是针对 user/pass 的检查;我在以前的身份验证提供程序中所做的。
所以总而言之,直到现在,我觉得我必须以不同的方式将我的自定义提供程序介绍给 spring refresh_token 流与密码流
您的 AuthenticationProvider
实现仅支持 UsernamePasswordAuthenticationToken
,用于 username/password 身份验证,而 refresh_token
流程尝试使用 PreAuthenticatedAuthenticationToken
更新身份验证(参见 DefaultTokenServices.java)。
因此您需要为 PreAuthenticatedAuthenticationToken
创建另一个 AuthenticationProvider
并将其添加到 AuthenticationManagerBuilder
。
更新:
我发现 AuthorizationServerEndpointsConfigurer
会创建一个新的 DefaultTokenServices
实例,如果 none 被赋值,这又会创建一个 PreAuthenticatedAuthenticationProvider
的新实例并执行不使用提供的 AuthenticationManager
。为避免这种情况,您可以创建自己的 DefaultTokenServices
实例并将其传递给 AuthorizationServerEndpointsConfigurer
:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(jwtTokenStore)
.accessTokenConverter(accessTokenConverter)
.tokenEnhancer(accessTokenConverter)
.authenticationManager(authenticationManager)
.tokenServices(createTokenServices(endpoints, authenticationManager));
}
private DefaultTokenServices createTokenServices(AuthorizationServerEndpointsConfigurer endpoints, AuthenticationManager authenticationManager) {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}