Я хочу, чтобы мое приложение могло отправлять запросы REST API от имени пользователей определенной платформы. Я зарегистрировал свое приложение на платформе, и у них есть поддержка OAuth2 со следующими конечными точками:
GET /login/oauth2/auth
, который является URI авторизации для запуска потока аутентификации_кода.POST /login/oauth2/token
, куда я отправляю код (при условии, что пользователь дал согласие) и получаю ответ, содержащий токен доступа и токен обновления:{
"access_token": "...",
"refresh_token": "...",
"user": {"id":5, "name": "First Last"}
}
Сначала я использовал Spring Security oauth2Login
:
spring:
security:
oauth2:
client:
provider:
the-provider:
authorization-uri: https://provider.domain.com/login/oauth2/auth
token-uri: https://provider.domain.com/login/oauth2/token
user-info-authentication-method: Bearer
user-info-uri: https://provider.domain.com/api/users/me
registration:
the-provider:
authorization-grant-type: authorization_code
client-id: my-client-id
client-secret: myClientS3cret
redirect-uri: "{baseUrl}/login/oauth2/code/the-provider"
@Bean
public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception {
http.oauth2Login(Customizer.withDefaults());
http.authorizeHttpRequests(r -> r.anyRequest().authenticated());
return http.build();
}
По умолчанию пользователь, который обращается к моему приложению по адресу "/"
, автоматически проходит через поток OAuth2, а Spring Security помещает информацию о пользователе в контекст безопасности. Это хорошо, но я не понимаю, как получить/использовать токен доступа для последующих запросов API от имени пользователя.
Authentication
— это OAuth2AuthenticationToken
, principal
которого DefaultOAuth2User
— это authorities
, чьи [ "OAUTH2_USER" ]
являются attributes
и чьи user-info-uri
являются данными из oauth2Client
, но я не вижу нигде, где бы содержался токен доступа и токен обновления.
Проведя еще немного исследований, я понял, что, возможно, мне следует использовать oauth2Login
вместо ReportService
, но мне не совсем понятно, как это будет работать.
Просто чтобы немного прояснить то, что я себе представляю, это будет что-то вроде
@Controller
public class MyController {
private final ReportService reportService;
public MyController(final ReportService reportService) {
this.reportService = reportService;
}
@GetMapping({"", "/"})
public String index(final Model model) {
model.addAttribute("report", reportService.generateReport());
return "index";
}
}
где access_token
делает пару запросов REST API, используя oauth2Login
, сгенерированный для пользователя, который, как я полагаю, будет отключен от аутентификации контекста безопасности.
Думаю, мой вопрос как бы направлен на то, могу ли я получить это из коробки с помощью Spring Security (через oauth2Client
или GET /login/oauth2/auth
), или мне нужно будет создать что-то собственное с помощью примитивов Spring Security?
oauth2Login
настраивает приложение с кодом авторизации и обновляет потоки токенов. Итак, по задумке приложение с oauth2Login
является клиентом OAuth2.
oauth2Client
— это приложение Spring, которое получает токены с сервера авторизации. При использовании Boot клиенты OAuth2 настраиваются со свойствами spring.security.oauth2.client.*
. Чтобы oauth2Login
работал, эти свойства должны иметь хотя бы одну регистрацию в authorization_code
.
Обратите внимание, что вы можете сделать любое приложение (не только oauth2Login
, но также oauth2ResourceServer
, formLogin
и т. д.) клиентом OAuth2, если ему необходимо авторизовать запросы к серверу ресурсов.
В клиентском приложении Spring OAuth2 вы можете получить токены из (Reactive)OAuth2AuthorizedClientManager.
@Service
public class ReportServiceImpl implements ReportService {
private final OAuth2AuthorizedClientManager clients;
private final OAuth2AuthorizeRequest req;
public ReportServiceImpl(OAuth2AuthorizedClientManager clients, @Value("${report-client-registration-id:the-provider}") String reportClientRegistrationId) {
super();
this.clients = clients;
this.req = OAuth2AuthorizeRequest.withClientRegistrationId(reportClientRegistrationId).build();
}
@Override
public ReportDto generateReport() {
final var authorized = clients.authorize(req);
final var bearerString = "Bearer %s".formatted(authorized.getAccessToken().getTokenValue());
// TODO: set the bearerString as Authorization header to the request using your favorite client;
return new ReportDto();
}
}
Обратите внимание, что большинство клиентов REST включают функции прозрачной авторизации всех запросов (получите токен от «авторизованного клиента» и установите заголовок авторизации, не загромождая код службы). Например, RestClient
можно настроить с помощью requestInterceptor
функций, а WebClient
с помощью filter
функций.
spring-addons-starter-rest
Чтобы не писать код REST-клиента и его авторизацию, публикую стартер Spring Boot. Он интегрируется с Spring HttpServiceProxyFactory
, чтобы добавить RestClient
(или WebClient
) автоматическую настройку HTTP-прокси и авторизацию OAuth2 (или Basic).
Пример описания удаленной службы (вероятно, созданный на основе спецификации OpenAPI с использованием такого инструмента, как openapi-generator-maven-plugin
):
@HttpExchange(accept = MediaType.APPLICATION_JSON_VALUE)
public interface ReportApi {
@GetExchange(url = "/report")
ReportDto generateReport();
}
spring-addons
зависимость:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-starter-rest</artifactId>
<version>7.8.8</version>
</dependency>
spring-addons
конфигурация для использования токена от авторизованного клиента (альтернативной стратегией является пересылка носителя, который авторизовал входящий запрос, но это возможно только на ресурсном сервере):
com:
c4-soft:
springaddons:
rest:
client:
report-api:
base-url: http://reporting-service
authorization:
oauth2:
# Kept your registration ID, even if it is rather confusing
oauth2-registration-id: the-provider
Создав клиент удаленного обслуживания:
@Configuration
public class RestConfiguration {
@Bean
ReportApi reportApi(SpringAddonsRestClientSupport restSupport) {
return restSupport.service("report-api", ReportApi.class);
}
}
Использование в компоненте Spring:
@Controller
@RequiredArgsConstructor
public class SomeController {
// Abracadabra! This is successfully auto-wired
private final ReportApi reportApi;
...
}
Обратите внимание, что кода для реализации или авторизации ReportApi
совершенно нет, все генерируется!
В моих проектах даже клиентские интерфейсы (те, которые украшены @HttpExchange
, как ReportApi
выше) генерируются из спецификации OpenAPI, которая сама генерируется Swagger из источников удаленного сервиса @RestController
(украшенных вариантами @RequestMapping
).
Я добавил образец с «моей» закваской, чтобы дать вам представление о
HttpServiceProxyFactory
волшебстве весны. Если вы можете получить спецификацию OpenAPI для своей удаленной службы, тоopenapi-generator-maven-plugin
сможете генерировать@HttpExchange
интерфейсы (с DTO). В этой ситуации клиент REST для этой службы создается Spring и авторизуется с использованием OAuth2 «моим» стартером.