Токен обновления не используется Spring OAuth2 по истечении срока действия токена доступаJAVA

Программисты JAVA общаются здесь
Ответить Пред. темаСлед. тема
Anonymous
 Токен обновления не используется Spring OAuth2 по истечении срока действия токена доступа

Сообщение Anonymous »

Привет, коллеги-разработчики!
Уже пару дней я пытаюсь выяснить, почему Spring OAuth2 не использует токен обновления, возвращаемый для предоставления кода авторизации, когда срок действия токена доступа истекает.
Настройка
По сути, я следовал этому руководству, чтобы настроить все необходимое. Мой Keycloak работает на локальном хосте: 8090, и я настроил его следующим образом:

Код: Выделить всё

package com.my.project.config.security;

import com.my.project.security.authentication.CustomOidcClientInitiatedLogoutSuccessHandler;
import jakarta.servlet.http.Cookie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.*;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;

@Configuration
public class WebSecurityConfig {

public static String[] PUBLIC_PATHS = {
"/api/csrf",
"/oauth2/login",
"/oauth2/logout",
"/oauth2/error",
"/api/contact-form"
};

@Bean
public SecurityFilterChain securityFilterChain(final HttpSecurity http,
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
final ClientRegistrationRepository clientRegistrationRepository) throws Exception {

final CustomOidcClientInitiatedLogoutSuccessHandler clientInitiatedLogoutSuccessHandler =
new CustomOidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
clientInitiatedLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/oauth2/login?redirect=true");

final DefaultOAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver =
new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
oAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());

return http
.csrf(Customizer.withDefaults())
.cors(Customizer.withDefaults())
.authorizeHttpRequests((customizer) -> customizer
.requestMatchers(PUBLIC_PATHS).permitAll()
.anyRequest().authenticated())

.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults()))
.oauth2Client(configurer -> {
//override behaviour of authentication: don't redirect but change status and add location header.
//it's a bit hacky, but otherwise we get CORS errors on client side, because through the redirect we're running into cross-origin issues
//and keycloak is just not setting correct CORS headers :/

//delete this hack when bug https://github.com/keycloak/keycloak/pull/27334 is fixed

configurer.authorizationCodeGrant(customizer -> {
customizer.authorizationRedirectStrategy((request, response, url) ->  {
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, HttpHeaders.LOCATION);
response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.LOCATION);
response.addCookie(new Cookie("test", System.currentTimeMillis() + ""));
response.setHeader(HttpHeaders.LOCATION, url);
if ("true".equals(request.getParameter("redirect"))) {
response.setStatus(302);
} else {
response.setStatus(201);
}

});

customizer.authorizationRequestResolver(oAuth2AuthorizationRequestResolver);
});
})

.oauth2Login(configurer -> {

configurer.userInfoEndpoint(customizer -> customizer.userAuthoritiesMapper(customGrantedAuthoritiesMapper()));
configurer.loginPage("/oauth2/login");
configurer.defaultSuccessUrl("/oauth2/success");
configurer.failureUrl("/oauth2/error");
})

.logout(configurer -> {
configurer.invalidateHttpSession(true);
configurer.clearAuthentication(true);
configurer.deleteCookies("JSESSIONID");
configurer.logoutUrl("/oauth2/logout");
configurer.logoutSuccessHandler(clientInitiatedLogoutSuccessHandler);
})

//.addFilterBefore(new TokenExpiredFilter(), AnonymousAuthenticationFilter.class)
.build();
}

@Bean
public GrantedAuthoritiesMapper customGrantedAuthoritiesMapper() {
return new CustomGrantedAuthoritiesMapper();
}

}
Мне потребовалось несколько настроек, потому что у меня были проблемы с Keycloak, который не устанавливал заголовки ACCESS-CONTROL-ALLOW_ORIGIN. Поэтому я последовал предложению изменить перенаправление по умолчанию (HTTP 302) на код 2xx при настройке заголовка местоположения. Затем в коде внешнего интерфейса (React) я добавляю window.location к значению этого заголовка местоположения. Для пользовательского обработчика выхода из системы я делаю то же самое. В противном случае это копия SimpleUrlLogoutSuccessHandler.
Мой файл application.yml выглядит так:

Код: Выделить всё

service:
mock: false

keycloak:
realm-id: my-realm-id
base-uri: http://localhost:8090
base-rest-uri: ${keycloak.base-uri}/admin/realms/${keycloak.realm-id}
token-uri: ${keycloak.base-uri}/realms/${keycloak.realm-id}/protocol/openid-connect/token

logging:
level:
root: INFO

server:
port: 8080
servlet:
session:
timeout: 15s
cookie:
same-site: none
http-only: true
secure: true

error:
include-message: never
include-binding-errors: never
include-stacktrace: never
include-exception: false

spring:
datasource:
url: jdbc:postgresql://localhost:5432/db-name
driverClassName: org.postgresql.Driver
username: my-user
password: my-password
jpa:
open-in-view: false
hibernate:
ddl-auto: none
properties:
database-platform: org.hibernate.dialect.PostgreSQL10Dialect
show-sql: false

security:
oauth2:
client:
registration:
keycloak:
client_id: my-client-id
client_secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid
provider:
keycloak:
issuerUri: ${keycloak.base-uri}/realms/${keycloak.realm-id}
user-name-attribute: preferred_username
resourceserver:
jwt:
issuer-uri: ${keycloak.base-uri}/realms/${keycloak.realm-id}

flyway:
url: ${spring.datasource.url}
user: ${spring.datasource.username}
password: ${spring.datasource.password}
locations: classpath:/db/migration/ddl
Описание поведения/проблемы
Когда я пытаюсь запросить защищенный ресурс в своем бэкэнде Spring, я получаю перенаправление на /oauth2/login, который перенаправляет на /oauth2/authorization/keycloak, который, наконец, «перенаправляет» на страницу входа в Keycloak. Это работает хорошо, я никогда не связываюсь с учетными данными пользователя, а интерфейс знает только JSESSIONID. Кроме того, выход из системы работает нормально, сеанс в области Keycloak успешно уничтожается, и сеанс Spring тоже уничтожается.
Мои проблемы начинаются, когда дело доходит до токена доступа с истекшим сроком действия. В целях тестирования я установил продолжительность жизни токена доступа на 10 секунд в Keycloak, а продолжительность сеанса Spring — на 15 секунд. Всякий раз, когда я запрашиваю что-то из своего защищенного бэкэнда и срок действия сеанса истекает, процесс входа в систему запускается снова, перенаправляясь на /oauth2/login, который перенаправляется на /oauth2/authorization/keycloak и, наконец, на настроенный URL-адрес успешного входа в систему oauth2, не видя входа в систему. сформировать заново. В пользовательских событиях Keycloak я вижу событие входа другого пользователя и код для токена с новым токеном доступа в качестве результата.
Когда я отлаживаю код Spring, я вижу, что грант авторизации_кода возвращает оба токен доступа и токен обновления правильно, но кажется, что токен обновления больше никогда не используется. К сожалению, я не могу понять, в чем дело, но я читал, что Spring должен обрабатывать обновление токена доступа, используя токен обновления из коробки. Но

org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider#authorize

никогда не вызывается и не вызывается

org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient#getTokenResponse

Я вижу это

org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter#attemptAuthentication

вызывает

this.authorizedClientRepository.saveAuthorizedClient(authorizedClient,
oauth2Authentication, request , ответ)

Похоже, это последний след токена обновления, поскольку его не существует в OAuth2AuthenticationToken, возвращенном из этого метода.
К сожалению, ни в одной реализации нет точки останова

org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository#loadAuthorizedClient< /p>

когда-либо вызывается для повторного получения токена обновления.
Любая помощь приветствуется, так как я действительно застрял здесь. Большое спасибо за любой ответ, вопросы и помощь!

Подробнее здесь: https://stackoverflow.com/questions/783 ... en-expires
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «JAVA»