如何在拦截器 android 改造时检查令牌过期?

How to check token expiration at interceptors android retrofit?

我想自己处理令牌过期并发送新令牌请求。我有这样的条件:

sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60

此条件检查我的令牌何时过期并且我必须从拦截器发送新请求。我也看到了 问题。我创建了这样的拦截器:

class RefreshTokens(cont: Context) : Interceptor{
    val context = cont
    override fun intercept(chain: Interceptor.Chain): Response {
        var tokenIsUpToDate = false
        val sp = context.getSharedPreferences("app_data", 0)
        if (sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60) {
            Singleton.apiService(context).getNewToken(ReqAccessToken(context.getSharedPreferences("app_data", 0).getString("refresh_token", ""))).enqueue(object : Callback<ResNewTokens>, retrofit2.Callback<ResNewTokens> {
                override fun onResponse(call: Call<ResNewTokens>, response: retrofit2.Response<ResNewTokens>) {
                    if (response.isSuccessful) {
                        tokenIsUpToDate = true
                    }
                }

                override fun onFailure(call: Call<ResNewTokens>, t: Throwable) {

                }

            })

            return if (tokenIsUpToDate) {
                chain.proceed(chain.request())
            } else {
                chain.proceed(chain.request())
            }

        } else {
            val response = chain.proceed(chain.request())
            when (response.code) {
                401->{
                    chain.request().url
                    response.request.newBuilder()
                            .header("Authorization", "Bearer " + context.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
                            .build()
                }
                500 -> {
                    Toast.makeText(context, context.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
                }
            }
            return response
        }
    }
}

我无法想象如何将 return 条件添加到我的代码中。我知道 Authentificator 但当我使用它时,我又发送了一个请求,该响应给我 401 令牌更新错误。当我使用 Authentificator 我发送这样的请求:

  1. 旧请求 access_token -> 401 错误
  2. 请求新令牌 -> 200 OK
  3. 新请求 access_token -> 200 OK

所以我想删除 1 个会出错的请求并发送新令牌请求。但是我有问题:

  1. 我不知道如何修复我的拦截器来解决这个任务
  2. 我不知道如何像在 Authentificator 中一样重复我要发出的请求

也许有人知道如何解决我的问题?

是的,太简单了,不要太难了,我也有同样的问题,但我是这样解决的

所以当令牌被扩展时,Retrofit 给出

错误代码 = 401

所以你需要使用 sharedPrefuserEmail 或者 userName 保存用户数据以及 userPassword 所以

当用户收到 token exipre 消息或错误 code 401 时,您需要调用一个方法来再次登录用户,以使用 向用户显示任何内容useremailuserpassword,然后生成一个新的令牌,然后将生成的令牌发送到服务器,在这种情况下它将起作用

希望对你有所帮助

我想分享我自己的解决方案,我认为效果很好:

class AuthToken(context: Context) : Interceptor {
    var cont = context
    override fun intercept(chain: Interceptor.Chain): Response {
        val sp = cont.getSharedPreferences("app_data", 0)
        if (sp!!.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60 && !sp.getString("refresh_token", "")!!.isBlank()) updateAccessToken(cont)

        val initialRequest = if (sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60 && !sp.getString("refresh_token", "")!!.isBlank()) {
            updateAccessToken(cont)
            requestBuilder(chain)
        } else {
            requestBuilder(chain)
        }


        val initialResponse = chain.proceed(initialRequest)

        return if (initialResponse.code == 401 && !sp.getString("refresh_token", "").isNullOrBlank() && sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60) {
            updateAccessToken(cont)
            initialResponse.close()
            val authorizedRequest = initialRequest
                    .newBuilder()
                    .addHeader("Content-type:", "application/json")
                    .addHeader("Authorization", "Bearer " + cont.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
                    .build()
            chain.proceed(authorizedRequest)
        } else {
            val errorBody = initialResponse.message
            when {

            }
            if (initialResponse.code == 500) {
                val thread = object : Thread() {
                    override fun run() {
                        Looper.prepare()
                        Toast.makeText(cont, cont.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
                        Looper.loop()
                    }
                }
                thread.start()
            }
            initialResponse
        }
    }


    private fun updateAccessToken(context: Context) {
        val sp = context.getSharedPreferences("app_data", 0)
        synchronized(this) {
            val tokensCall = accessTokenApi()
                    .getNewToken(ReqAccessToken(context.getSharedPreferences("app_data", 0).getString("refresh_token", "")!!))
                    .execute()

            if (tokensCall.isSuccessful) {
                val responseBody = tokensCall.body()
                val editor = sp.edit()

                val localTime = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH).parse(tokensCall.headers()["Date"]!!)
                Singleton.setServerTime(localTime!!.time / 1000, context)

                editor.putString("access_token", Objects.requireNonNull<ResNewTokens>(responseBody).access_token).apply()
                editor.putString("refresh_token", Objects.requireNonNull<ResNewTokens>(responseBody).refresh_token).apply()
                editor.putLong("expires_in", responseBody!!.expires_in!!).apply()
            } else {
                when (tokensCall.code()) {
                    500 -> {
                        val thread = object : Thread() {
                            override fun run() {
                                Looper.prepare()
                                Toast.makeText(cont, cont.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
                                Looper.loop()
                            }
                        }
                        thread.start()
                    }

                    401 -> {
                        Singleton.logOut(context)
                    }
                }
            }

        }
    }


    private fun requestBuilder(chain: Interceptor.Chain): Request {
        return chain.request()
                .newBuilder()
                .header("Content-type:", "application/json")
                .header("Authorization", "Bearer " + cont.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
                .build()
    }

    private fun accessTokenApi(): APIService {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY

        val dispatcher = Dispatcher()
        dispatcher.maxRequests = 1


        val client = OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .connectTimeout(100, TimeUnit.SECONDS)
                .dispatcher(dispatcher)
                .readTimeout(100, TimeUnit.SECONDS).build()


        client.dispatcher.cancelAll()

        val retrofit = Retrofit.Builder()
                .baseUrl(BuildConfig.API_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .build()

        return retrofit.create(APIService::class.java)
    }
}

一般来说,我看到我在发送过期 access_token 的请求之前发送令牌刷新请求。也许有人会对我的解决方案提出一些建议或改进:)