如何在拦截器 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
我发送这样的请求:
- 旧请求 access_token -> 401 错误
- 请求新令牌 -> 200 OK
- 新请求 access_token -> 200 OK
所以我想删除 1 个会出错的请求并发送新令牌请求。但是我有问题:
- 我不知道如何修复我的拦截器来解决这个任务
- 我不知道如何像在 Authentificator 中一样重复我要发出的请求
也许有人知道如何解决我的问题?
是的,太简单了,不要太难了,我也有同样的问题,但我是这样解决的
所以当令牌被扩展时,Retrofit 给出
错误代码 = 401
所以你需要使用 sharedPref 和 userEmail 或者 userName 保存用户数据以及 userPassword 所以
当用户收到 token exipre 消息或错误 code 401 时,您需要调用一个方法来再次登录用户,以使用 向用户显示任何内容useremail 和 userpassword,然后生成一个新的令牌,然后将生成的令牌发送到服务器,在这种情况下它将起作用
希望对你有所帮助
我想分享我自己的解决方案,我认为效果很好:
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 的请求之前发送令牌刷新请求。也许有人会对我的解决方案提出一些建议或改进:)
我想自己处理令牌过期并发送新令牌请求。我有这样的条件:
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
我发送这样的请求:
- 旧请求 access_token -> 401 错误
- 请求新令牌 -> 200 OK
- 新请求 access_token -> 200 OK
所以我想删除 1 个会出错的请求并发送新令牌请求。但是我有问题:
- 我不知道如何修复我的拦截器来解决这个任务
- 我不知道如何像在 Authentificator 中一样重复我要发出的请求
也许有人知道如何解决我的问题?
是的,太简单了,不要太难了,我也有同样的问题,但我是这样解决的
所以当令牌被扩展时,Retrofit 给出
错误代码 = 401
所以你需要使用 sharedPref 和 userEmail 或者 userName 保存用户数据以及 userPassword 所以
当用户收到 token exipre 消息或错误 code 401 时,您需要调用一个方法来再次登录用户,以使用 向用户显示任何内容useremail 和 userpassword,然后生成一个新的令牌,然后将生成的令牌发送到服务器,在这种情况下它将起作用
希望对你有所帮助
我想分享我自己的解决方案,我认为效果很好:
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 的请求之前发送令牌刷新请求。也许有人会对我的解决方案提出一些建议或改进:)