Я пытаюсь настроить свой Spring Boot 2, OAuth2 с сервером авторизации JWT, который должен выполнить следующие действия:
- взять имя пользователя / пароль, создать объект CustomUserDetails на основе данных БД и данных Salesforce и вернуть маркер JWT, если аутентификация прошла (это работает)
- взять токен обновления и вернуть новый токен обновления JWT и доступа (это работает)
- (NEW) возьмите токен обновления, проверьте базу данных на наличие сохраненного идентификатора токена, прежде чем возвращать новый токен доступа + обновления JWT (это проблема). Суть в том, чтобы убедиться, что только одно устройство подключено к учетные данные пользователя за раз.
Чтобы выполнить # 3, я пытаюсь настроить PreAuthenticatedAuthenticationProvider
, задав ему пользовательский UserDetailsService
, и бин AuthenticationManagerBuilder
должен быть передан как настроенному PreAuthenticatedAuthenticationProvider
, так и DaoAuthenticationProvider
.
Если предположить, что я иду в правильном направлении, вот мой код конфигурации:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Autowired
PasswordEncoder passwordEncoder;
//implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>
@Autowired
CustomPreauthenticatedUserDetailsService customPreauthenticatedUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/swagger-ui**","/webjars/**","/swagger-resources/**", "/v2/**").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/oauth/token/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManager) throws Exception {
authenticationManager.authenticationProvider(preauthAuthProvider());
authenticationManager.authenticationProvider(dbAuthProvider());
}
@Bean
@Qualifier(value = "authenticationManagerBean")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean(value="preAuthProvider")
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
LOGGER.info("Setting customPreauthenticatedUserDetailsService");
preauthAuthProvider.setPreAuthenticatedUserDetailsService(customPreauthenticatedUserDetailsService);
return preauthAuthProvider;
}
@Bean(value="dbAuthProvider")
public DaoAuthenticationProvider dbAuthProvider() {
DaoAuthenticationProvider dbAuthProvider = new DaoAuthenticationProvider();
dbAuthProvider.setUserDetailsService(userDetailsService);
dbAuthProvider.setPasswordEncoder(passwordEncoder);
return dbAuthProvider;
}
}
На стороне AuthorizationServerConfig
:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final Integer ACCESS_TOKEN_VALIDITY_SECONDS = 300;
private static final Integer REFRESH_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 60;
@Autowired
public AuthorizationServerConfig(AuthenticationManager authenticationManagerBean, PasswordEncoder passwordEncoder, CustomTokenEnhancer customTokenEnhancer, TokenStore tokenStore, JwtAccessTokenConverter accessTokenConverter) {
this.authenticationManagerBean = authenticationManagerBean;
this.passwordEncoder = passwordEncoder;
this.customTokenEnhancer = customTokenEnhancer;
this.tokenStore = tokenStore;
this.accessTokenConverter = accessTokenConverter;
}
private AuthenticationManager authenticationManagerBean;
private PasswordEncoder passwordEncoder;
private CustomTokenEnhancer customTokenEnhancer;
private JwtAccessTokenConverter accessTokenConverter;
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) {
authorizationServerSecurityConfigurer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
clientDetailsServiceConfigurer.inMemory().withClient("someclient")
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write").accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS)
.secret(this.passwordEncoder.encode("somepassword"));
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, accessTokenConverter));
endpoints.tokenStore(tokenStore).tokenEnhancer(tokenEnhancerChain)
.authenticationManager(this.authenticationManagerBean);
}
}
После запуска приложения появляются первые признаки проблемы:
s.c.a.w.c.WebSecurityConfigurerAdapter$3 : No authenticationProviders and no parentAuthenticationManager defined. Returning null.
Как ни странно, это кажется ложью или говорит о чем-то другом, потому что, когда я пытаюсь войти в систему с именем пользователя и паролем - это работает, и я получаю и свои обновления, и токены доступа. Отладчик показывает, что ProviderManager
имеет и мои собственные классы провайдера аутентификации, и использует их для аутентификации.
Однако, когда я пытаюсь получить новый токен с токеном обновления, отладчик показывает, что приложение, похоже, использует другой путь ProviderManager
- этот ProviderManager
имеет только PreAuthenticatedAuthenticationProvider
в своем списке, и что провайдер не тот, который я настроил. PreAuthenticatedAuthenticationProvider
пытается получить UserDetailsService
из WebSecurityConfigurerAdapter$UserDetailsServiceDelegator
, и в результате возникает ошибка:
2019-06-08 13:27:24.764 ERROR 8731 --- [nio-8080-exec-3] o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
Итак, какой шаг настройки я пропустил? Почему вызов токена обновления происходит в другом месте? Это authenticationManagerBean
, который я передаю в AuthorizationServerConfig
?