I am learning more about SharedPreferences and would like to understand how exactly everything is working. I have watched a decent amount of videos but I still have some questions.
Upon a user logging in, I generate a random session ID using UUID. I then assign the user their session ID and save a session by passing the UserModel into a SessionManagement instance that handles SharedPreferences.
userModel.setSessionId(UUID.randomUUID().toString());
SessionManagement sessionManagement = new SessionManagement(LoginActivity.this);
sessionManagement.saveSession(userModel);
When the user closes/opens the app, onStart() is called. It creates another instance of SessionManagement and checks if the session is null using getSession() to determine whether they're logged in or not.
SessionManagement sessionManagement = new SessionManagement(LoginActivity.this);
if (sessionManagement.getSession() != null) {
// go to some activity
}
And here is what SessionManagement constructor looks like:
private SharedPreferences sharedPreferences;
private final SharedPreferences.Editor editor;
private MasterKey masterKey;
//private String SHARED_PREF_NAME = "session";
private final String SESSION_KEY = "session_user";
private final String SESSION_USERNAME = "session_username";
public SessionManagement(Context context) {
try {
masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
} catch (GeneralSecurityException | IOException e) {
e.printStackTrace();
}
try {
sharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
} catch (GeneralSecurityException | IOException e) {
e.printStackTrace();
}
//sharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
editor = sharedPreferences.edit();
}
My question now is, if I am just checking whether the session is null or not, how does SharedPreferences know that the sessionID corresponds to the user that initialized it in step 1?
What are the ways that people work around weak/exposed session ID's that a SharedPreferences implementation can protect against?
Is my implementation/flow correct?
Is it safe to save the sessionID to a user model?
I appreciate any help I can get with this topic!
I use SharedPreferences a lot but did not yet use EncryptedSharedPreferences. I think this is only necessary, if you have data that deserves protection in a very strict way (like passwords or similar). But then you might probably want to use Google Identity or similar.
If you use private SharedPreferences, by calling context.getSharedPreferences(.., Context.MODE_PRIVATE) the OS already makes sure your data is only accessable from your app. Unless there is a bug in the system or the device is hacked, no other party has access to your data.
Now to answer your question, what do you use the session ID for?
For me it looks like you try to implement a web application inside an Android app. If this is the case because you are used to build web apps and have no more specific reason, then just forget about session handling and implement the app assuming there is only one user.
If you want it because you are communicating with a web service or similar, then let that service do the session handling, if possible.
If you really need that session, then the answer to your question is:
With your example the system doesn't know the SharedPrefrences you are calling is valid for the current session. The values are always the same, independent on who is currently logged in.
What you can do, is to choose a name for your SharedPreferences that corresponds with your logged in user (e.g. the hash of the username/mail address or similar).
So you always load the SharedPreferences based on some user identification, just like that:
context.getSharedPreferences(username.hashCode(), Context.MODE_PRIVATE)
There are two points to consider, if you do so:
You might generate junk data, if a user logs in once, therefore generates a SharedPreferences file, and never logs in again. So it might be appropriate to save the users last activity and clean up from time to time.
If you have lot's of users logging in, you might want to consider a database or some other storage type, like json files or whatever, as it's much easier to handle and clean them up.
sharedPrefences is like a repo. So write our data with string types. You can select your data is private or public. And remove your data when your deleted app.
Maybe your help this link:
https://www.geeksforgeeks.org/shared-preferences-in-android-with-examples/
Related
I know there must be douzens of answers to this question out there, but either i cant't find them or I don't understand them.
The Question:
How do I get my app to exactly start as it was left?
F.e. dynamicly added checkBoxes shouldn't dissapear!
There is no "out of the box" way of doing it. You could save the current state of your Activity in some way (More on persistence)
Then you need to be able to rebuild the desired state of the persisted state in your Activity lifecycle
You could save and load with the shared preferences for example:
public void saveState(YourState state) {
SharedPreferences sharedPreferences = app.getSharedPreferences(R.string.preference_file_key, Context.MODE_PRIVATE)
sharedPreferences.edit()
.putString("CustomAtt", state.getCustomAtt())
}
public YourState loadState() {
SharedPreferences sharedPreferences = app.getSharedPreferences(R.string.preference_file_key, Context.MODE_PRIVATE)
String customAtt = sharedPreferences.getString("CustomAtt", "DefaultValue")
return new YoutState(customAtt)
}
And use it like this
#Override
protected void onCreate(Bundle savedInstanceState) {
YourState state = loadState();
// Rebuild your activity based on state
someView.setText(state.getCustomAtt())
}
You can store such values in SharedPreferences.
https://developer.android.com/training/basics/data-storage/shared-preferences.html
It is using key-value approach for saving. So you can save some values and read it from SharedPreferences whenever you want to.
This is the best approach for small data, that can be used on the app launch. So you can quit your app and the data is still present - so can be read on the next app launch.
Or save the condition of your program to a text file, so that the program can "translate" it back into conditions before it stops, or what I do not recommend, it saves every object created with ObjectOutputStream.
I started to learn Android few days back and so far I am done with implementing Login Activity, Main Activity which extends abstract Base Activity.
Nav Bar item when clicked opens xml from Fragments.
I have a question about the token that I receive after successful login. This token is being used with each request to get data after successful login. Should I save the token in sqlite database securely or I should make a public property in Main Activity? Main Activity will always remain in memory as this will open fragments.
I can suggest 3 options:
1) you can save the token to the file, something like this:
public static void saveToken(Context ctx, String fileName, Object token) {
if (token == null) {
ctx.deleteFile(fileName);
} else {
ObjectOutputStream out = null;
try {
FileOutputStream fout = ctx.openFileOutput(fileName, 0);
out = new ObjectOutputStream(fout);
out.writeObject(token);
fout.getFD().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Make sure object token implements java.io.Serializable interface.
Usage before API level 24:
saveToken(appContext, someFileName, someTokenObject);
Usage with API level 24 and higher:
saveToken(appContext.createDeviceProtectedStorageContext(), someFileName, someTokenObject);
2) Use SQLCipher library for encrypted database.
3) You can encrypt your token using keystore system https://developer.android.com/training/articles/keystore.html
Use SharedPreferences and make sure you are using Context.MODE_PRIVATE this way only your app can access the data. SharedPreferences is a persistent store
e.g.
SharedPreferences prefs = context.getPreferences(Context.MODE_PRIVATE);
prefs.edit().putString("token", token).apply();
token = prefs.getString("token");
Why not to use SQLite:
SQLite is a database and is targeted at tabular data, a single token does not fit this use case.
Why not store in the main activity:
The main activity will not be around for the lifetime of the application install, it can be cleaned up by the OS at any time. It is not a persistent data store.
Should I save the token in sqlite database securely or I should make a public property in Main Activity? Main Activity will always remain in memory as this will open fragments.
The Official Android documentation already answers your question in the section on called "Best practices for security and privacy". It gives the following statement:
If you have access to user data and can avoid storing or transmitting it, don't store or transmit the data
In other words, if you can avoid persisting then don't persist it.
You mentioned "public property" in your question which makes me wonder if the concept of visibility modifiers is not yet clear. The Java public and private modifiers are for controlling access to the members of your class. They have nothing to do with security as per this answer here.
If you do persist the token in memory, as a public field or otherwise, you may reduce your exposure slightly by storing the token in a char[] rather than a String. That also is detailed in this canonical answer.
Finally, if you do have to store the token, the sqlite database is not the correct place to do it. Instead, you should use the provided KeyStore which will make for more difficult extraction of the token in the case that the device is compromised. The link to the documentation above is complete with code examples. If this proves too difficult to use, there are some wrappers around it including Scytale.
1) Store the token value within the base application singleton (where your application must be an instance of BaseApplication)
public class BaseApplication extends Application {
// token
private String token = null;
public String getToken() {return this.token;}
public void setToken(String token) {this.token = token;}
}
With the implementation above you will be able to set and get the token value from any activity/fragment. However the value is not persistent and it will be lost once the application ends.
Remark: If you are using the token for REST api access then you can store the token in the background service instance using a similar solution as above.
2) Use SharedPreferences - this is recommended way in case you want to store the token's value between application's runs.
Please see the answer from #Ryan.
You can use SharedPreferences to store token.it is available over application.
You can store it in Shared Preference as this one is token.
Now coming to the part of Security You can obviously use encryption for the shared preference.
There are already lots of open items available you can use below library for example
https://github.com/ophio/secure-preferences
Regarding keys that are in your java file to encrypt, You need to be sure you are applying proguard before you upload it to playstore.
In that manner now your token is fully secure with shared preferences.
In order to save it in sqlite than by decoding or root access your db file can also be accessed same as preferences. Regarding clear data from setting I think it will delete your sqlite data as well. Not sure about this though.
I hope it will help you out.
Better to use Sqlite or Realm. And store in Application memory and not in external memory. As for data residing in application memory we don't need to worry much about security. Saving in MainActivity is not a good solution, as once application closes, this will get cleared.
Storing in Shared Preference is also an option. But if user clears the cache from setting's this value will also get cleared. Realm Android Reference Link
I need to assign roles to my Liferay's users when they log in the application.
I have implemented all the logic in the 'authenticateByScreenName' method of a custom class that implements 'Authenticator'.
Example code:
public class ESBAuthenticator implements Authenticator{
public int authenticateByScreenName(long companyId, String screenName, String password,
Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
setProfile(companyId, screenname);
return 1;
}
public static void setProfile(long companyId, long userId){
User user = UserLocalServiceUtil.getUser(userId);
Role liferayRole = RoleLocalServiceUtil.fetchRole(companyId, "Administrator");
RoleLocalServiceUtil.addUserRole(user.getUserId(), liferayRole.getRoleId());
UserLocalServiceUtil.updateUser(user);
}
}
When I log-in, apparently it works, I check liferay database's tables and they are updated, my user has "Administrator" role assigned. However, the portal in front-end doesn't show the "Admin" option.
But If I go to 'My Account', press the 'save' button, log-out and log-in again I have the Admin options availables.
Anyone know why this happens?, I am calling 'updateUser()' after assign the role, It is not the same as the 'save' button?
Possible solution:
I have found that If I clear the content cached across the cluster it works fine. I found it in this post:
https://www.liferay.com/es/web/kamesh.sampath1/blog/-/blogs/how-to-clear-liferay-cache
Add the following line:
MultiVMPoolUtil.clear();
Anyone know if is this the right solution?, I can't find what does liferay when the "save" button from the "my_account" page is pressed. Maybe it clear this cache?. I was searching for a synchronize with database function but couldn't find anything. It seems to be that if a column is updated, liferay doesn't use it if it's cached :(.
I can give you a cheap hack.
Step 1: Make sure you have the hold of user credential.
Step 2: Do as necessary to change user roles etc
Step 3: Flush all kinds of cache (client or server) related to the flow
Step 4: redirect the user to a temp view where the user credential you held will be applied automatically and then redirected to the portal.
Lemme know whether it works
When you call the method you are passing screenName
setProfile(companyId, screenname);
But in ur setProfileMethod, you are using UserId
public static void setProfile(long companyId, long userId){
User user = UserLocalServiceUtil.getUser(userId);
Role liferayRole = RoleLocalServiceUtil.fetchRole(companyId, "Administrator");
RoleLocalServiceUtil.addUserRole(user.getUserId(), liferayRole.getRoleId());
UserLocalServiceUtil.updateUser(user);
}
Use Method
UserLocalServiceUtil.fetchUserByScreenName(companyId, screenName)
I think that the solution of clear cache works because you need to remove all cached portlets repsones. This is the way my_account do it.
// Clear cached portlet responses
PortletSession portletSession = actionRequest.getPortletSession();
InvokerPortletImpl.clearResponses(portletSession);
The problem is that InvokerPortletImpl is not visible externally. to replicate this functionally you can try a login.events.post and get the responses cache from the HttpSession e clear the map;
httprequest.getSession().getAttribute("CACHE_PORTLET_RESPONSES").clear()
but it is like a hack its better in this case MultiVMPoolUtil.clear();
There's an int variable accessed via singleton pattern.
But the problem is that the variable is set once and it's reset to 0 after some time.
It seems to be reset by garbage collection. Saving the value in Activity.onSaveInstanceState and restoring it in onCreate() is non-working.
Is the solution to save the variable in disk?
I just want to prevent it from resetting.
The variable will only stay active as long as the application is active. Android is notorious for killing background processes automatically. Unfortunately, if you're trying to access a variable that you'd like to persist over multiple runs of the application, you will need to write it to disk somehow.
There are a couple of options open to you, however. I will list the ones Google recommends.
Shared Preferences, "Store private primitive data in key-value pairs."
Internal Storage, "Store private data on the device memory."
External Storage, "Store public data on the shared external storage."
SQLite Database, "Store structured data in a private database."
Network Connection, "Store data on the web with your own network server."
Of all of these solutions, Shared Preferences, is probably the easiest to implement. It is as simple as:
public class Calc extends Activity {
public static final String PREFS_NAME = "MyPrefsFile";
#Override
protected void onCreate(Bundle state){
super.onCreate(state);
. . .
// Restore preferences
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
boolean silent = settings.getBoolean("silentMode", false);
setSilent(silent);
}
#Override
protected void onStop(){
super.onStop();
// We need an Editor object to make preference changes.
// All objects are from android.context.Context
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("silentMode", mSilentMode);
// Commit the edits!
editor.commit();
}
}
Code is from the Android Data Storage page.
I just want to prevent it from resetting.
You can use the SharedPreferences to save and then retrieve the variable later. A tutorial on how to do so can be found here.
On a side note though, are you sure your variable is resetting because of garbage collection?
I want to pass strings to all pages in my android app. When the user logs in I save userName and others to strings. I want to send the strings over multiple pages. How can I do that?
I have tried to send it through Intent
Intent myIntent = new Intent(view.getContext(), Help.class);
myIntent.putExtra("userName", userNameString);
startActivityForResult(myIntent, 0);
But when I go to another page I dont want to keep sending the data through Intent every time.
I have tried to get it from the class where I parse and put them in strings
HomeScreen home = new HomeScreen();
String userName= home.userNameString;
But since i am creating a new instance of the HomeScreen then userNameString is null
Any help will be greatly appreciated
Beware of using a static or instance variable to hold this state as the memory state of an Android application can be in flux. Data only stored in memory can be destroyed without your knowledge if your application process is killed while in the background.
The simplest mechanism would be to persist your data into SharedPreferences that you can access from any place in the application. So, first you save the string that you get from login (this is called inside an Activity, FYI):
//Write the username string to preferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putString("userName", userNameString).commit();
Then elsewhere in your application (i.e. other Activity instances) can read that string:
//Read the username string
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String userName = prefs.getString("userName", "");
With SharedPreferences, your values are actually persisted, meaning they will live forever on disk and won't go away just because your application's memory is reset (this is usually an advantage, especially for login information). Because of this, you will also need to remove the string when your user "logs out" like so:
//Remove the username string from preferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().remove("userName").commit();
There are also methods like contains() to allow you to check if the value is currently saved (is the user logged in?), etc.
The calling Activity is not guaranteed to remain in memory... the Android system may destroy it at any time.
If the child Activity depends on the user name, then the correct means of passing the information is with an Intent.
Declare a static variable and use it from everywhere:
public class SomeClass
{
public static String userNameString;
}
Use it like:
Strign s = SomeClass.userNameString;
Change its value as:
SomeClass.userNameString = "new string";