Я реализую в своем приложении проверку действительности токена
OAuth
. Сначала я прочитал документацию по нужному мне для этого
запросу и проверил работу в
cmd
с помощью
cURL
:
curl -X POST https://CLIENT_ID:CLIENT_SECRET@api.github.com/applications/CLIENT_ID/token -d {\"access_token\":\"ACCESS_TOKEN\"}
данный запрос работает корректно, сервер отвечает 200 кодом.
Однако тот же запрос, с тем же токеном доступа, только реализованный с помощью
retrofit
, выдает мне следующую ошибку:
{"message":"Not Found","documentation_url":"https://docs.github.com/rest"}
Но я читал логи от
HttpLoggingInterceptor
и не увидел там какой-то ошибки, запрос выглядит аналогично тому, что я писал в
cmd
. Я пробовал делать запрос через
cURL
с неправильным токеном доступа и в этом случае сервер мне отвечал таким образом:
{
"message": "Not Found",
"documentation_url": "https://docs.github.com/rest/reference/apps#check-a-token"
}
Я понимаю, что можно сделать проверку токена на валидность иным способом, просто выполнив какой-нибудь запрос к серверу с хедером
"Authorization: token <TOKEN>"
, но это, как мне кажется, не лучший вариант, т.к. в этом случае уменьшается кол-во доступных запросов в час, да и для проверки токена все-таки есть отдельная конечная точка (обращение к которой не уменьшает кол-во доступных запросов).
Вот какой код у меня получился.
Сущность для POST запроса:
data class AccessToken(
@SerializedName("access_token")
val value: String,
)
Интерфейсы GitHub API:
interface GitHubApi {
companion object {
const val DEFAULT_REQUEST_LIMIT = 60
const val AUTHORIZED_USER_REQUEST_LIMIT = 5000
}
}
interface TokenValidityApi : GitHubApi {
@POST("applications/$CLIENT_ID_PATH_SEGMENT/token")
suspend fun checkAccessTokenValidity(
@Body accessToken: AccessToken,
): Response<AccessToken>
companion object {
const val CLIENT_ID_PATH_SEGMENT = "{client_id}"
}
}
API providers:
abstract class GitHubApiProvider {
abstract val api: GitHubApi
protected abstract val acceptHeaderValue: String
protected abstract val baseUrl: String
protected abstract val clientBuilder: OkHttpClient.Builder
protected val clientId = ClientId(value = CLIENT_ID)
protected val clientSecret = ClientSecret(value = CLIENT_SECRET)
protected val retrofit: Retrofit
get() {
val client = clientBuilder
.addInterceptor(headerInterceptor)
.addInterceptor(loggingInterceptor)
.build()
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
}
private val headerInterceptor: Interceptor
get() = Interceptor { chain ->
val acceptHeader = Accept(acceptHeaderValue)
val newRequest = chain.request().newBuilder()
.addHeader(acceptHeader.name, acceptHeader.value)
.build()
return@Interceptor chain.proceed(newRequest)
}
private val loggingInterceptor: Interceptor
get() = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
}
object TokenValidityApiProvider : GitHubApiProvider() {
override val acceptHeaderValue = "application/vnd.github+json"
override val baseUrl = "https://api.github.com/"
override val clientBuilder: OkHttpClient.Builder
get() = OkHttpClient.Builder()
.addInterceptor(UrlInterceptor)
override val api: TokenValidityApi
get() = retrofit.create(TokenValidityApi::class.java)
private val UrlInterceptor: Interceptor
get() = Interceptor { chain ->
val originalRequest = chain.request()
val originalUrl = originalRequest.url
val newUrl = originalUrl.newBuilder()
.setPathSegment(
index = originalUrl.pathSegments.indexOf(TokenValidityApi.CLIENT_ID_PATH_SEGMENT),
pathSegment = clientId.value,
)
.username(clientId.value)
.password(clientSecret.value)
.build()
val newRequest = originalRequest.newBuilder()
.url(newUrl)
.build()
return@Interceptor chain.proceed(newRequest)
}
}
Мой метод проверки на наличие и валидность токена:
suspend fun isAccessTokenAvailableAndValid(): Boolean {
dataStore.getAccessTokenSync()?.let {
Log.d(TAG, "access_token = $it")
val accessToken = AccessToken(value = it)
val response = tokenValidityApi.checkAccessTokenValidity(accessToken)
return response.isSuccessful
}
return false
}
Всякие вспомогательные классы, которые есть в коде выше:
open class Query(
val name: String,
val value: String,
) {
override fun toString(): String {
return "$name=$value"
}
}
class ClientId(value: String) : Query("client_id", value)
class ClientSecret(value: String) : Query("client_secret", value)
open class Header(
val name: String,
val value: String,
) {
override fun toString(): String {
return "$name: $value"
}
}
class Accept(value: String) : Header("Accept", value)
Может я что-то делаю не так?