Dan*_*nai 141 android oauth-2.0 retrofit
我们在Android应用中使用Retrofit与OAuth2安全服务器进行通信.一切都很好,我们使用RequestInterceptor在每次调用时包含访问令牌.但是,有时候,访问令牌将过期,并且需要刷新令牌.当令牌过期时,下一个调用将返回一个未授权的HTTP代码,因此很容易监控.我们可以通过以下方式修改每个Retrofit调用:在失败回调中,检查错误代码,如果它等于Unauthorized,则刷新OAuth令牌,然后重复Retrofit调用.但是,为此,应修改所有呼叫,这不是一个易于维护的好解决方案.有没有办法在不修改所有Retrofit调用的情况下执行此操作?
lgv*_*lle 196
请不要Interceptors
用于处理身份验证.
目前,处理身份验证的最佳方法是使用Authenticator
专门为此目的而设计的新API .
OkHttp会自动询问了Authenticator
当响应凭据401 Not Authorised
重试最后一次失败的请求与他们.
public class TokenAuthenticator implements Authenticator {
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
@Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
Run Code Online (Sandbox Code Playgroud)
Authenticator
以OkHttpClient
与您相同的方式附加Interceptors
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Run Code Online (Sandbox Code Playgroud)
创建时使用此客户端 Retrofit
RestAdapter
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setClient(new OkClient(okHttpClient))
.build();
return restAdapter.create(API.class);
Run Code Online (Sandbox Code Playgroud)
the*_*ang 63
如果您正在使用Retrofit > = 1.9.0
那么您可以使用OkHttp的新Interceptor,它是在OkHttp 2.2.0
.您可能希望使用允许您使用的Application Interceptorretry and make multiple calls
.
你的拦截器可能看起来像这个伪代码:
public class CustomInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
if (response shows expired token) {
// get a new token (I use a synchronous Retrofit call)
// create a new request and modify it accordingly using the new token
Request newRequest = request.newBuilder()...build();
// retry the request
return chain.proceed(newRequest);
}
// otherwise just pass the original response on
return response;
}
}
Run Code Online (Sandbox Code Playgroud)
定义之后Interceptor
,创建一个OkHttpClient
并将拦截器添加为Application Interceptor.
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new CustomInterceptor());
Run Code Online (Sandbox Code Playgroud)
最后,OkHttpClient
在创建你的时候使用它RestAdapter
.
RestService restService = new RestAdapter().Builder
...
.setClient(new OkClient(okHttpClient))
.create(RestService.class);
Run Code Online (Sandbox Code Playgroud)
警告:正如Jesse Wilson
(来自Square)在这里提到的,这是一种危险的力量.
话虽如此,我绝对认为这是现在处理这类事情的最好方法.如果您有任何疑问,请随时在评论中提问.
Dav*_*son 21
TokenAuthenticator依赖于服务类.服务类依赖于OkHttpClient实例.要创建OkHttpClient,我需要TokenAuthenticator.我怎样才能打破这个循环?两个不同的OkHttpClients?他们将有不同的连接池..
如果你有一个TokenService
你需要的改造,Authenticator
但你只想设置一个,OkHttpClient
你可以使用一个TokenServiceHolder
作为依赖TokenAuthenticator
.您必须在应用程序(单例)级别维护对它的引用.如果您使用Dagger 2,这很容易,否则只需在您的应用程序中创建类字段.
在 TokenAuthenticator.java
public class TokenAuthenticator implements Authenticator {
private final TokenServiceHolder tokenServiceHolder;
public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) {
this.tokenServiceHolder = tokenServiceHolder;
}
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
//is there a TokenService?
TokenService service = tokenServiceHolder.get();
if (service == null) {
//there is no way to answer the challenge
//so return null according to Retrofit's convention
return null;
}
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken().execute();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
@Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
Run Code Online (Sandbox Code Playgroud)
在TokenServiceHolder.java
:
public class TokenServiceHolder {
TokenService tokenService = null;
@Nullable
public TokenService get() {
return tokenService;
}
public void set(TokenService tokenService) {
this.tokenService = tokenService;
}
}
Run Code Online (Sandbox Code Playgroud)
客户端设置:
//obtain instance of TokenServiceHolder from application or singleton-scoped component, then
TokenAuthenticator authenticator = new TokenAuthenticator(tokenServiceHolder);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(tokenAuthenticator);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(okHttpClient)
.build();
TokenService tokenService = retrofit.create(TokenService.class);
tokenServiceHolder.set(tokenService);
Run Code Online (Sandbox Code Playgroud)
如果您正在使用Dagger 2或类似的依赖注入框架,则在此问题的答案中有一些示例
Pha*_*inh 10
使用TokenAuthenticator
like @theblang answer 是 handle 的正确方法refresh_token
。
这是我的实现(我使用了 Kotlin、Dagger、RX,但您可以使用这个想法来实现您的案例)
TokenAuthenticator
class TokenAuthenticator @Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {
override fun authenticate(route: Route, response: Response): Request? {
val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
return response.request().newBuilder()
.header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
.build()
}
}
Run Code Online (Sandbox Code Playgroud)
为了防止像@Brais Gabin 评论这样的依赖循环,我创建了2 个界面,例如
interface PotoNoneAuthApi { // NONE authentication API
@POST("/login")
fun login(@Body request: LoginRequest): Single<AccessToken>
@POST("refresh_token")
@FormUrlEncoded
fun refreshToken(@Field("refresh_token") refreshToken: String): Single<AccessToken>
}
Run Code Online (Sandbox Code Playgroud)
和
interface PotoAuthApi { // Authentication API
@GET("api/images")
fun getImage(): Single<GetImageResponse>
}
Run Code Online (Sandbox Code Playgroud)
AccessTokenWrapper
班级
class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
private var accessToken: AccessToken? = null
// get accessToken from cache or from SharePreference
fun getAccessToken(): AccessToken? {
if (accessToken == null) {
accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
}
return accessToken
}
// save accessToken to SharePreference
fun saveAccessToken(accessToken: AccessToken) {
this.accessToken = accessToken
sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
}
}
Run Code Online (Sandbox Code Playgroud)
AccessToken
班级
data class AccessToken(
@Expose
var token: String,
@Expose
var refreshToken: String)
Run Code Online (Sandbox Code Playgroud)
我的拦截器
class AuthInterceptor @Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val authorisedRequestBuilder = originalRequest.newBuilder()
.addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
.header("Accept", "application/json")
return chain.proceed(authorisedRequestBuilder.build())
}
}
Run Code Online (Sandbox Code Playgroud)
最后,添加Interceptor
和Authenticator
您OKHttpClient
创建服务时PotoAuthApi
https://github.com/PhanVanLinh/AndroidMVPKotlin
getImage()
返回 401 错误代码authenticate
里面的方法TokenAuthenticator
会被触发noneAuthAPI.refreshToken(...)
调用noneAuthAPI.refreshToken(...)
响应- >新的令牌会增加头getImage()
将使用新标头自动调用(HttpLogging
不会记录此调用)(intercept
内部AuthInterceptor
不会调用)如果getImage()
仍然失败并出现错误 401,则authenticate
内部方法TokenAuthenticator
将再次触发,然后将多次抛出有关调用方法的错误(java.net.ProtocolException: Too many follow-up requests
)。您可以通过count response来防止它。例如,如果您return null
在authenticate
3 次重试后,getImage()
将完成并return response 401
如果getImage()
响应成功 => 我们将正常产生结果(就像您调用getImage()
时没有错误一样)
希望有帮助
归档时间: |
|
查看次数: |
55274 次 |
最近记录: |