When I log into my account in my app, I save an auth token in my SharedPreferences, like this:
PreferenceUtils.setAuthToken(LoginActivity.this, authToken);
Here is my PreferenceUtils class:
public class PreferenceUtils {
public static SharedPreferences getSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
public static String getAuthToken(Context context) {
SharedPreferences sp = getSharedPreferences(context);
return sp.getString("auth_token", null);
}
public static void setAuthToken(Context context, final String token) {
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString("auth_token", token).apply();
}
}
When I log out of the account, I delete the auth token by calling the logOut() method in my UserUtils class:
public class UserUtils {
public static void logOut(Context context) {
SharedPreferences prefs = PreferenceUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.remove("auth_token");
editor.apply();
}
}
However, even after logging out of my account and removing the auth token from SharedPreferences, all Retrofit calls still somehow have the auth token saved and I'm not sure how.
In other words, when I log out of my account and Retrofit makes a new call, it will print out the auth token that I thought I had deleted when the user logged out.
Only when I restart my app does the auth token get fully removed.
Why is it doing this?
Here is my Retrofit client class (note the comment):
public class ApiClient {
public static final String API_BASE_URL = "https://www.example.com/";
private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.build();
private static HttpLoggingInterceptor logging =
new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY);
public static Retrofit getRetrofit() {
return retrofit;
}
public static <S> S createService(Class<S> serviceClass) {
if (!httpClient.interceptors().contains(logging)) {
httpClient.addInterceptor(logging);
builder.client(httpClient.build());
retrofit = builder.build();
}
return retrofit.create(serviceClass);
}
public static <S> S createService(Class<S> serviceClass, final String authToken) {
if (authToken != null) {
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// THIS STILL PRINTS THE AUTH TOKEN EVEN AFTER I'VE
// REMOVED IT FROM THE SHARED PREFERENCES
Log.d("AUTH TOKEN", authToken);
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "Bearer " + authToken)
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
}
OkHttpClient client = httpClient.build();
Retrofit retrofit = builder.client(client).build();
return retrofit.create(serviceClass);
}
}
How do I fix this??
Most likely the reason you still are seeing the token is because while you delete the token from the share preferences, you never reset the variable in the program. You need to make sure that you set the variable to null or empty, not just delete it from shared preferences.
As you requested, here is my previous comment as an answer:
It's because the interceptor you added is still alive in the httpClient. Once you've removed the token (or pass a null to the createService() method, you need to remove the interceptor as well.
Related
I develop spring non-web service which has webflux basic authentication.
Its working and i am able to successfully use cURL to reach certain endpoints like:
curl -I --user user:password http://localhost:5051/service/v1/health
or
curl -I http://user:password#localhost:5051/service/v1/health
But now im trying to send post via other services which use OkHttp and Retrofit to communicate with my spring service.
This process is more complicated, in main apllication, the OkHttpCllient is created and then separate, Retrofit service client provider is called.
The main application:
httpUrl = url
.newBuilder()
.username(Username) // first approach
.password(Password)
{..}
.build();
ClientProvider
.getInstance(httpUrl, Username, Password)
.subscribeOn(Schedulers.io())
.subscribe(new ProfiledSingleObserver<Event>() {
#Override
public void profiledOnError(#NotNull Throwable e) {
LOG.error("Transport layer error", e);
resultFuture.completeExceptionally(e);
}
#Override
public void profiledOnSuccess(#NotNull Event event) {
resultFuture.complete(Collections.singleton(event));
}
});
public class ClientProvider {
private static HttpUrl httpUrl = null;
private static Service instance = null;
private ClientProvider() {
}
public static Service getInstance(final HttpUrl url, String username, String password) {
instance = Client.createService(url, HttpClient.getInstance(username, password));
return instance;
}
}
public static OkHttpClient getInstance(String username, String password) {
if (instance == null) {
synchronized (lock) {
instance = new OkHttpClient.Builder()
// .authenticator(new Authenticator() { // second approach
// #Override
// public Request authenticate(#Nullable Route route, #NotNull Response response) throws IOException {
// return response
// .request().newBuilder()
// .header("Authorization", Credentials.basic(username, password))
// .build();
// }
// })
// .addInterceptor(new BasicAuthInterceptor(username,password)) // third approach
.addInterceptor(createHttpBodyLoggingInterceptor())
.addInterceptor(createHttpBasicLoggingInterceptor())
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.dispatcher(createDispatcher())
.connectionPool(createConnectionPool())
.build();
}
}
return instance;
}
public class BasicAuthInterceptor implements Interceptor {
private final String credentials;
public BasicAuthInterceptor(String user, String password) {
this.credentials = Credentials.basic(user, password);
}
#NotNull
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request authenticatedRequest = request.newBuilder()
.header(HttpHeaders.AUTHORIZATION, credentials).build();
return chain.proceed(authenticatedRequest);
}
}
And the Retrofit service client provider:
public static Service createService(final HttpUrl baseUrl, final OkHttpClient okHttpClient) {
return createRetrofit(baseUrl, okHttpClient).create(Service.class);
}
protected static Retrofit createRetrofit(HttpUrl baseUrl, OkHttpClient client) {
return new Retrofit
.Builder()
.baseUrl(baseUrl)
.client(client)
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.addConverterFactory(JacksonConverterFactory.create())
.build();
}
I was taking 3 different approach to solve this problem, as u can see in the comments next to them.
First was just passing username and password via url to look something like:
http://user:password#localhost:5051/service/v1/health
but even if curl with this notation is working, authentication did not pass.
Second approach was with creating Authenticator in OkHttpClient.Builder().
Still the same results.
And third one was with creating Interceptor also in the OkHttpClient.
In this approach i was able to pass unit tests with stubbed server,
which was impossible in other solutions ( Status 404, Not found):
#BeforeClass
public static void setUpClass() {
serviceStubServer = new StubServer();
whenHttp(serviceStubServer)
.match(Condition.basicAuth("user", "pass"), Condition.post("/service/v1/health"))
.then(
Action.status(HttpStatus.OK_200),
);
serviceStubServer.run();
}
But still i was unable to send records to my Spring service via my main appplication( Status 401, Unauthorized).
My question is, whats the correct way to pass credentials thru OkHttp and Retrofit to be able to reach endpoints in Spring non-web, webflux basic authentication secure application?
Seems like the problem was with singleton approach to create OkHttpClient. This system was operating with two different Spring services and remembered one of the credentials, so he was unable to correctly reach second service.
To solve this problem i create second OkHttpClient class and use the "Interceptor" method to provide authorization data.
After I authenticate and get JWT in header I want to add token to every request sent from person logged in with this token.
But I don't know how to pass token from shared preferences to my Api client class.
I tried defining sharedPref object, but it needs Context to use .getSharedPreferences method.
This is how I retrieve and save token from authentication endpoint: (this is in loginActivity class)
private void login() {
LoginRequest loginRequest = new LoginRequest();
loginRequest.setUsername(usernameLog.getText().toString().trim());
loginRequest.setPassword(passwordLog.getText().toString().trim());
Toast.makeText(getApplicationContext(), "Clicked", Toast.LENGTH_SHORT).show();
Call<Void> loginResponseCall = ApiClient.getUserService().login(loginRequest);
loginResponseCall.enqueue(new Callback<Void>() {
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
if (response.isSuccessful()) {
token = response.headers().get("Authorization");
SharedPreferences preferences = getSharedPreferences("logged", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("isLogged", true);
editor.putString("token", token);
editor.apply();
Toast.makeText(getApplicationContext(), "User logged in successfully", Toast.LENGTH_SHORT).show();
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
}
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
Toast.makeText(getApplicationContext(), "User failed logging in: " + t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
And this is my ApiClient class, where I'm trying to use token saved from Shared Preferences.
public class ApiClient {
private static Retrofit getRetrofit() {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", TOKEN)
// Instead of "TOKEN" I need to somehow pass
//
// SharedPreferences preferences = getSharedPreferences("logged", MODE_PRIVATE);
// preferences.getString("token", "");
.build();
return chain.proceed(newRequest);
})
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.1.60:8080/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
public static UserService getUserService() {
UserService userService = getRetrofit().create(UserService.class);
return userService;
}
}
When I hard code it, it works. But It needs to pass token dynamically depending on which user is logged in.
Hello i didn't found solution for my problem. In new OkHttp and Retrofit is some function:
HandshakeCertificates certificates = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.addInsecureHost("192.168.0.150")
.build();
I am trying to connect my Android App to Spring Boot Server. This Server must use HTTPS - not my idea.
On this server i generated self-sign certyficate, but still i got error, now i don't have any ideas. Here is a full error:
java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Below i insert code of Retrofit generator:
public class ServiceGenerator {
public static final String API_BASE_URL = "https://192.168.0.150:8443/";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// private static OkHttpClient httpClient = getUnsafeOkHttpClient();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.build();
public static <S> S createService(Class<S> serviceClass) {
return createService(serviceClass, null);
}
static public <S> S createService(
Class<S> serviceClass, final String authToken) {
if (!TextUtils.isEmpty(authToken)) {
AuthenticationInterceptor interceptor =
new AuthenticationInterceptor(authToken);
if (!httpClient.interceptors().contains(interceptor)) {
httpClient.addInterceptor(interceptor);
HandshakeCertificates certificates = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.addInsecureHost("192.168.0.150")
.build();
httpClient.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager());
OkHttpClient okHttpClient = httpClient.build();
builder.client(okHttpClient);
retrofit = builder.build();
}
}
return retrofit.create(serviceClass);
}
}
Request Code:
private void doLoginRequest() {
DeviceAPI deviceAPI = ServiceGenerator.createService(DeviceAPI.class);
Call<JWTResponse> call = deviceAPI.login(new Login(usernameEditText.getText().toString(), passwordEditText.getText().toString()));
call.enqueue(new Callback<JWTResponse>() {
#Override
public void onResponse(Call<JWTResponse> call, Response<JWTResponse> response) {
if (response.isSuccessful()) {
Toast.makeText(LoginActivity.this, response.body().toString(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(LoginActivity.this, response.message(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<JWTResponse> call, Throwable t) {
System.out.println(t.getCause());
Toast.makeText(LoginActivity.this, t.getMessage() , Toast.LENGTH_SHORT).show();
}
});
}
Api Code:
public interface DeviceAPI {
#POST("api/auth/signin")
Call<JWTResponse> login(#Body Login login);
}
If you need more information give me feedback!
This example shows connecting twice to a host - Once with a valid HTTPS Handshake, and second using addInsecureHost. n.b. you won't get a handshake peer in the second because the handshake won't result in valid certificates.
The same will apply if you run against a dev server, so edit the example to use your devserver.
https://gist.github.com/yschimke/796e58a6152137bdcd7d2f9d63e26363
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.tls.HandshakeCertificates.Builder
fun main() {
val request = Request.Builder()
.url("https://httpbin.org/get")
.build()
var client = OkHttpClient();
var response = client.newCall(request).execute()
println(response.handshake?.peerPrincipal) // CN=httpbin.org
println(response.code)
val certificates = Builder()
.addInsecureHost("httpbin.org")
.build()
client = OkHttpClient.Builder().sslSocketFactory(certificates.sslSocketFactory(),
certificates.trustManager
).build();
response = client.newCall(request).execute()
println(response.handshake?.peerPrincipal) // null
println(response.code)
}
Hi Im trying to add a bearer token to a retrofit call in java, but i cant seem to pass it.
Currently Im logging in with one method and this creates a bearer token and im trying to add the token to the Get Call, but its just returning a 401 error, have I added the token to the call correctly?
#GET("diagnosis/configuration")
Call<ResponseBody> getFavourites (#Query("favourite") Boolean fave,#Header("Bearer Token") String authHeader);
#POST("auth/login")
Call<LoginResponse> postLogin (#Body LoginCredentialsBody body);
public class LoginApiStepDefinition extends TestBaseFix {
Retrofit retrofit = super.buildRetrofit(super.buildOkHttpClient());
RetrofitCallsLogin call = retrofit.create(RetrofitCallsLogin.class);
RetrofitCallsGetFavourites favecall = retrofit.create(RetrofitCallsGetFavourites.class);
private Response<LoginResponse> responseBody;
private String favouritesResponseBody;
String usernameValue;
String passwordValue;
#And("I login with {string} and {string} to return login token")
public void iLoginWithAndToReturnLoginToken(String username, String password) throws Exception {
LoginApi(username, password);
}
public String LoginApi(String username, String password) throws Exception {
usernameValue = username;
passwordValue = password;
//gets fixture ids for the dates
LoginCredentialsBody login = new LoginCredentialsBody();
login.setPassword(passwordValue);
login.setUsername(usernameValue);
String responseBody = call.postLogin(login).execute().body().toString();
String requiredString = responseBody.substring(responseBody.indexOf("=") + 1, responseBody.indexOf(","));
System.out.println(requiredString);
return token;
}
#Then("I get the list of favourites with {string} and {string}")
public void iGetTheListOfFavouritesWithAnd(String username, String password) throws Exception {
String favouritesResponseBody = favecall.getFavourites(true, LoginApi(username, password)).execute().body().toString();
System.out.println(favouritesResponseBody);
}
}
To add bearer token in retrofit, you have to create a class that implements Interceptor
public class TokenInterceptor implements Interceptor{
#Override
public Response intercept(Chain chain) throws IOException {
//rewrite the request to add bearer token
Request newRequest=chain.request().newBuilder()
.header("Authorization","Bearer "+ yourtokenvalue)
.build();
return chain.proceed(newRequest);
}
}
Now add your Interceptor class in OKHttpClient object and add that obejct in Retrofit object:
TokenInterceptor interceptor=new TokenInterceptor();
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl("add your url here")
.addConverterFactory(JacksonConverterFactory.create())
.build();
these three class will be your final setup for all types of call
for first call(Login) you do not need to pass token and after login pass jwt as bearer token to authenticate after authentication do not need to pass
public class ApiUtils {
private static final String BASE_URL="https://abcd.abcd.com/";
public ApiUtils() {
}
public static API getApiService(String token){
return RetrofitClient.getClient(BASE_URL,token).create(API.class);
}}
2.Using ApiUtils.getapiService you can get the client ,pass jwt or bearer token
public class RetrofitClient {
public static Retrofit retrofit=null;
public static Retrofit getClient(String baseUrl, String token){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(60,TimeUnit.SECONDS)
.connectTimeout(60,TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor(new Interceptor() {
#NotNull
#Override
public Response intercept(#NotNull Chain chain) throws IOException {
Request request=chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build();
return chain.proceed(request);
}
}).build();
if(retrofit==null||token!=null){
retrofit= new Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}}
3 In this Interface you can create methods for get or post requests
public interface API {
#POST("/Api/Authentication/Login")
Call<JsonObject> login(#Body Model userdata);
#POST("/api/Authentication/ValidateSession")
Call<JsonObject> validateSession(#Body MyToken myToken);
#POST("/api/master/abcd")
Call<JsonObject> phoneDir(#Body JsonObject jsonObject);
#Multipart
#POST("/api/dash/UploadProfilePic")
Call<JsonObject> uploadProfile(#Part MultipartBody.Part part);
#FormUrlEncoded
#POST("/api/dashboard/RulesAndPolicies")
Call<JsonObject> rulesAndProcess(#Field("ct") int city);
#FormUrlEncoded
#POST("/api/dashboard/RulesAndPolicies")
Call<JsonObject> rulesAndProcess(
#Field("city") int city,
#Field("department") String department,
#Field("ctype") String ctype
);
I have a retrofit Client that helps me set a header for any requests i make to my REST APIs. On user login i get the token from the server and set this token to the header of the requests. I save this token to SharedPreferences so that i can get it anytime i need to make requests to my REST APIs. The problem is that anytime i set a new token to my SharedPreferences file when a new user signs in, it still gets the old token instead of saving this new token to use for future requests.
This is my Retrofit Client below:
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String token) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okClient = new OkHttpClient();
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.create();
okClient.interceptors().add(chain -> chain.proceed(chain.request()));
okClient.interceptors().add(chain -> {
Request original = chain.request();
Request request = original.newBuilder()
.header(Config.X_AUTH_TOKEN, "Bearer" + " " + token)
.method(original.method(), original.body())
.build();
Log.d("Authorization", token);
return chain.proceed(request);
});
okClient.interceptors().add(logging);
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(Config.BASE_URL1)
.client(okClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
return retrofit;
}
}
this is my codes for setting and getting the token
public String getToken() {
return prefs.getString(AuthUser.USER_TOKEN, "");
}
public void setToken(String token) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(AuthUser.USER_TOKEN, token);
editor.apply();
}
this is where i call my set token method to save the new token to SharedPreference
authUser.setToken(token);
I completely don't see how this is surprising. Your RetrofitClient is a confusingly (and arguably badly written) singleton. Let's go through a typical situation where this will fail.
You launch your app with a previously saved token. At first everything works fine. At some point you call RetrofitClient.getClient(token) and all requests succeed. After some time the server invalidates the token. You probably get a 403 response from your server, lauch the login screen again and update your token in your SharedPreferences. Here is where your problems begin. Although you saved your new token correctly, your RetrofitClient will do what singletons do and continue to return the first instantiation of itself stored in the private static Retrofit retrofit filed.
A quick workaround would be to add an invalidate method to your RetrofitClient. Something like.
public static void invalidate() {
this.retrofit = null;
}
Call it when you get your 403 response, or when you logout.
PS: Please move the following line if (retrofit==null) { at the beginning of your getClient method. Creating a new okHttp client, for nothing, every time someone calls getClient is just wasteful.
You can write intercepter to execute each time before network request happen.Create new file as HeaderIntercepter
public class HeaderIntercepter implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String token = context.getSharedPreferences(FILENAME, MODE_PRIVATE).getString("TOKEN","");
Request tokenRequest = request.newBuilder()
.addHeader("Authorization", "Bearer " + token)
.addHeader("Content-Type", "application/json")
.build();
return chain
.proceed(tokenRequest);
}
}
Add intercepter to okHttpclient
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(new HeaderIntercepter());