草庐IT

Android Retrofit2 刷新 Oauth 2 token

coder 2023-06-07 原文

我正在使用 RetrofitOkHttp 库。我有一个 Authenticator 在我们收到 401 响应时对用户进行身份验证。

我的build.gradle是这样的:

compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.okhttp3:okhttp:3.1.2'

而我的Authenticator是这样的:

public class CustomAuthanticator  implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {
    
    //refresh access token
    refreshTokenResult=apiService.refreshUserToken(parameters);
    //this is synchronous retrofit request
    RefreshTokenResult refreshResult = refreshTokenResult.execute().body();
    //check if response equals 400, means empty response
    if(refreshResult != null) {
        // save new access and refresh token
        // then create a new request and new access token as header
        return response.request().newBuilder()
                .header("Authorization", newaccesstoken)
                .build();

    } else {
        // we got empty response and we should return null
        // if we don't return null
        // this method will try to make so many requests to get new access token
        return null;
    }
                    
}}

这是我的 APIService 类:

public interface APIService {

@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST("token")
public Call<RefreshTokenResult> refreshUserToken(@Header("Accept") String accept, 
    @Header("Content-Type") String contentType, @Field("grant_type") String grantType,
    @Field("client_id") String clientId, @Field("client_secret") String clientSecret, 
    @Field("refresh_token") String refreshToken);
}

我正在像这样使用 Retrofit:

CustomAuthanticator customAuthanticator=new CustomAuthanticator();
OkHttpClient okClient = new OkHttpClient.Builder()
        .authenticator(customAuthanticator)
        .build();
Retrofit client = new Retrofit.Builder()
        .baseUrl(getResources().getString(R.string.base_api_url))
        .addConverterFactory(GsonConverterFactory.create(gson))
        .client(okClient)
        .build();
    
//then make retrofit request

所以我的问题是:有时我会得到一个新的访问 token 并继续工作。但有时我得到一个 400 响应,这意味着一个空响应。所以我的旧刷新 token 无效,我无法获得新 token 。通常我们的刷新 token 会在 1 年内到期。所以我怎么能做到这一点。请帮帮我!

最佳答案

Disclaimer : Actually I am using Dagger +RxJava + Retrofit but I just wanted to provide an answer to demonstrate logic for future visitors.

Important : If you are making requests from several places your token will refresh multiple times inside TokenAuthenticator class. For example when your activity and your service make requests concurrently. To beat this issue just add synchronized keyword to your TokenAuthenticators authenticate method.

Please make synchronous requests when refreshing your token inside Authenticator because you must block that thread until your request finishes, otherwise your requests will be executed twice with old and new tokens. You can use Schedulers.trampoline() or blockingGet() when refreshing your token to block that thread.

Also inside authenticate method you can check if token is already refreshed by comparing request token with stored token to prevent unnecessary refresh.

And please do not consider using TokenInterceptor because it is edge case and not for everyone, just focus on TokenAuthenticator.

这是我们正在努力实现的目标:

首先,刷新 token 是大多数应用程序的关键过程。 流程是:如果刷新 token 失败,则注销当前用户并要求重新登录。 (可能在注销用户之前重试几次刷新 token )

反正我会一步一步解释的:

第一步:请引用singleton pattern ,我们将创建一个类来负责返回我们的改造实例。因为如果没有可用的实例它是静态的,它只会创建一次实例,当你调用它时总是返回这个静态实例。这也是单例设计模式的基本定义。

public class RetrofitClient {

private static Retrofit retrofit = null;

private RetrofitClient() {
    // private constructor to prevent access
    // only way to access: Retrofit client = RetrofitClient.getInstance();
}

public static Retrofit getInstance() {
    if (retrofit == null) {
        // TokenAuthenticator can be singleton too
        TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();

        // !! This interceptor is not required for everyone !!
        // Main purpose of this interceptor is to reduce server calls

        // Our token needs to be refreshed after 10 hours
        // We open our app after 50 hours and try to make a request.
        // Of course token is expired and we will get a 401 response.
        // So this interceptor checks time and refreshes token beforehand.
        // If this fails and I get 401 then my TokenAuthenticator does its job.
        // if my TokenAuthenticator fails too, basically I just logout the user.
        TokenInterceptor tokenInterceptor = new TokenInterceptor();

        OkHttpClient okClient = new OkHttpClient.Builder()
                .authenticator(tokenAuthenticator)
                .addInterceptor(tokenInterceptor)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(base_api_url)
                .client(okClient)
                .build();
    }
    return retrofit;
  }
}

第 2 步:在我的 TokenAuthenticator 的 authenticate 方法中:

@Override
public synchronized Request authenticate(Route route, Response response) throws IOException {

    boolean refreshResult = refreshToken();
    if (refreshResult) {
    // refresh token is successful, we saved new token to storage.
    // Get your token from storage and set header
    String newaccesstoken = "your new access token";

    // execute failed request again with new access token
    return response.request().newBuilder()
            .header("Authorization", newaccesstoken)
            .build();

    } else {
        // Refresh token failed, you can logout user or retry couple of times
        // Returning null is critical here, it will stop the current request
        // If you do not return null, you will end up in a loop calling refresh
        return null;
    }
}

还有 refreshToken 方法,这只是一个示例,您可以自己创建:

public boolean refreshToken() {
    // you can use RxJava with Retrofit and add blockingGet
    // it is up to you how to refresh your token
    RefreshTokenResult result = retrofit.refreshToken();
    int responseCode = result.getResponseCode();

    if(responseCode == 200) {
        // save new token to sharedpreferences, storage etc.
        return true;
    } else {
        //cannot refresh
        return false;
    } 
}

第 3 步: 想看TokenInterceptor 逻辑的人:

public class TokenInterceptor implements Interceptor {
SharedPreferences prefs;
SharedPreferences.Editor prefsEdit;

@Override
public Response intercept(Chain chain) throws IOException {

    Request newRequest = chain.request();

    // get expire time from shared preferences
    long expireTime = prefs.getLong("expiretime",0);
    Calendar c = Calendar.getInstance();
    Date nowDate = c.getTime();
    c.setTimeInMillis(expireTime);
    Date expireDate = c.getTime();

    int result = nowDate.compareTo(expireDate);
    // when comparing dates -1 means date passed so we need to refresh token
    if(result == -1) {
        //refresh token here , and get new access token
        TokenResponse tokenResponse = refreshToken();

        // Save refreshed token's expire time :
        integer expiresIn = tokenResponse.getExpiresIn();
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND,expiresIn);
        prefsEdit.putLong("expiretime",c.getTimeInMillis());

        String newaccessToken = "new access token";
        newRequest=chain.request().newBuilder()
                .header("Authorization", newaccessToken)
                .build();
    }
    return chain.proceed(newRequest);
  }
}

我在 Activity 和后台服务中提出请求。它们都使用相同的改造实例,我可以轻松管理访问 token 。请引用此答案并尝试创建您自己的客户端。如果您仍有问题,请在下方评论,我会尽力提供帮助。

关于Android Retrofit2 刷新 Oauth 2 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35516626/

有关Android Retrofit2 刷新 Oauth 2 token的更多相关文章

  1. ruby-on-rails - Rails 中的 NoMethodError::MailersController#preview undefined method `activation_token=' for nil:NilClass - 2

    似乎无法为此找到有效的答案。我正在阅读Rails教程的第10章第10.1.2节,但似乎无法使邮件程序预览正常工作。我发现处理错误的所有答案都与教程的不同部分相关,我假设我犯的错误正盯着我的脸。我已经完成并将教程中的代码复制/粘贴到相关文件中,但到目前为止,我还看不出我输入的内容与教程中的内容有什么区别。到目前为止,建议是在函数定义中添加或删除参数user,但这并没有解决问题。触发错误的url是http://localhost:3000/rails/mailers/user_mailer/account_activation.http://localhost:3000/rails/mai

  2. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  3. ruby-on-rails - 使用 HTTP.get_response 检索 Facebook 访问 token 时出现 Rails EOF 错误 - 2

    我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token

  4. ruby-on-rails - 设计通过 reset_password_token 获取用户 - 2

    我正在尝试创建密码规则来设计可恢复的密码更改。我通过passwords_controller.rb做了一个父类(superclass),但我需要在应用规则之前检查用户角色,但我所拥有的只是reset_password_token。 最佳答案 假设您的模型是用户:User.with_reset_password_token(your_token_here)Source 关于ruby-on-rails-设计通过reset_password_token获取用户,我们在StackOverflow

  5. ruby - token 认证 - 2

    简单代码require'net/http'url=URI.parse('getjson/otherdatahere[link]')req=Net::HTTP::Get.new(url.to_s)res=Net::HTTP.start(url.host,url.port){|http|http.request(req)}putsres.body只是想知道如何在phpcURL中放置身份验证token,我是这样做的    curl_setopt($ch,CURLOPT_HTTPHEADER,array('Authorization:Bearerxxx'));//Bearertokenfora

  6. ruby-on-rails - Rails 两条腿的 OAuth 提供商? - 2

    我有一个Rails2.3.5应用程序,其中包含我希望保护的API。没有用户-它是一个应用到应用风格的网络服务(更像是亚马逊服务而不是facebook),所以我想使用两条腿的OAuth方法来实现它。我一直在尝试使用oauth-plugin服务器实现作为开始:http://github.com/pelle/oauth-plugin...但它的构建需要三足(网络重定向流)oauth。在我深入研究对其进行更改以支持两条腿之前,我想看看是否有更简单的方法,或者是否有人有更好的方法让Rails应用程序实现成为两条腿的OAuth提供程序。 最佳答案

  7. ruby-on-rails - 访问授权和访问 token 之间的区别 - 2

    Doorkeeper中Token和Grant的区别我搞不清楚。Doorkeeper在哪个时刻创建访问授权,何时创建访问token?文档似乎对此什么也没说,现在我正在阅读代码,但不是十几行。 最佳答案 我还建议阅读documentationofoauth2据我了解,Doorkeeper也是基于该文档中描述的协议(protocol)。在doorkeeper中,你会先获得accessgrant,然后是accesstoken。访问授权通常只存在很短的时间(doorkeeper中的默认值为10分钟)。您将通过向api-url/oauth/au

  8. ruby - 如何使用 omniauth/oauth 对每秒登录数进行基准测试? ( ruby +rspec) - 2

    我想用一个(自己的)omniauth提供商来衡量每秒可以登录多少次。我需要了解此omniauth/oauth请求的性能如何,以及此身份验证是否具有可扩展性?到目前为止我得到了什么:defperformance_auth(user_count=10)bm=Benchmark.realtimedouser_count.timesdo|n|forkdoclick_on'Logout'omniauth_config_mock(:provider=>"foo",:uid=>n,:email=>"foo#{n}@example.net")visit"/account/auth/foo/"enden

  9. ruby-on-rails - 如何编写 Rails 4 测试以使用 omniauth-google-oauth2 gem 创建 session ? - 2

    我正在尝试为使用omniauth-google-oauth2gem创建session编写测试。我是否需要将env["omniauth.auth"]变量与post:create一起传递?也许当我试图这样做时,我做错了。我得到的错误如下所示...Rake测试错误1)Error:SessionsControllerTest#test_should_get_create:NoMethodError:undefinedmethod`provider'fornil:NilClassapp/models/user.rb:6:in`from_omniauth'app/controllers/sessi

  10. ruby-on-rails - 如何在 ruby​​ on rails 中为 gmail 联系人创建访问 token - 2

    我正在使用Omniauth请求用户gmail凭据,因此我可以稍后请求用户friend/联系人。现在,我正在使用身份验证请求为我生成的访问token,在OmniauthCallbacksController中获取好友列表。像这样classUsers::OmniauthCallbacksController如何使用存储在数据库中的凭据创建新的访问token,以便从不同的Controller调用googleAPI? 最佳答案 从here获取您的client_id和client_secret|.这是一个粗略的脚本,可以很好地工作。根据您的需

随机推荐