Я работаю над реализацией токенов доступа и обновления с помощью OAuth 2.0. Я реализовал собственный тип гранта и могу обновить токен доступа, используя токен обновления с уже существующими OAuth2RefreshTokenAuthenticationConverter
и OAuth2RefreshTokenAuthenticationProvider
. Проблема в том, что перед предоставлением нового токена доступа мне нужно вызвать в базу данных, чтобы убедиться, что пользователь все еще активен, и выполнить дополнительные проверки (например, чтобы определить, потеряли ли они за это время право на вход в систему). В идеале мне нужно вызвать аутентификациюManager.authenticate перед предоставлением нового токена доступа.
Я попытался реализовать фильтр OncePerRequest
для дополнительной проверки, но внутри него у меня нет доступа к имени пользователя, только к токену обновления, типу гранта и области действия.
Существует ли фильтр, который принимает OAuth2AuthenticationToken и запускается непосредственно перед OAuth2RefreshTokenAuthenticationProvider
?
Я также подумал о том, чтобы скопировать весь OAuth2RefreshTokenAuthenticationConverter
и соответствующим образом откорректировать его. Однако я бы не хотел создавать копию конвертера из соображений удобства обслуживания.
Заранее спасибо!
Напишите собственный AuthenticationProvider для выполнения дополнительных проверок. Он запустится до запуска OAuth2RefreshTokenAuthenticationProvider
. Например:
/**
* If the user account is locked, then throws an exception, which stops subsequent
* {@link AuthenticationProvider#authenticate AuthenticationProvider}s being tried.
*/
@RequiredArgsConstructor
public class UserDetailsRefreshTokenAuthenticationProvider implements AuthenticationProvider {
private final CustomUserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var authenticationToken = (OAuth2RefreshTokenAuthenticationToken) authentication;
String refreshToken = authenticationToken.getRefreshToken();
if (userDetailsService.isAccountLocked(refreshToken)) {
throw new LockedException("User account is locked");
}
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2RefreshTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Добавьте этот пользовательский AuthenticationProvider в конечную точку токена:
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(
HttpSecurity http,
UserDetailsRefreshTokenAuthenticationProvider userDetailsRefreshTokenAuthenticationProvider
) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.tokenEndpoint(endpoint -> endpoint
.authenticationProvider(userDetailsRefreshTokenAuthenticationProvider)
// ...
Ваш RefreshTokenValidationFilter также отвечает за чтение параметров HTTP-запроса и запись HTTP-ответа? Об этом позаботится OAuth2TokenEndpointFilter Spring Authorization Server, поэтому вам нужно всего лишь реализовать собственный поставщик аутентификации.
Он не записывает ответ, но если requestURI имеет значение oauth/token и содержит тип грантаrefresh_token, он вызывает диспетчер аутентификации с помощью PreAuthenticatedAuthenticationToken, и если все в порядке, то он вызывает следующий фильтр в цепочке с помощью 'filterChain.doFilter(request , ответ)', в противном случае выдается ошибка. Это правильный способ сделать это?
На самом деле я напрямую использую PreAuthenticatedAuthenticationProvider.authenticate вместо AuthenticationManager.
Если запрос токена не удался, то спецификация OAuth 2.0 требует, чтобы сервер авторизации возвращал ответ об ошибке. Если ваш фильтр выдает исключение, что отправляет ответ об ошибке?
Извините за задержку, как-то пропустил ваш ответ. RefreshTokenValidationFilter добавляется перед UsernamePasswordAuthenticationFilter, и когда он генерирует исключение, исключение обрабатывается и записывается в ответ методом OAuth2ClientAuthenticationFilter.onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationExceptionException). В ответе этот метод показывает только код ошибки и скрывает описание ошибки.
Кстати, если я реализую RefreshTokenAuthenticationProvider, мне всегда придется выдавать один из типов «AccountStatusException», чтобы предотвратить обновление токена доступа AuthenticationProvider по умолчанию, или есть другой способ? Когда я вызываю исключение OAuth2AuthException, токен доступа по-прежнему обновляется с помощью RefreshTokenAuhenticationProvider по умолчанию, если токен обновления действителен.
В Javadoc говорится, что вы должны выдать исключение AccountStatusException, чтобы предотвратить выполнение последующих поставщиков в списке.
Огромное спасибо, Чин, за все ответы. Я реализовал это так, как вы предложили.
Спасибо за Ваш ответ! В реализации я видел, что «.authenticationProvider()» ставит указанного поставщика аутентификации первым элементом в списке поставщиков аутентификации, и это будет работать. Тем временем я реализовал RefreshTokenValidationFilter, который расширяет OncePerRequestFilter, и реализовал там свою логику. По вашему мнению, какой подход лучше: использовать фильтр или поставщика аутентификации?