When dealing with Spring Security do you usually store the current user into a session variable or do you hit the DB every single time you want to access some user information?
At the moment I do the following but it seems a bit wasteful:
public class CurrentUserService {
private UserDAO userDAO;
public CurrentUserService(UserDAO userDAO) {
super();
this.userDAO = userDAO;
}
public User getUser(){
String username=SecurityContextHolder.getContext().getAuthentication().getName();
return userDAO.findUser(username);
}
}
Spring Security will automatically store the authenticated User object in the session in it's default configuration. One of the first things the Security Filter Chain does is check the session for a valid Authentication Token, if present then it populates the SecurityContext with it and skips any new authentication filters. All you need to do is write your UserDetailsService and the filter chain should od the rest.
Keep in mind that the user object to be stored in the session need not be the same you retrieved from the database. A generally acceptable approach is you store the frequently required details of the user in the session and hit the database only for the data that is accessed less frequently. Well, what user information to store in the session and what not to store is totally application dependent.
Related
Is there anyway to save additional data to the session when doing a social login/signup?
I noticed that if I send returnUrl parameter to the SS OAuth endpoint (i.e. /auth/google?retunUrl=...) then this value gets saved to the session as ReferrerUrl so I am using that to embed data as url parameters. I would prefer to be able to write to the Meta collection when directing to the SS Auth endpoint and then later read it from the session.
I tried to follow the exact process of how this was being saved to the session but I found it quite confusing.
What is the best way to add additional meta data to a social login/signup?
Edit:
I am talking about making a GET request to /auth/google, /auth/facebook etc...
I have additional data I want to track with the signup the user has entered in the browser.
If I add code to OnAuthenticated then this doesn't solve problem as the data has gone out of scope of the browser. It has to be passed in the GET request to the auth endpoint or have some reference to match up.
Edit:
public class CustomUserSession : AuthUserSession
{
public override void OnCreated(IRequest httpReq)
{
this.Meta.Add("foo", "bar");
httpReq.SaveSession(this);
}
}
You can handle a callback with the OnAuthenticated() Session or Auth Events.
Can someone please advise how do we display the EmailID of the logged-in user in a View. I am using ASP.NET MVC 5 identity.
Regards,
Ram
Assuming that you are using the user's email as the username then once the user has already been authenticated you can access the name from the User Principal Identity.Name
#if (Request.IsAuthenticated) {
<span>#User.Identity.Name</span>
}
That is the simple approach. If you did not use the email as the username then you will have to attach that info using claims and then you can use an extenion method to retrieve it. I had the reverse problem. I stored the email as the username and needed to get the logged-in user's Full Name to display. I then had to do what I described for you and had to add a custom DisplayName() extension for the IIdentity .
You can easily use the #inject feature call for injecting both UserManager and SignInManager (This feature available in .NET Core).
In your view add the following:
#inject SignInManager<YourUserIdentity> SignInManager
#inject UserManager<YourUserIdentity> UserManager
After the injection, you should be able to work with UserManager and SignInManager methods. For instance:
#if (SignInManager.IsSignedIn(User))
{
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello #UserManager.GetUserName(User)</a>
}
else
{
}
Pay attention for passing the User object when you need to reference the current logged in user.
In your case, if you would like to get the logged in Email Address, you can use the following technique:
var loggedInUserName = #UserManager.GetUserName(User);
var loggedInEmail = await UserManager.GetEmailAsync(
UserManager.Users.FirstOrDefault(u => u.UserName == loggedInUserName)
);
Or just keep it inside a ViewBag as you like.
Hope this will be handy for anyone :)
My Problem
I have a #SessionScoped sessionInformationBean, which holds a Person-Entity from a logged in user. So, if a User logs in, I am looking up the corresponding Entity and put in in the #SessionScoped CDI Bean. This Bean is used to retrieve the current user (a Person-Entity) at any position in code, so that you can check, if it is a Admin or things like that.
#Inject
private PersonFacade personFacade;
private Person currentUser;
public Person getCurrentUser() {
if (currentUser == null) {
String loginname = FacesContext.
getCurrentInstance().
getExternalContext().getRemoteUser();
currentUser = personFacade.findByLoginname(loginname);
}
return currentUser;
}
But set the case, an Admin is giving this logged in user ( the Person-Entity) some Admin-Rights and saves him to the database. In this case, the Person at the #SessionScoped Bean is not updated, therefore the already logged in user is not seeing his Admin-Rights after a refresh of his page. Thats the problem. To avoid this problem I am fetching the user new from the database every access (There is no cache activated) to the #SessionScoped bean.
What I want
But I want to cache him and avoid a database access every time. So, I thought, if anyone saves a user, I will simply notice all sessionInformationBean-Instances and set the currentUser-Attribute to null. So, the next call, they fetch it again from database and cache it till its set to null again from my Person.save()-Operation.
What I tried
But that seems to be a little bit tricky. I thought I can handle it with CDI-Events, but they only will be pushed to the sessionInformationBean of the user, that is editing the other user.
Maybe something to do with my problem: CDI Events observed across sessions
Then I thought.. okay.. lets do it with Primefaces-Push. But the same thing.. the Events are just coming to my own sessionInformationBean.
EventBus eventBus = EventBusFactory.getDefault().eventBus();
eventBus.publish("/session", "test");
I thought the purpose of push and WebSockets is to notify all users or sessions.
What should I do?
So, the question is: How to access all instances of a specific #SessionScopedBean? I just want to access the sessionInformationBean from every logged in user and set the currentUserto null.
There's no built in way I can think of to do this. What I would recommend is to add an ApplicationScoped bean. Whenever your SessionScoped bean is created, register it with this app scoped bean. When you want to process this event iterate through all of these objects.
I'm curious though, what happens when you have multiple servers?
I posted this question Access Control with a multi database application
So I tried putting it into application. Here is the case. I have a mainDB that has an ACL with no roles defined. The User clicks a button and it opens a control for CRUD with a datasource that has a computed filepath to a different database call it appDB. In appDB the ACL has several roles defined, and I have added myself to the ACL and assigned me the roles [Admin] and [Finance]. In this control I have added an After Page Load event that does the following:
var roles = context.getUser().getRoles();
viewScope.put("vsRoles", roles);
Upon opening the page the viewScope vsRoles is [] so it has not recognized that I have an additional set of roles in the appDB. So it would appear that context.getUser().getRoles() only gets my roles at authentication time when I log into the mainDB.nsf, and is not picking up the roles when I open appDB. I need to use the roles to configure what actions a person can perform, plus which documents a user can read and/or edit.
To complicate the issue the user may switch between multiple target application databases and will no doubt have different roles and access to each one.
thanks for the response to my previous question,but I might not have explained it in enough detail.
So, as far as I understood, what you need is to learn what specific roles the user has for the appDb.
context.getUser().getRoles() provides information about the current application (mainDB.nsf in your case). You are accessing appDB.nsf at a data source level. You can use a java method to learn the roles of a specific user in a target database:
public static List<String> getRoles(Database targetDb, String userName) {
ACL acl=null;
List<String> roles=new ArrayList<String>();
try {
acl=targetDb.getACL();
roles.addAll(targetDb.queryAccessRoles(userName));
} catch (NotesException e) {
// failed, nothing to do...
} finally {
if(acl!=null) acl.recycle();
}
return roles;
}
As an example:
Session session=ExtLibUtil.getCurrentSession();
Database appDb=session.getDatabase("", "appdb.nsf");
// Make sure appDb is not null...
List<String> roleList=getRoles(appDb, session.getEffectiveUserName());
ExtLibUtil.getViewScope().put("vsRoles", roleList);
So for my webapp, if I remove a user that is currently logged in, and I want to invalidate his/her session. So that as soon as he/she refresh the page or navigate, they are no longer log in. The way I have now is that if a User logged in successfully, I will store the user object in my SessionScoped bean, and store the HttpSession to the Application Map. Below is my code
This is my SessionScoped bean
#PostConstruct
public void init() {
User user = UserDAO.findById(userId, password);
Map<String, Object> appMap = FacesContext.getCurrentInstance().
getExternalContext().getApplicationMap();
HttpSession session = (HttpSession) FacesContext.getCurrentInstance().
getExternalContext().getSession(false);
appMap.put(userId, session);
}
Is this a correct approach? If so, how do I clean up my application map?
Is this a correct approach?
There are basically 2 ways.
Store the HttpSession handle in the application scope by the user ID as key so that you can get a handle of it and invalidate it. This may work for a small web application running on a single server, but may not work on a web application running on a cluster of servers, depending on its configuration.
I would only store it in another map in the application scope, not directly in the application scope like as you did, so that you can easier get an overview of all users and that you can guarantee that an arbitrary user ID won't clash with an existing application scoped managed bean name, for example.
Add a new boolean/bit column to some DB table associated with the user which is checked on every HTTP request. If the admin sets it to true, then the session associated with the request will be invalidated and the value in the DB will be set back to false.
how do I clean up my application map?
You could use HttpSessionListener#sessionDestroyed() for this. E.g.
public void sessionDestroyed(HttpSessionEvent event) {
User user = (User) event.getSession().getAttribute("user");
if (user != null) {
Map<User, HttpSession> logins = (Map<User, HttpSession>) event.getSession().getServletContext().getAttribute("logins");
logins.remove(user);
}
}
I think you can use your approach (with some modifications proposed by #BalusC) plus some notification mechanism (to make it work in distributed environment). You can do one of the following:
Use a topic queue subscribed by all your servers. When you remove user from your admin panel the JMS message will be created and sent to the topic. Every server will be responsible for invalidating the user session if it exists on the particular server (if the session is referenced in servletContext map).
Implement some action to invalidate the user session and run this action on every server in the cluster (The admin panel should send HTTP request to every server).
Use JGroups and TCP reliable multicast.
All of these solutions are not simple but much faster than polling the DB server on every request.