java 匕首+改造。在运行时添加身份验证标头

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43051558/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 07:02:42  来源:igfitidea点击:

Dagger + Retrofit. Adding auth headers at runtime

javaandroidretrofit2dagger-2dagger

提问by AIntel

I'm wondering if there is a way for Dagger to know that it should recreate an object when new data is available.

我想知道 Dagger 是否有办法知道它应该在新数据可用时重新创建一个对象。

The instance I am speaking of is with the request headers I have for retrofit. At some point (when the user logs in) I get a token that I need to add to the headers of retrofit to make authenticated requests. The issue is, I'm left with the same unauthenticated version of retrofit. Here's my injection code:

我所说的实例是我用于改造的请求标头。在某些时候(当用户登录时)我得到一个令牌,我需要将它添加到改造的标头中以发出经过身份验证的请求。问题是,我留下了相同的未经身份验证的改造版本。这是我的注入代码:

@Provides
    @Singleton
    OkHttpClient provideOkHttpClient(Cache cache) {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .cache(cache).build();
         client
                .newBuilder()
                .addInterceptor(
                    chain -> {
                        Request original = chain.request();
                        Request.Builder requestBuilder = original.newBuilder()
                                .addHeader("Accept", "Application/JSON");
                        Request request = requestBuilder.build();
                        return chain.proceed(request);
                    }).build();
        return client;
    }

  @Provides
    @Singleton
    Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) { 
        Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create())
                .baseUrl(mBaseUrl)
                .client(okHttpClient)
                .build();
        return retrofit;
}

@Provides
    @Singleton
    public NetworkService providesNetworkService(Retrofit retrofit) {
        return retrofit.create(NetworkService.class);
    }

Any ideas on how to make this work?

关于如何使这项工作的任何想法?

采纳答案by azizbekian

Please consider using the approachmentioned by @oldergodas it is the "official"and much better way, whereas the approaches mentioned below are notadvised, they may be considered as workarounds.

请考虑使用@oldergod提到的方法,因为它是“官方”且更好的方法,但建议使用下面提到的方法,它们可能被视为解决方法。



You have a couple of options.

你有几个选择。

  1. As soon as you get the token, you have to null out the component that provided you the Retrofitinstance, create a new component and ask for a new Retrofitinstance, which will be instantiated with necessary okhttpinstance.
  2. A fast and bad one - Save the token in SharedPreferences, create okHttpheader, which will apply token reading from SharedPreferences. If there is none - send no token header.
  3. Even uglier solution - declare a static volatile Stringfield, and do the same thing like in step 2.
  1. 获得令牌后,您必须立即清空为您提供Retrofit实例的组件,创建一个新组件并请求一个新Retrofit实例,该实例将使用必要的okhttp实例进行实例化。
  2. 一个快速而糟糕的方法 - 将令牌保存在 中SharedPreferences,创建okHttp标头,它将应用从SharedPreferences. 如果没有 - 不发送令牌标头。
  3. 更丑陋的解决方案 - 声明一个static volatile String字段,并执行与步骤 2 中相同的操作。

Why the second option is bad? Because on each request you would be polling SD card and fetch data from there.

为什么第二个选项不好?因为在每个请求中,您都会轮询 SD 卡并从那里获取数据。

回答by oldergod

I personally created an okhttp3.Interceptorthat does that for me, which I update once I have the required token. It looks something like:

我个人创建了一个okhttp3.Interceptor为我做这件事的人,一旦我拥有所需的令牌,我就会更新它。它看起来像:

@Singleton
public class MyServiceInterceptor implements Interceptor {
  private String sessionToken;

  @Inject public MyServiceInterceptor() {
  }

  public void setSessionToken(String sessionToken) {
    this.sessionToken = sessionToken;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    Request.Builder requestBuilder = request.newBuilder();

    if (request.header(NO_AUTH_HEADER_KEY) == null) {
      // needs credentials
      if (sessionToken == null) {
        throw new RuntimeException("Session token should be defined for auth apis");
      } else {
        requestBuilder.addHeader("Cookie", sessionToken);
      }
    }

    return chain.proceed(requestBuilder.build());
  }
}

In the corresponding dagger component, I expose this interceptor so I can set the sessionTokenwhen I need to.

在相应的匕首组件中,我公开了这个拦截器,以便我可以设置sessionToken需要的时间。

That is some stuff that Jake talked about it his talk Making Retrofit Work For You.

这就是 Jake 在他的演讲让改造为您工作时谈到的一些内容。

回答by Faraz Ahmed

Based on @oldergod solution kotlin version with different classes and structure

基于@oldergod 解决方案 kotlin 版本,具有不同的类和结构

Make Retrofit instance like this

像这样制作 Retrofit 实例

object RetrofitClientInstance {
   private var retrofit: Retrofit? = null
   private val BASE_URL = "http://yoururl"


    val retrofitInstance: Retrofit?
        get() {
            if (retrofit == null) {
                var client = OkHttpClient.Builder()
                      .addInterceptor(ServiceInterceptor())
                      //.readTimeout(45,TimeUnit.SECONDS)
                      //.writeTimeout(45,TimeUnit.SECONDS)
                        .build()

                retrofit = Retrofit.Builder()
                        .baseUrl(BASE_URL)
                        .client(client)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build()

            }
            return retrofit
      }

}

Add ServiceInterceptorclass like below

添加ServiceInterceptor如下类

class ServiceInterceptor : Interceptor{

  var token : String = "";

  fun Token(token: String ) {
     this.token = token;
  }

  override fun intercept(chain: Interceptor.Chain): Response {
    var request = chain.request()

    if(request.header("No-Authentication")==null){
        //val token = getTokenFromSharedPreference();
        //or use Token Function
        if(!token.isNullOrEmpty())
        {
            val finalToken =  "Bearer "+token
            request = request.newBuilder()
                    .addHeader("Authorization",finalToken)
                    .build()
        }

    }

    return chain.proceed(request)
  }

}

Login Interface and data class implementation

登录接口和数据类实现

interface Login {
  @POST("Login")
  @Headers("No-Authentication: true")
  fun login(@Body value: LoginModel): Call<LoginResponseModel>



  @POST("refreshToken")
  fun refreshToken(refreshToken: String): 
      Call<APIResponse<LoginResponseModel>>
}

data class LoginModel(val Email:String,val Password:String)
data class LoginResponseModel (val token:String,val 
         refreshToken:String)

call this in any activity like this

在任何这样的活动中调用它

val service = RetrofitClientInstance.retrofitInstance?.create(Login::class.java)
val refreshToken = "yourRefreshToken"
val call = service?.refreshToken(refreshToken)
        call?.enqueue(object: Callback<LoginResponseModel>{
            override fun onFailure(call: Call<LoginResponseModel>, t: Throwable) {
                print("throw Message"+t.message)
                Toast.makeText(applicationContext,"Error reading JSON",Toast.LENGTH_LONG).show()
            }

            override fun onResponse(call: Call<LoginResponseModel>, response: Response<LoginResponseModel>) {
                val body = response?.body()
                if(body!=null){
                    //do your work
                }
            }

        })

回答by Naveen Dew

Created custom RequestInterceptor with @Inject constructor

使用 @Inject 构造函数创建自定义 RequestInterceptor

RequestInterceptor

请求拦截器

@Singleton
class
RequestInterceptor @Inject constructor(
    private val preferencesHelper: PreferencesHelper,
) : Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        var newRequest: Request = chain.request()

        newRequest = newRequest.newBuilder()
            .addHeader(
                "AccessToken",
                preferencesHelper.getAccessTokenFromPreference()
            )
            .build()


        Log.d(
            "OkHttp", String.format(
                "--> Sending request %s on %s%n%s",
                newRequest.url(),
                chain.connection(),
                newRequest.headers()
            )
        );
        return chain.proceed(newRequest)

  }

ApplicationModule

应用模块

@Module(includes = [AppUtilityModule::class])
class ApplicationModule(private val application: AppController) {

    @Provides
    @Singleton
    fun provideApplicationContext(): Context = application

    @Singleton
    @Provides
    fun provideSharedPreferences(): SharedPreferences =
        PreferenceManager.getDefaultSharedPreferences(application.applicationContext)

}

PreferencesHelper

首选项助手

@Singleton
class PreferencesHelper
@Inject constructor(
    private val context: Context,
    private val sharedPreferences: SharedPreferences
) {
    private val PREF_KEY_ACCESS_TOKEN = "PREF_KEY_ACCESS_TOKEN"


    fun getAccessTokenFromPreference(): String? {
        return sharedPreferences.getString(PREF_KEY_ACCESS_TOKEN, null)
    }

}

回答by Ankit Dubey

Well tested and working

经过良好测试和工作

public OkHttpClient getHttpClient(Context context) {
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    return  new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .callTimeout(60,TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .addInterceptor(logging)
            .addInterceptor(chain -> {
                Request newRequest = chain.request().newBuilder()
                        .addHeader("Authorization", "Bearer " + Utility.getSharedPreferencesString(context, API.AUTHORIZATION))
                        .build();
                return chain.proceed(newRequest);
            })

            .build();

}

Earlier I was wondering, if session expires and user login again, will this interceptor replace the existing auth, but fortunately it is working fine.

早些时候我想知道,如果会话过期并且用户再次登录,这个拦截器会取代现有的身份验证,但幸运的是它工作正常。