Android Studio中如何存储Access Token与Refresh Token?基于Volley的REST API实现场景问询
Great question! Let's break this down based on Android best practices and your existing code setup:
It depends on your app's user model:
- Single-user app (most common for course projects): You don't need a database. Instead, use encrypted shared preferences (via Jetpack Security's
EncryptedSharedPreferences). It's lightweight, easy to implement, and designed for storing small, sensitive pieces of data like tokens. - Multi-user app (supports switching accounts): A database (like Room) makes sense here. You can store each user's tokens linked to their account ID, but you still need to encrypt the token fields themselves—never store plaintext tokens anywhere.
Critical note: Never store tokens in plaintext (whether in SharedPreferences or a database). Tokens are sensitive and can be misused if accessed by malicious actors. Encryption is non-negotiable here.
The best approach is to encapsulate token retrieval and attach it automatically to every authenticated API request. Here's how to integrate this with your existing Volley setup:
Step 1: Add Token Storage Logic
First, add methods to save and retrieve encrypted tokens in your Model class (or create a dedicated TokenManager class for better separation of concerns):
// In your Model class import androidx.security.crypto.EncryptedSharedPreferences; import androidx.security.crypto.MasterKey; import android.content.Context; import android.util.Log; import java.util.HashMap; import java.util.Map; private SharedPreferences getEncryptedPrefs() { try { MasterKey masterKey = new MasterKey.Builder(mApplication) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build(); return EncryptedSharedPreferences.create( mApplication, "secure_tokens", masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ); } catch (Exception e) { Log.e("TokenStorage", "Failed to create encrypted prefs", e); // Fallback to regular SharedPreferences only for testing (not production!) return mApplication.getSharedPreferences("fallback_tokens", Context.MODE_PRIVATE); } } public void saveAuthTokens(Authentication auth) { SharedPreferences.Editor editor = getEncryptedPrefs().edit(); editor.putString("access_token", auth.getAccessToken()); editor.putString("refresh_token", auth.getRefreshToken()); editor.apply(); } public String getAccessToken() { return getEncryptedPrefs().getString("access_token", null); } public String getRefreshToken() { return getEncryptedPrefs().getString("refresh_token", null); }
Don't forget to add the Jetpack Security dependency to your build.gradle (Module level):
implementation "androidx.security:security-crypto:1.1.0-alpha06"
Step 2: Save Tokens After Login
Update your onLogin callback in the Activity to save the tokens once login succeeds:
@Override public void onLogin(Authentication authentication){ if(authentication.getSuccess().equals("true")) { model.saveAuthTokens(authentication); // Save encrypted tokens model.setAuth(authentication); Toast.makeText(LoginActivity.this, "Login success!", Toast.LENGTH_LONG).show(); Intent intent = new Intent(LoginActivity.this, HomeActivity.class); startActivity(intent); } else{ Toast.makeText(LoginActivity.this, "Invalid Login!", Toast.LENGTH_LONG).show(); } }
Step 3: Attach Tokens to API Requests
Modify your WebApi class to create authenticated requests that automatically include the access token in the request headers. Add a helper method to build these requests:
// In your WebApi class private JsonObjectRequest createAuthenticatedRequest(int method, String url, JSONObject body, Response.Listener<JSONObject> successListener, Response.ErrorListener errorListener) { return new JsonObjectRequest(method, url, body, successListener, errorListener) { @Override public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> headers = new HashMap<>(); String accessToken = mModel.getAccessToken(); // Assume WebApi has access to your Model instance if (accessToken != null) { headers.put("Authorization", "Bearer " + accessToken); } return headers; } }; }
Now, use this helper method for all authenticated API calls. For example, if you have a request to fetch user profile data:
public void fetchUserProfile(APIListener listener) { String url = BASE_URL + "/user/profile"; Response.Listener<JSONObject> successListener = response -> { // Handle profile data (pass to listener) listener.onProfileFetched(response); }; Response.ErrorListener errorListener = error -> { // Handle errors (see next section for token expiration handling) Log.e("ProfileRequest", "Error fetching profile", error); Toast.makeText(mApplication, "Failed to load profile", Toast.LENGTH_SHORT).show(); }; JsonObjectRequest request = createAuthenticatedRequest(Request.Method.GET, url, null, successListener, errorListener); mRequestQueue.add(request); }
Eventually, your access token will expire. When the API returns a 401 Unauthorized response, you should use the refresh token to get a new access token, then retry the original request. Here's a simplified example of how to handle this in your error listener:
Response.ErrorListener errorListener = error -> { if (error.networkResponse != null && error.networkResponse.statusCode == 401) { // Access token expired, try to refresh refreshAccessToken(new TokenRefreshListener() { @Override public void onRefreshSuccess(String newAccessToken) { // Update stored token and retry the original request model.saveAuthTokens(new Authentication(newAccessToken, model.getRefreshToken(), "true")); mRequestQueue.add(createAuthenticatedRequest(Request.Method.GET, url, null, successListener, errorListener)); } @Override public void onRefreshFailed() { // Redirect to login screen if refresh fails Intent intent = new Intent(mApplication, LoginActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mApplication.startActivity(intent); } }); } else { Toast.makeText(mApplication, "Request failed", Toast.LENGTH_SHORT).show(); } };
You'll need to implement the refreshAccessToken method in your WebApi class to call your OAuth refresh endpoint with the stored refresh token.
内容的提问来源于stack exchange,提问作者Aditya

