Авторизация HTTP-клиента
Провайдер авторизации добавляет application-interceptor OkHttp и, при
необходимости, Authenticator. Режим выбирается ключом:
api.<key>.auth.type=<режим>
Встроенные режимы
| Режим | Обязательные параметры | Поведение |
|---|---|---|
|
нет |
Запрос передаётся без изменений. |
|
|
В |
|
|
В |
|
|
Токен читается из хранилища. После ответа, для которого OkHttp вызвал |
|
|
Используется собственная реализация interceptor/authenticator. |
Общие параметры:
| Ключ | По умолчанию | Назначение |
|---|---|---|
|
|
Имя заголовка для |
|
нет |
Имя пользователя для |
|
нет |
Пароль для |
|
нет |
Статический токен; также доступен собственным провайдерам. |
|
нет |
Дополнительный секрет для собственных провайдеров. |
|
|
Префикс внешнего токена: |
|
|
Хранилище внешнего токена: |
|
|
Срок жизни внешнего токена в хранилище, секунды. |
Значения логинов, паролей и токенов не следует помещать в исходный код, примеры документации или логи.
Запрос без авторизации
@NoAuth отключает встроенную или собственную авторизацию для отдельного метода.
Это нужно, например, когда TokenProvider получает токен через тот же API.
import retrofit2.http.Body
import retrofit2.http.POST
import ru.bgcrm.util.api.tag.NoAuth
interface CatalogApi {
@NoAuth
@POST("api/auth")
fun authenticate(@Body credentials: Credentials): AccessToken
}
TaggingInterceptor переносит @NoAuth в тег OkHttp-запроса. Все встроенные
провайдеры, а также MasterTokenAuthenticationProvider и
XSecretAuthenticationProvider, проверяют этот тег.
BASIC
api.catalog.auth.type=BASIC
api.catalog.auth.header=Authorization
api.catalog.auth.username=<service-user>
api.catalog.auth.password=<service-password>
Параметры проверяются при выполнении запроса, а не при создании клиента. Поэтому
клиент с неполной конфигурацией может успешно создаться и завершиться
BGException только на первом защищённом вызове.
STATIC_TOKEN
api.catalog.auth.type=STATIC_TOKEN
api.catalog.auth.header=Authorization
api.catalog.auth.token=Bearer <service-token>
Провайдер передаёт auth.token как есть. Если сервис ожидает Bearer, префикс
нужно включить в само значение настройки.
EXTERNAL_TOKEN
Интерфейс API связывается с генератором аннотацией @TokenProvider:
import ru.bgcrm.util.api.annotation.Api
import ru.bgcrm.util.api.annotation.TokenProvider
@Api(name = "catalog")
@TokenProvider(providerClass = CatalogTokenProvider::class)
interface CatalogApi
Генератор наследует ru.bgcrm.util.api.auth.provider.token.TokenProvider и обязан
иметь открытый конструктор (Class<*>, ApiClientsConfig.ApiConfig):
class CatalogTokenProvider(
apiClass: Class<*>,
apiConfig: ApiClientsConfig.ApiConfig
) : TokenProvider(apiClass, apiConfig) {
override fun generateToken(): String {
// Получить токен у внешнего сервиса.
return requestToken()
}
}
api.catalog.auth.type=EXTERNAL_TOKEN
api.catalog.auth.header=Authorization
api.catalog.auth.tokenType=BEARER
api.catalog.auth.tokenRepositoryProvider=REDIS
api.catalog.auth.tokenExpireTime=3600
Порядок работы:
-
Перед запросом провайдер ищет токен по ключу
api:<Api.name>:token. -
Найденное значение отправляется как
<tokenType> <token>. -
Если OkHttp вызывает
Authenticatorпосле неуспешной аутентификации, вызываетсяgenerateToken(). -
Новый токен сохраняется и запрос повторяется.
-
После двух повторов с добавленным заголовком дальнейшая аутентификация прекращается.
LOCAL хранит токен в памяти текущей JVM. После перезапуска он исчезает, а в
кластере у каждого экземпляра ERP будет свой токен. REDIS делает токен общим
для экземпляров. Неположительный tokenExpireTime для LOCAL означает хранение
без срока, а для Redis передаётся в команду EXPIRE и не имеет такой семантики.
CUSTOM
Собственный провайдер наследует
ru.bgcrm.util.api.auth.provider.AuthenticationProvider, реализует interceptor и
authenticator и имеет открытый конструктор (Class<*>, ApiClientsConfig.ApiConfig).
class CatalogAuthenticationProvider(
clazz: Class<*>,
apiConfig: ApiClientsConfig.ApiConfig
) : AuthenticationProvider(clazz, apiConfig) {
override fun createInterceptor() = Interceptor { chain ->
val request = chain.request()
if (request.tag(NoAuth::class.java) != null) {
return@Interceptor chain.proceed(request)
}
val token = requireNotNull(apiConfig.authConfig.token)
chain.proceed(
request.newBuilder()
.header("Api-Key", token)
.build()
)
}
override fun createAuthenticator() = createDefaultAuthenticator()
}
@Api(name = "catalog")
@AuthenticationProvider(providerClass = CatalogAuthenticationProvider::class)
interface CatalogApi
api.catalog.auth.type=CUSTOM
api.catalog.auth.token=<service-token>
В ядре есть две готовые реализации для режима CUSTOM:
-
MasterTokenAuthenticationProviderотправляетauth.tokenв фиксированном заголовкеmaster-token; -
XSecretAuthenticationProviderотправляетauth.tokenвauth.header, аauth.secretKey— вX-Secret.
Они не имеют отдельных значений auth.type: интерфейс должен явно ссылаться на
нужный класс через @AuthenticationProvider, а настройка должна содержать
auth.type=CUSTOM.