Создание документации вручную — процесс трудоёмкий и часто рутинный. В этой статье рассмотрим основные возможности Swagger для документирования REST API в приложении на Spring Boot.
Используемые версии
Java 21
Spring Boot 3.3.5 | 2.7.18
SpringDoc 2.6.0 | 1.8.0
Swagger 2.2.25
Изменения статьи
29.06.22: Обновил до Java 17. Обновил все зависимости до актуальных.
11.02.23: Обновил SpringBoot до 3.0.2. Обновил остальные зависимости. Добавил раздел с авторизацией в Swagger UI.
04.11.24: Обновил Java до 21. Обновил SpringBoot до 3.3.5. Обновил остальные зависимости.
Что такое Swagger?
Swagger — это инструмент, который позволяет разработчикам описывать структуру своих API, а также генерировать интерактивную документацию, клиентские библиотеки и серверные модули для их реализации на различных языках.
Основой Swagger является спецификация OpenAPI (OAS), представляющая единый и чёткий способ описания REST API, включая эндпойнты, параметры, модели запросов и ответов и другие аспекты API.
В данной статье рассматривается пример генерации OAS на основе существующих REST-контроллеров. Мы разметим контроллеры и их эндпойнты аннотациями, чтобы автоматически создать OAS.
В данной статье рассматривается пример генерации OAS на основе уже существующих REST контроллеров. Мы разметим контроллеры и эндпойнты аннотациями, на основе которых будет сгенерирована OAS.
Почему SpringDoc, а не SpringFox?
Существует множество статей, описывающих генерацию Swagger UI с помощью библиотеки SpringFox. Однако я решил использовать SpringDoc по нескольким причинам.
SpringFox больше не поддерживается: последний коммит был сделан в 2020 году, и количество нерешённых задач (issue) уже превысило 200. В какой-то момент я столкнулся с багом в SpringFox, который не будет исправлен, и решил искать альтернативу.
SpringDoc оказался оптимальным выбором — он активно развивается и поддерживает новые версии Spring Boot, включая Spring Boot 3. Переход с SpringFox на SpringDoc прошёл быстро и успешно.
Swagger также может сгенерировать код клиента или сервера на основе OAS с помощью Swagger Codegen. Я лично не использую этот генератор, так как созданный автоматически код часто требует значительной доработки и проще написать его вручную.
Демо проект с REST API
Для начала документирования API необходимо сначала создать его. Вы можете пропустить эту главу.
Добавим несколько простых контроллеров и одно DTO. Наша система — это программа лояльности для пользователей. В рамках данного примера мы ограничимся только слоем контроллеров, пропустив серверную и репозиторную части. Это позволит сосредоточиться на документации, а бизнес-логику временно разместим непосредственно в контроллерах.
В качестве DTO будет использоваться класс UserDto
, представляющий пользователя системы. У него пять полей, из которых три обязательны.
UserDto.java
public class UserDto {
private String key;
private String name;
private Long points = 0L;
private Gender gender;
private LocalDateTime regDate = LocalDateTime.now();
public UserDto() {
}
public UserDto(String key, String name, Gender gender) {
this.key = key;
this.name = name;
this.gender = gender;
}
public static UserDto of(String key, String value, Gender gender) {
return new UserDto(key, value, gender);
}
// getters and setters
}
public enum Gender {
MAN, WOMAN
}
Для взаимодействия с бизнес-логикой добавим три контроллера: UserController
, PointController
и SecretController
.
UserController
: Обеспечивает добавление, обновление и получение данных пользователей.
UserController.java
@RestController
@RequestMapping("/api/user")
public class UserController {
private final Map<String, UserDto> repository;
public UserController(Map<String, UserDto> repository) {
this.repository = repository;
}
@PutMapping(produces = APPLICATION_JSON_VALUE)
public HttpStatus registerUser(@RequestBody UserDto userDto) {
repository.put(userDto.getKey(), userDto);
return HttpStatus.OK;
}
@PostMapping(produces = APPLICATION_JSON_VALUE)
public HttpStatus updateUser(@RequestBody UserDto userDto) {
if (!repository.containsKey(userDto.getKey())) return HttpStatus.NOT_FOUND;
repository.put(userDto.getKey(), userDto);
return HttpStatus.OK;
}
@GetMapping(value = "{key}", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<UserDto> getSimpleDto(@PathVariable("key") String key) {
return ResponseEntity.ok(repository.get(key));
}
}
PointController
: Управляет баллами пользователей. В этом контроллере реализован метод, который добавляет или удаляет баллы у пользователей.
PointContoller.java
@RestController
@RequestMapping("api/user/point")
public class PointController {
private final Map<String, UserDto> repository;
public PointController(Map<String, UserDto> repository) {
this.repository = repository;
}
@PostMapping("{key}")
public HttpStatus changePoints(
@PathVariable String key,
@RequestParam("point") Long point,
@RequestParam("type") String type
) {
final UserDto userDto = repository.get(key);
userDto.setPoints(
"plus".equalsIgnoreCase(type)
? userDto.getPoints() + point
: userDto.getPoints() - point
);
return HttpStatus.OK;
}
}
SecretController
: Содержит метод destroy
, который может удалить всех пользователей из системы.
SecretContoller.java
@RestController
@RequestMapping("api/secret")
public class SecretController {
private final Map<String, UserDto> repository;
public SecretController(Map<String, UserDto> repository) {
this.repository = repository;
}
@GetMapping(value = "destroy")
public HttpStatus destroy() {
repository.clear();
return HttpStatus.OK;
}
}
Настраиваем Swagger
Для добавления Swagger в проект необходимо добавить соответствующие зависимости в pom.xml
:
Если используется WebFlux, потребуется другая зависимость:
Эти зависимости предназначены для проектов на Spring Boot 3. Если ваш проект работает на Spring Boot 2, добавьте следующие зависимости:
После добавления зависимостей документация будет доступна по адресу: http://localhost:8080/swagger-ui/index.html, а спецификация OAS — по адресу: http://localhost:8080/v3/api-docs.
Swagger автоматически обнаруживает все контроллеры, отображая доступные HTTP-методы для каждого. Для каждого HTTP-метода доступна информация о статусе ответа, типе содержимого и параметрах.
Swagger также позволяет вызывать методы API через пользовательский интерфейс. Можно вызвать каждый метод с помощью пользовательского интерфейса.
Однако, по умолчанию, документация может выглядеть недостаточно информативно. Чтобы улучшить её, создадим конфигурационный класс Swagger — OpenApiConfig
:
@OpenAPIDefinition(
info = @Info(
title = "Loyalty System Api",
description = "API системы лояльности",
version = "1.0.0",
contact = @Contact(
name = "Struchkov Mark",
email = "mark@struchkov.dev",
url = "https://mark.struchkov.dev"
)
)
)
public class OpenApiConfig {
// Конфигурация для Swagger
}
title
— название приложения;version
— версия API;contact
— информация об ответственных за API.
Эти данные добавляют визуальную привлекательность пользовательскому интерфейсу документации.
Разметка контроллеров
Чтобы сделать документацию понятнее, переопределим описания контроллеров, используя аннотацию @Tag
:
@Tag(name = "Название контроллера", description = "Описание контроллера")
public class ControllerName {
// Контент контроллера
}
Кроме того, @Tag
поддерживает параметр externalDocs
, который позволяет добавить ссылку на внешнюю документацию, связанную с контроллером. Это может быть полезным, если API требует ссылки на общие технические документы, дополнительные спецификации или инструкции. Пример:
@Tag(
name = "User Controller",
description = "Контроллер для управления пользователями",
externalDocs = @ExternalDocumentation(
description = "Ссылка на общую документацию",
url = "https://example.com/docs/user-controller"
)
)
public class UserController {
// ... Контент контроллера ...
}
Скрытие контроллера
Если нужно скрыть контроллер из документации Swagger, можно воспользоваться аннотацией @Hidden
. Например, спрячем SecretController
:
@Hidden
@Tag(name = "Секретный контролер", description = "Позволяет удалить всех пользователей")
public class SecretController {
// Контент контроллера
}
@Hidden
скрывает элементы только из интерфейса Swagger, но не делает их недоступными для вызова. Для обеспечения безопасности используйте методы аутентификации и авторизации, такие как JWT-токены, OAuth2 или API-ключи, которые обеспечат доступ только для авторизованных пользователей.@Hidden
позволяет скрыть из Swagger не только контроллеры целиком, но также отдельные эндпойнты.
@Hidden
@PutMapping(produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Регистрация пользователя", description = "Позволяет зарегистрировать пользователя")
public HttpStatus registerUser(@RequestBody UserDto userDto) {
userDto.setPoints(0L);
repository.put(userDto.getKey(), userDto);
return HttpStatus.OK;
}
После добавления аннотаций документация становится гораздо понятнее. Теперь добавим описания для каждого метода контроллера.
Разметка методов
Для описания методов контроллеров используется аннотация @Operation
. Она позволяет указать:
summary
— краткое описание метода.description
— более развернутое описание.
Пример аннотации метода для регистрации пользователя:
@Operation(
summary = "Регистрация пользователя",
description = "Позволяет зарегистрировать пользователя"
)
public HttpStatus registerUser(@RequestBody UserDto userDto) {
// Реализация метода
}
Разметка параметров метода
Для описания параметров эндпойнтов используем аннотацию @Parameter
, которая позволяет уточнить детали каждого параметра.
public HttpStatus changePoints(
@PathVariable @Parameter(description = "Идентификатор пользователя", required = true) String key,
@RequestParam("point") @Parameter(description = "Количество баллов", example = "100") Long point,
@RequestParam("type") @Parameter(description = "Тип операции", example = "ADD") TypeOperation type
) {
// Реализация метода
}
По умолчанию параметры считаются необязательными. С помощью параметра required
в аннотации @Parameter
можно указать обязательные поля для запроса.
Для более точного описания значений параметров можно использовать атрибут example
. Этот атрибут помогает задать пример значения. Например, в этом коде example = "100"
указывает пример значения для параметра point
, а example = "PLUS"
— для параметра type
, что наглядно демонстрирует возможные значения, которые API ожидает получить.
Разметка DTO
Чтобы сделать документацию ещё более понятной, можно добавить человеко-понятные описания для самой DTO и её полей, используя аннотацию @Schema
. Даже если переменные в классе названы понятно, дополнительные пояснения помогут пользователям API быстрее ориентироваться в данных.
Пример базовой разметки для UserDto
выглядит так:
@Schema(description = "Сущность пользователя")
public class UserDto {
@Schema(description = "Уникальный идентификатор пользователя", example = "A-124523", accessMode = Schema.AccessMode.READ_ONLY)
private String key;
@Schema(description = "ФИО", example = "Иванов Иван Иванович")
private String name;
@Schema(description = "Баллы пользователя", example = "0", accessMode = Schema.AccessMode.READ_ONLY)
private Long points = 0L;
@Schema(description = "Дата регистрации пользователя", example = "2023-01-01", accessMode = Schema.AccessMode.READ_ONLY)
private LocalDate registrationDate;
@Schema(description = "Статус пользователя", allowableValues = {"ACTIVE", "INACTIVE", "BANNED"})
private String status;
// Вложенная структура для контактных данных
@Schema(description = "Контактные данные пользователя")
private ContactInfo contactInfo;
// ... другие поля ...
}
Swagger автоматически определяет формат для стандартных типов данных, таких как enum и даты. Однако, для полей со специфичным форматом желательно добавлять примеры значений через параметр example
. Например, в данном коде example = "A-124523"
показывает, как может выглядеть key
, а example = "0"
— формат для поля points
. Выглядеть это будет так:
Если в DTO есть поля, которые пользователю вводить не нужно , их можно скрыть из интерфейса ввода в Swagger с помощью accessMode = Schema.AccessMode.READ_ONLY
. Это обозначает, что поле будет отображаться только для чтения и Swagger не будет запрашивать его при отправке запроса.
Для полей, имеющих ограниченные значения, например, status
, можно использовать параметр allowableValues
, который укажет пользователям API допустимые значения. Для enum allowableValues
Swager сформирует сам.
Кроме того, если DTO содержит вложенные объекты (например, ContactInfo
в UserDto
), желательно также разметить вложенные классы с помощью @Schema
, чтобы Swagger отобразил их структуру и описание всех уровней вложенности.
Валидация
О валидации данных я подробно рассказывал в статье “Валидация данных в Spring Boot”. Здесь же хочу показать, что при добавлении валидации для параметров методов контроллеров эти ограничения также автоматически отображаются в Swagger.
Добавим валидацию в метод управления баллами пользователя в PointController
. Например, нам нужно, чтобы значение баллов не могло быть отрицательным. Для этого используем аннотацию @Min
с минимальным значением 0:
public HttpStatus changePoints(
// ... другие параметры ...
@RequestParam("point") @Min(0) @Parameter(description = "Количество баллов") Long point
// ... другие параметры ...
) {
// Реализация метода
}
Теперь, благодаря валидации, Swagger автоматически добавляет в спецификацию для поля point
ограничение minimum: 0
. Пользователи API сразу видят требование к этому параметру.
Можно также указать коды ошибок, которые возвращаются при нарушении правил валидации. Например, добавим описание ответа с кодом 400 (ошибка ввода) с помощью аннотации @ApiResponse
:
@Operation(summary = "Изменение баллов пользователя")
@ApiResponse(responseCode = "400", description = "Неверный запрос — количество баллов должно быть неотрицательным")
public HttpStatus changePoints(
@RequestParam("point") @Min(value = 0, message = "Количество баллов не может быть отрицательным")
@Parameter(description = "Количество баллов") Long point
) {
// Реализация метода
}
Авторизация в Swager
Если ваш API защищён аутентификацией и авторизацией, вы не сможете просто так выполнять запросы из Swagger. Один из самых распространённых способов авторизации — это JWT, о котором я рассказывал в отдельной статье.
Чтобы Swagger мог корректно отправлять запросы к защищённым эндпойнтам, необходимо указать, что API использует JWT для авторизации, и задать, какие методы требуют токена.
Авторизация с использованием JWT
Для авторизации через JWT Swagger должен получать access-токен и автоматически добавлять его в заголовок Authorization
каждого запроса. Начнём с описания схемы авторизации в конфигурационном классе Swagger
, добавив аннотацию @SecurityScheme
над классом OpenApiConfig
:
@OpenAPIDefinition(...)
@SecurityScheme(
name = "JWT",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
public class OpenApiConfig {
}
Здесь мы определили схему авторизации с использованием JWT, указав, что токен имеет формат bearer
и будет использоваться в заголовке запроса. Теперь Swagger знает, что для авторизации потребуется токен JWT.
После этого помечаем аннотацией @SecurityRequirement
все эндпойнты, которые требуют авторизации. В атрибуте name
аннотации @SecurityRequirement
указываем название схемы авторизации — в данном случае "JWT", что должно соответствовать значению, указанному в аннотации @SecurityScheme
:
Запустив приложение и открыв Swagger, мы увидим, что в интерфейсе появилась кнопка Authorize. Нажав на неё, можно ввести JWT-токен, который будет автоматически добавляться ко всем защищённым методам. Методы, защищённые аннотацией @SecurityRequirement
, теперь будут отмечены значком замка.
После установки токена, перейдите к защищённому эндпойнту и вызовите его. Swagger добавит заголовок Authorization
со значением токена и автоматически проставит перед ним Bearer
, как того требует спецификация JWT.
Если токен истёк или стал недействителен, его необходимо ввести заново через кнопку Authorize. Swagger не поддерживает автоматическое обновление токенов, поэтому при использовании токенов с ограниченным сроком действия разработчикам потребуется самостоятельно получать и вводить новый токен.
Авторизация с использованием Ouath2
Работа с OAuth2 в Swagger несколько сложнее, чем с JWT, из-за особенностей авторизации в Spring Boot. При OAuth2-авторизации Spring Boot создаёт JSESSIONID
— специальную куку, которая сохраняется в браузере и передаётся с каждым запросом. Это позволяет приложению идентифицировать пользователя.
Swagger при авторизации через OAuth2 генерирует access token
, который он затем использует для запросов. Однако он не может сам получить JSESSIONID
, так как его создаёт Spring Boot после успешной авторизации, и передаётся кука исключительно через браузер.
Swagger же при Oauth2 авторизации генерирует себе access token, который пытается использовать при запросе. Проблема в том, что он никак не может сам получить значение JSESSIONID
, так как его генерирует Spring после успешной Oauth2 авторизации.
Чтобы использовать JSESSIONID
для авторизации, укажем Swagger, что он должен передавать его в виде куки. Добавим аннотацию @SecurityScheme
в конфигурационный класс OpenApiConfig
, настроив её для передачи JSESSIONID
в запросах:
@OpenAPIDefinition(...)
@SecurityScheme(
name = "jsessionid",
in = SecuritySchemeIn.COOKIE,
type = SecuritySchemeType.APIKEY,
paramName = "JSESSIONID"
)
public class OpenApiConfig {
}
В этом примере мы указываем, что схема безопасности jsessionid
использует куку JSESSIONID
для передачи токена. Теперь Swagger знает, что для авторизации будет использоваться именно эта кука.
Затем помечаем защищённые эндпойнты аннотацией @SecurityRequirement
, указав в атрибуте name название схемы — jsessionid
:
@SecurityRequirement(name = "jsessionid")
Для прохождения авторизации переходим на любой URL вашего API, требующий авторизации, и видим окно авторизации OAuth2. После успешного входа JSESSIONID
будет сохранён в браузере.
Откройте консоль разработчика в браузере: Найдите раздел с куками и отыщите куку JSESSIONID
.
Скопируйте значение JSESSIONID
: Откройте окно авторизации в Swagger и вставьте значение JSESSIONID
в соответствующее поле.
Теперь, когда JSESSIONID
установлен, Swagger будет передавать его в каждом запросе к защищённым эндпойнтам, обеспечивая корректную аутентификацию.
Заключение
В этой статье мы рассмотрели, как настроить и документировать API в Spring Boot, используя Swagger для упрощённого взаимодействия с разработчиками и тестирования защищённых эндпойнтов. Мы начали с основ настройки Swagger и аннотаций для документирования контроллеров, DTO и параметров методов. Затем добавили валидацию данных, которая не только усиливает защиту приложения, но и помогает пользователям API понимать ограничения при работе с запросами.