So i am trying to figure out if there is a simpler and more straightforward way of obtaining a CustomUser object for the currently logged in user.
I have a custom UserServiceImplementation and use a Custom User object.
Reading around i noticed the use of #AuthenticationPrincipal but i cannot seem to get any examples of it working. Furthermore it depends on #EnableWebMVCSecurity which is depreciated for the current version of spring security that i am using (4.x.x).
Am i looking at the right functionality to be able to achieve my goal or should i be looking at something completely different?
Example of my current code where i am forced to get current user object for the logged in user to be able to achieve further processing.
#RequestMapping(value = "/map", method = method = RequestMethod.POST)
public String processMap(#Valid MapProc mapObject) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = userInterface.findByLogin(((UserDetails) principal).getUsername());
// rest of code
return "map/processed";
}
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String userName= auth.getName();
In the Session is always the UserDetails object from the UserDetailsService persistent. If you have your own Implementation witch returns your own User Object you get it by:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof User) {
//Your Code here
}
Related
I want to store additional information in my custom "userDetail"-object, but I am not sure when I want to add the information into the object. It needs to be accessible for every method after the login happened (JWT-Token login) via SecurityContextHolder.getContext().getAuthentication().getInformation()
I thought about setting it in my custom AuthenticationController around the time the token is generated, but this is difficult due to the act that the referenced type at that point is userDetails, not customUserDetails:
UserDetails userDetails = this.userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
//TODO setInformation
String token = this.tokenUtils.generateToken(userDetails);
What is the best point in the authentication/authorization process to add such information, and how would I best do so?
After further research, the best point in time to populate the user object with custom data seems to be the constructor of your customized implementation of the UserDetails interface, at the same time Authorities and other data is set for the User object. For example:
public SpringSecurityUser(Long id, String username, String password, String email, Date lastPasswordReset,
Collection<? extends GrantedAuthority> authorities) {
this.setId(id);
this.setUsername(username);
this.setPassword(password);
this.setEmail(email);
this.setLastPasswordReset(lastPasswordReset);
this.setAuthorities(authorities);
this.setMyInfo(Information);
}
This was initially a problem for me, because the method setting the information was doing so based on the logged in Principal, which is not possible because at that time of the login process, the Principal is being created.
I am trying to use a simple custom role provider and using the code from here: http://code.google.com/p/sfckopanka/source/browse/trunk/App_Code/OdbcRoleProvider.cs?r=45
Which is implemented using this: http://msdn.microsoft.com/en-us/library/tksy7hd7(v=vs.100).aspx
This is all just simple boilerplate code from Microsoft.
When I debug my app I can see that my Role Provider is initialized BUT no methods are ever called when I try to check roles.
[Authorize(Roles="Customer")]
or
User.IsInRole("Customer")
I put break points in several places in my role provider and they are just never hit.
FYI I am using WebAPI and I am not using a Membership Provider, instead I am using Basic Auth via a message handler.
http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/
The Basic Auth is working great, but I ma not sure if this is what is preventing my Role Provider from being called.
Answering this in case it can help someone else. In the Basi Auth code linked above there there is a PrincipalProvider class. in this class you create a GenericPrincipal, which also takes the roles that the user is in, so I just had to add a line of code to get my roles to provide to the GenericPrincipal
public IPrincipal CreatePrincipal(string username, string password)
{
if (!MyRepo.Authentication.ValidateUser(username, password))
{
return null;
}
var identity = new GenericIdentity(username);
//Code to get my roles from my role provider to use when setting principal
string[] roles =Roles.Provider.GetRolesForUser(username);
IPrincipal principal = new GenericPrincipal(identity,roles);
ShopZioRepo.ClearUserCache(ShopZioGlobal.MyCookies.UserID);
var user = ShopZioRepo.GetUserByEmail(username);
ShopZioGlobal.MyCookies.UserID = user.SalesRepID;
ShopZioGlobal.MyCookies.Username = username;
return principal;
}
Hope this helps someone.
This is a Spring Security question.
In my application, I have a User entity as a domain object. This object contains implementation to support Spring UserDetails object. The authentication (login/logout) process works fine.
The challenge is that I need to extract that object from the session to make 'business logic' decisions in my code.
I've been reading about querying SecurityContextHolder, but frankly, I still don't know what is the best approach, given that multiple Spring versions seem to be a factor in those discussions. Also, the Principal object isn't a solution for me, as it does not seem to contain any access level or role information.
Below is a simple controller to illustrate my challenge. It has my User domain object hardcoded. I need to replace that block with code that will obtain the User object from Spring Security session. I'm looking for the best way to do this within Spring 3.
Can I get this object as my domain object or do I need to get it as Spring UserDetails object and manually convert it?
Can this Security context lookup be injected somehow into my controller?
public class HomeController {
#RequestMapping(value="/home.html", method=RequestMethod.GET)
public ModelAndView getHomePage(Map<String, Object> model) {
// Get current user
User currentUser=new User();
currentUser.setUserName("Admin");
currentUser.setAccessLevel(UserAccessLevel.ADMINISTRATOR);
// Construct HomePage bean
HomeBean bean=new HomeBean();
bean.setCurrentUserName(currentUser.getUserName());
// Construct list of catalogs
Collection<String> catalogList=new ArrayList<String>();
catalogList.add("articles");
catalogList.add("files");
catalogList.add("comments");
if(currentUser.hasAdministratorAccessLevel()) {
catalogList.add("users");
}
bean.setCatalogList(catalogList);
// Construct and return ModelAndView
ModelAndView mav=new ModelAndView();
mav.setViewName(WebView.HOME_PAGE.getViewName());
mav.addObject(bean.getBeanId(), bean);
return mav;
}
=== Update 2012-01-07 ======================================================
I'm working with Luke's suggestion. The method that gets UserDetails from session and converts it to a returned my domain User object is in my UserService.
Here's my controller:
#Controller
public class HomeController {
#Autowired
private UserService userService;
#RequestMapping(value="/home.html", method=RequestMethod.GET)
public ModelAndView getHomePage(Map<String, Object> model) {
// Construct HomePage bean
HomeBean bean=new HomeBean();
User currentUser=userService.getCurrentlyAuthenticatedUser();
bean.setCurrentUserName(currentUser.getUserName());
And here's key code from UserServiceImpl.getCurrentlyAuthenticatedUser():
#Override
public User getCurrentlyAuthenticatedUser() {
User currentUser=new User();
Authentication a = SecurityContextHolder.getContext().getAuthentication();
UserDetails currentUserDetails = (UserDetails) a.getPrincipal();
if(currentUserDetails==null) {
return currentUser;
}
currentUser.setUserName(currentUserDetails.getUsername());
This works but am I doing this right? Feedback much appreciated. I am still unable to retrieve my User domain object from the session. I'm retrieving Spring's UserDetails object and with it constructing my domain User object but in the process some information is lost.
Normally, the principal object contained in the successful Authentication will be an instance of your user object. So, for a quick solution, use
Authentication a = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User)a.getPrincipal();
But also (once you get that working), you might want to look at the answer I just gave (to a similar question) on how to inject a custom security context accessor.
Spring also provides an annotation #AuthenticationPrincipal, it is used to resolve Authentication.getPrincipal(). It can be used like below...
public ResponseEntity<UserProfileResponse>UserProfile(#AuthenticationPrincipal JwtAuthenticationToken principal){
I implemented a custom Profile object in code as described by Joel here:
How to assign Profile values?
I can't get it to work when I'm creating a new user, however. When I do this:
Membership.CreateUser(userName, password);
Roles.AddUserToRole(userName, "MyRole");
the user is created and added to a role in the database, but HttpContext.Current.User is still empty, and Membership.GetUser() returns null, so this (from Joel's code) doesn't work:
static public AccountProfile CurrentUser
{
get { return (AccountProfile)
(ProfileBase.Create(Membership.GetUser().UserName)); }
}
AccountProfile.CurrentUser.FullName = "Snoopy";
I've tried calling Membership.GetUser(userName) and setting Profile properties that way, but the set properties remain empty, and calling AccountProfile.CurrentUser(userName).Save() doesn't put anything in the database. I've also tried indicating that the user is valid & logged in, by calling Membership.ValidateUser, FormsAuthentication.SetAuthCookie, etc., but the current user is still null or anonymous (depending on the state of my browser cookies).
SOLVED (EDITED FURTHER, SEE BELOW): Based on Franci Penov's explanation and some more experimentation, I figured out the issue. Joel's code and the variations I tried will only work with an existing Profile. If no Profile exists, ProfileBase.Create(userName) will return a new empty object every time it's called; you can set properties, but they won't "stick" because a new instance is returned every time you access it. Setting HttpContext.Current.User to a new GenericPrincipal will give you a User object, but not a Profile object, and ProfileBase.Create(userName) and HttpContext.Current.Profile will still point to new, empty objects.
If you want to create a Profile for a newly-created User in the same request, you need to call HttpContext.Current.Profile.Initialize(userName, true). You can then populate the initialized profile and save it, and it will be accessible on future requests by name, so Joel's code will work. I am only using HttpContext.Current.Profile internally, when I need to create/access the Profile immediately upon creation. On any other requests, I use ProfileBase.Create(userName), and I've exposed only that version as public.
Note that Franci is correct: If you are willing to create the User (and Roles) and set it as Authenticated on the first round-trip, and ask the user to then log in, you will be able to access the Profile much more simply via Joel's code on the subsequent request. What threw me is that Roles is immediately accessible upon user creation without any initialization, but Profile is not.
My new AccountProfile code:
public static AccountProfile CurrentUser
{
get
{
if (Membership.GetUser() != null)
return ProfileBase.Create(Membership.GetUser().UserName) as AccountProfile;
else
return null;
}
}
internal static AccountProfile NewUser
{
get { return System.Web.HttpContext.Current.Profile as AccountProfile; }
}
New user creation:
MembershipUser user = Membership.CreateUser(userName, password);
Roles.AddUserToRole(userName, "MyBasicUserRole");
AccountProfile.NewUser.Initialize(userName, true);
AccountProfile.NewUser.FullName = "Snoopy";
AccountProfile.NewUser.Save();
Subsequent access:
if (Membership.ValidateUser(userName, password))
{
string name = AccountProfile.CurrentUser.FullName;
}
Further thanks to Franci for explaining the Authentication life cycle - I'm calling FormsAuthentication.SetAuthCookie in my validation function, but I'm returning a bool to indicate success, because User.Identity.IsAuthenticated will not be true until the subsequent request.
REVISED: I'm an idiot. The above explanation works in the narrow case, but doesn't resolve the core problem: Calling CurrentUser returns a new instance of the object each time, whether it's an existing Profile or not. Because it's defined as a property, I wasn't thinking about this, and wrote:
AccountProfile.CurrentUser.FullName = "Snoopy";
AccountProfile.CurrentUser.OtherProperty = "ABC";
AccountProfile.CurrentUser.Save();
which (of course) doesn't work. It should be:
AccountProfile currentProfile = AccountProfile.CurrentUser;
currentProfile.FullName = "Snoopy";
currentProfile.OtherProperty = "ABC";
currentProfile.Save();
It's my own fault for completely overlooking this basic point, but I do think declaring CurrentUser as a property implies that it's an object that can be manipulated. Instead, it should be declared as GetCurrentUser().
Creating a user just adds it to the list of users. However, this does not authenticate or authorize the new user for the current request. You also need to authenticate the user in the current request context or for subsequent requests.
Membership.ValidateUser will only validate the credentials, but it's not authenticating the user for the current or subsequent requests. FormsAuthentication.SetAuthCookie will set the authentication ticket in the response stream, so the next request will be authenticated, but it does not affect the state of the current request.
The easiest way to authenticate the user would be to call FormsAuthentication.RedirectFromLoginPage (assuming you are using forms authentication in your app). However, this one would actually cause a new HTTP request, which will authenticate the user.
Alternatively, if you need to continue your logic for processing the current request, but want the user to be authenticated, you can create a GenericPrincipal, assign it the identity of the new user and set the HttpContext.User to that principal.
You are going to run into problems with this approach if you enable anonymousIdentification. Rather than Membership.GetUser().UserName, I would suggest using HttpContext.Profile.UserName.
Like this...
private UserProfile _profile;
private UserProfile Profile
{
get { return _profile ?? (_profile = (UserProfile)ProfileBase.Create(HttpContext.Profile.UserName)); }
}
Hat tip: SqlProfileProvider - can you use Profile.GetProfile() in a project?
First of all, thanks #Jeremy for sharing your findings. You helped me get going in the right direction. Secondly, sorry for bumping this old post. Hopefully this will help someone connect the dots.
The way I finally got this working was to use the following static method inside my profile class:
internal static void InitializeNewMerchant(string username, Merchant merchant)
{
var profile = System.Web.HttpContext.Current.Profile as MerchantProfile;
profile.Initialize(username, true);
profile.MerchantId = merchant.MerchantId;
profile.Save();
}
Basically, I log into my website using OpenId, very similar to what I am assuming SO does. When I get the information back, I throw it into a database and create my "Registered User". I set my AuthCookie:
FormsAuthentication.SetAuthCookie(user.Profile.MyProfile.DisplayName, false);
Then I can use this for the User Name. However, I would like to pass in the entire object instead of just the string for display name. So my question is:
How does SO do it?
Do they extend/override the SetAuthCookie(string, bool) method to accept the User object, i.e. SetAuthCookie(User(object), bool).
What is the best way to persist a User object so that it is available to my UserControl on every single page of my Web Application?
Thanks in advance!
You can achieve this behavior by implementing your custom Membership Provider, or extending an existing one. The provider stores user information based on a key (or just by user name) and provides access to the MembershipUser class, which you can extend however you wish. So when you call FormsAuthentication.SetAuthCookie(...), you basically set the user key, which can be accessed be the provider.
When you call Membership.GetUser(), the membership infrastructure will invoke the underlying provider and call its GetUser(...) method providing it with a key of the current user. Thus you will receive the current user object.
Jeff,
As I said in a comment to your question above, you must use the ClaimedIdentifier for the username -- that is, the first parameter to SetAuthCookie. There is a huge security reason for this. Feel free to start a thread on dotnetopenid#googlegroups.com if you'd like to understand more about the reasons.
Now regarding your question about an entire user object... if you wanted to send that down as a cookie, you'd have to serialize your user object as a string, then you'd HAVE TO sign it in some way to protect against user tampering. You might also want to encrypt it. Blah blah, it's a lot of work, and you'd end up with a large cookie going back and forth with every web request which you don't want.
What I do on my apps to solve the problem you state is add a static property to my Global.asax.cs file called CurrentUser. Like this:
public static User CurrentUser {
get {
User user = HttpContext.Current.Items["CurrentUser"] as User;
if (user == null && HttpContext.Current.User.Identity.IsAuthenticated) {
user = Database.LookupUserByClaimedIdentifier(HttpContext.Current.User.Identity.Name);
HttpContext.Current.Items["CurrentUser"] = user;
}
return user;
}
}
Notice I cache the result in the HttpContext.Current.Items dictionary, which is specific to a single HTTP request, and keeps the user fetch down to a single hit -- and only fetches it the first time if a page actually wants the CurrentUser information.
So a page can easily get current logged in user information like this:
User user = Global.CurrentUser;
if (user != null) { // unnecessary check if this is a page that users must be authenticated to access
int age = user.Age; // whatever you need here
}
One way is to inject into your controller a class that is responsible for retrieving information for the current logged in user. Here is how I did it. I created a class called WebUserSession which implements an interface called IUserSession. Then I just use dependency injection to inject it into the controller when the controller instance is created. I implemented a method on my interface called, GetCurrentUser which will return a User object that I can then use in my actions if needed, by passing it to the view.
using System.Security.Principal;
using System.Web;
public interface IUserSession
{
User GetCurrentUser();
}
public class WebUserSession : IUserSession
{
public User GetCurrentUser()
{
IIdentity identity = HttpContext.Current.User.Identity;
if (!identity.IsAuthenticated)
{
return null;
}
User currentUser = // logic to grab user by identity.Name;
return currentUser;
}
}
public class SomeController : Controller
{
private readonly IUserSession _userSession;
public SomeController(IUserSession userSession)
{
_userSession = userSession;
}
public ActionResult Index()
{
User user = _userSession.GetCurrentUser();
return View(user);
}
}
As you can see, you will now have access to retrieve the user if needed. Of course you can change the GetCurrentUser method to first look into the session or some other means if you want to, so you're not going to the database all the time.