> ## Documentation Index
> Fetch the complete documentation index at: https://veriqa.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Быстрый старт Java

> Подключение Java-приложения к Veriqa по стандартному OpenID Connect — облако, Docker или служба ОС.

Для не-.NET стеков Veriqa — обычный OpenID Connect провайдер. Пакета Veriqa для Java нет и не
требуется: вы подключаете стандартный OIDC-клиент, а всю специфику (страница входа, QR,
подтверждение в мессенджере) Veriqa выполняет на своей стороне.

<Note>
  **Один код, любое развёртывание.** Приложению не важно, **где** работает Veriqa — в облаке Veriqa,
  в вашем контейнере Docker или службой (systemd либо служба Windows) на вашей машине. Варианты
  различаются **только адресом issuer'а** и тем, где заведён клиент; код приложения ниже одинаков
  для всех, а переключение сводится к одному значению `issuer-uri`.
</Note>

## 1. Подключите стартеры

Основной путь для Java — **Spring Security OAuth2 Client** через стартер Spring Boot. Версию
задавать не нужно: её берёт на себя BOM Spring Boot. Вместе с ним идёт второй стартер: OAuth2-стартер
объявляет `spring-boot-starter`, Spring Security и клиента OAuth2/OIDC — и **никакого веб-сервера**,
так что сам по себе он даёт приложение, которое стартует и завершается, ни разу не обслужив callback:

<CodeGroup>
  ```xml pom.xml theme={null}
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  ```

  ```groovy build.gradle theme={null}
  implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  ```
</CodeGroup>

Вдвоём они дают всё нужное: встроенный Tomcat и Spring MVC, на которых держится контроллер
[шага 5](#5-прочитайте-claims), плюс клиента OAuth2/OIDC, обработку callback и интеграцию со
Spring Security.

## 2. Заведите клиента на стороне Veriqa

<Note>
  **Veriqa Cloud сейчас недоступен** — строки про облако описывают контракт его API. Используйте
  self-hosted; подробности — в [Veriqa Cloud](/docs/ru/cloud/overview).
</Note>

| Режим | Где заводится клиент | Что получаете |
| - | - | - |
| Облако | Раздел **Applications / OIDC clients** консоли. Вместе с проектом уже создано дефолтное приложение — **public-клиент с PKCE, без секрета и без `redirect_uri`**, так что первым делом добавьте свой `redirect_uri`. Адрес `issuer` — на экране Project settings, read-only | `issuer`, `client_id`, `redirect_uri` |
| Self-hosted | Секция `Veriqa:OpenIddict:Clients` в конфигурации сервера — см. [шаг 4 self-hosted quickstart](/docs/ru/quickstart/self-hosted#4-объявите-свой-клиент). Секция та же, как бы сервер ни был поставлен | То же, значения задаёте сами |

**У self-hosted две поставки, и для этой страницы они равнозначны.** Сервер работает в контейнере
Docker — [быстрый старт self-hosted](/docs/ru/quickstart/self-hosted) — либо ставится из архива юнитом
systemd или службой Windows, без Docker и без рантайма .NET на машине —
[установка службой ОС](/docs/ru/quickstart/os-service). Выбор определяет способ поставки сервера, а не
регистрацию клиента, адрес issuer'а и код ниже.

**Секрет не обязателен.** Минимальный рабочий набор — `issuer`, `client_id`, `redirect_uri`:
public-клиент с PKCE, и это дефолт облака. Confidential-клиент нужен, только если вы хотите
аутентифицировать серверное приложение секретом; в облаке секрет выпускается отдельным явным
действием на экране Applications и показывается **один раз**.

<Warning>
  **`redirect_uri` сверяется точным совпадением** — wildcard не поддерживаются. Spring Security по
  умолчанию использует шаблон `{baseUrl}/login/oauth2/code/{registrationId}`, то есть при
  `registrationId` = `veriqa` и приложении на `https://app.example.com` регистрировать в Veriqa
  нужно ровно `https://app.example.com/login/oauth2/code/veriqa`. Это самая частая причина отказа
  на первом входе.

  Для приложения, запущенного как в [шаге 7](#7-контрольная-точка), этот URL —
  `http://127.0.0.1:8080/login/oauth2/code/veriqa`: зарегистрируйте ровно его и открывайте
  приложение в той же форме хоста (`127.0.0.1`, а не `localhost`) — callback-URL собирается из хоста
  входящего запроса, а сверка на стороне Veriqa буквальная.
</Warning>

## 3. Опишите регистрацию клиента

`issuer-uri` включает discovery: Spring сам читает `/.well-known/openid-configuration` и находит
остальные эндпоинты.

<CodeGroup>
  ```yaml application.yml — public-клиент (PKCE) theme={null}
  spring:
    security:
      oauth2:
        client:
          registration:
            veriqa:
              client-id: my-app
              client-authentication-method: none
              authorization-grant-type: authorization_code
              scope: openid, profile, channel
          provider:
            veriqa:
              issuer-uri: https://auth.your-domain.com
  ```

  ```yaml application.yml — confidential-клиент theme={null}
  spring:
    security:
      oauth2:
        client:
          registration:
            veriqa:
              client-id: my-app
              client-secret: ${VERIQA_CLIENT_SECRET}
              authorization-grant-type: authorization_code
              scope: openid, profile, channel
          provider:
            veriqa:
              issuer-uri: https://auth.your-domain.com
  ```
</CodeGroup>

<Note>
  **PKCE включать отдельно не нужно.** Spring Security применяет его автоматически, когда
  `client-authentication-method` равен `none`; никакой дополнительной настройки для public-клиента
  не требуется. Секрет при этом не задаётся вовсе — не пустой строкой, а отсутствием ключа.
</Note>

`issuer-uri` — это адрес, который дал [шаг 2](#2-заведите-клиента-на-стороне-veriqa): замените им
`https://auth.your-domain.com`, а `client-id` — идентификатором заведённого там клиента. Spring
сверяет discovery-документ с этим значением на старте, поэтому забытый плейсхолдер валит запуск, а не
первый вход.

Секреты держите в окружении (`${VERIQA_CLIENT_SECRET}`), а не в закоммиченном `application.yml`.
`${…}` Spring разрешает из окружения процесса, поэтому confidential-варианту нужна переменная,
экспортированная в той оболочке, из которой запускается приложение:

```bash theme={null}
export VERIQA_CLIENT_SECRET="…"    # только для confidential-клиента
```

Public-вариант не читает секрет вовсе: при `client-authentication-method: none` ключа в
`application.yml` нет — именно нет, а не пустой. Не задайте переменную для confidential-варианта —
запуск упадёт на неразрешённом плейсхолдере.

Переключение между вариантами — облако, Docker, служба ОС — правка одного `issuer-uri`.

## 4. Включите вход

Минимальной конфигурации достаточно: `oauth2Login()` поднимает весь поток — редирект на страницу
входа Veriqa, обработку callback и создание сессии.

```java SecurityConfig.java theme={null}
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .oauth2Login(Customizer.withDefaults());
        return http.build();
    }
}
```

Дальше работает Veriqa: пользователь видит страницу входа с QR и списком каналов, подтверждает
вход в мессенджере на телефоне, после чего браузер возвращается на ваш `redirect_uri`.

## 5. Прочитайте claims

Аутентифицированный пользователь доступен как `OidcUser`:

```java ProfileController.java theme={null}
@RestController
public class ProfileController {

    @GetMapping("/")
    public String home(@AuthenticationPrincipal OidcUser user) {
        String subject = user.getSubject();
        Instant confirmedAt = user.getAuthenticatedAt();      // claim auth_time
        String channelType = user.getClaimAsString("channel_type");

        return "Hello, " + user.getFullName() + " (" + subject + ", " + channelType + ")";
    }
}
```

Именно этот маршрут открывает контрольная точка [шага 7](#7-контрольная-точка): он закрыт
`anyRequest().authenticated()`, поэтому первое обращение начинает вход, а ответ после него
показывает, что вход дал.

Базовый набор Veriqa кладёт в токены сама:

| Claim | Значение | Где |
| - | - | - |
| `sub` | Стабильный идентификатор субъекта | оба токена |
| `auth_time` | Момент завершения подтверждения — `getAuthenticatedAt()` | только `id_token` |
| `amr` | Тип канала, которым подтверждён вход (`telegram`, `whatsapp`, `max`, `email`) | оба токена; в `id_token` — массивом строк |
| `name`, `email`, `phone_number`, … | Из resolved identity — по запрошенным scopes | оба токена |
| `picture` | Аватар пользователя — только со scope `avatar`, разрешённым и в `AllowedScopes` ([подробнее](/docs/ru/concepts/oidc-explainer#resolved-identity-и-claims)) | только `access_token` — читать из userinfo |

**Claims канала выдаются только по scope `channel`:**

| Claim | Значение |
| - | - |
| `channel_type` | Тип канала, к которому привязан пользователь |
| `channel_user_id` | Идентификатор пользователя внутри этого канала |

Без запрошенного scope `channel` эти два claim не попадают **ни в один** токен — и, следовательно,
не появляются в ответе userinfo, который зеркалит access\_token. Именно по ним приложение различает
привязанные каналы, поэтому для сценария привязки аккаунта scope обязателен.

<Note>
  `amr` приходит в `id_token` **массивом строк** — так его определяет OIDC Core 1.0 §2, и массив
  там даже при единственном методе аутентификации. Читайте его из ID-токена и берите первый
  элемент:

  ```java theme={null}
  List<String> amr = user.getIdToken().getClaimAsStringList("amr");
  String channel = (amr == null || amr.isEmpty()) ? null : amr.get(0);
  ```

  Тот же метод есть и в access\_token, поэтому он виден в ответе `/connect/userinfo`, который Spring
  вызывает штатно при запрошенном `profile` (как в конфигурации выше) и объединяет с claims
  `id_token`. Но в userinfo значение — строка, а не массив, поэтому `user.getClaimAsString("amr")`
  на объединённом наборе зависит от того, чей claim победил в слиянии; чтение из `getIdToken()`
  такой развилки не имеет.

  Для **привязки канала к аккаунту** этого всё равно недостаточно: `amr` называет тип канала, но не
  идентификатор пользователя в нём. Идентификатор даёт `channel_user_id` под scope `channel`.
</Note>

## 6. Ограничьте вход конкретным каналом

Стандартный OIDC-параметр `acr_values` формата `channel:{тип}` сужает выбор на странице входа. В
Spring Security он добавляется кастомайзером authorization-запроса:

```java SecurityConfig.java theme={null}
// Репозиторий регистраций поднимает сам стартер — его достаточно внедрить в тот же класс
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;

private OAuth2AuthorizationRequestResolver authorizationRequestResolver(
        ClientRegistrationRepository clientRegistrationRepository) {

    DefaultOAuth2AuthorizationRequestResolver resolver =
            new DefaultOAuth2AuthorizationRequestResolver(
                    clientRegistrationRepository, "/oauth2/authorization");

    resolver.setAuthorizationRequestCustomizer(customizer -> customizer
            .additionalParameters(params -> params.put("acr_values", "channel:telegram")));

    return resolver;
}
```

Готовый resolver подключается в цепочке:

```java SecurityConfig.java theme={null}
.oauth2Login(oauth2 -> oauth2
    .authorizationEndpoint(authorization -> authorization
        .authorizationRequestResolver(
            authorizationRequestResolver(this.clientRegistrationRepository))
    )
);
```

Несколько значений через пробел (`channel:telegram channel:whatsapp`) — пользователь выбирает из
перечисленных. Без параметра доступны все включённые на сервере каналы.

## 7. Контрольная точка

Запустите приложение из каталога проекта, в той оболочке, где [шаг 3](#3-опишите-регистрацию-клиента)
экспортировал секрет (только для confidential-варианта):

<CodeGroup>
  ```bash Maven theme={null}
  mvn spring-boot:run
  ```

  ```bash Gradle theme={null}
  ./gradlew bootRun
  ```
</CodeGroup>

Spring Boot поднимает его на `http://127.0.0.1:8080` — том адресе, из которого собран `redirect_uri`,
зарегистрированный в [шаге 2](#2-заведите-клиента-на-стороне-veriqa).

<Steps>
  <Step title="Проверьте discovery">
    `curl https://auth.your-domain.com/.well-known/openid-configuration` — с вашим `issuer-uri`
    вместо хоста — возвращает метаданные с `https`-URL.
  </Step>

  <Step title="Запустите вход">
    Откройте `http://127.0.0.1:8080/` — маршрут закрыт `anyRequest().authenticated()`, поэтому Spring
    отправит браузер на страницу Veriqa с QR и каналами.
  </Step>

  <Step title="Подтвердите на телефоне">
    Подтвердите запрос в доверенном канале; браузер вернётся на
    `http://127.0.0.1:8080/login/oauth2/code/veriqa`, а Spring перенаправит его обратно на `/`.
  </Step>

  <Step title="Проверьте claims">
    `/` отвечает приветствием из [шага 5](#5-прочитайте-claims): `OidcUser` несёт `sub`, а при
    запрошенном scope `channel` на нём доступен и `channel_type`.
  </Step>
</Steps>

## Дальше

<CardGroup cols={2}>
  <Card title="Сценарии интеграции" icon="route" href="/docs/ru/guides/scenarios">
    Привязка канала, повторное подтверждение и подтверждение действия — на уровне HTTP.
  </Card>

  <Card title="Быстрый старт: self-hosted" icon="server" href="/docs/ru/quickstart/self-hosted">
    Развернуть свой issuer в Docker.
  </Card>

  <Card title="Установка службой ОС" icon="server" href="/docs/ru/quickstart/os-service">
    Тот же issuer без Docker — systemd или служба Windows, из архива.
  </Card>

  <Card title="OIDC + Veriqa: разбор" icon="key" href="/docs/ru/concepts/oidc-explainer">
    Где Veriqa встаёт в стандартный поток.
  </Card>

  <Card title="Hardening к продакшну" icon="shield-check" href="/docs/ru/guides/hardening">
    Что проверить перед выпуском.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.