4

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.

  1. Can I get this object as my domain object or do I need to get it as Spring UserDetails object and manually convert it?
  2. 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.

jacekn
  • 1,521
  • 5
  • 29
  • 50

2 Answers2

8

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.

Community
  • 1
  • 1
Shaun the Sheep
  • 22,353
  • 1
  • 72
  • 100
  • java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to com.modelsite.domain.User – jacekn Jan 08 '12 at 01:29
  • If I can't retrieve the User object as my domain User, then it'll be a bit of a pain. From UserDetails, I could figure out what access level I'm dealing with but another piece of info I need is the userId. userName and userId are two different things for me. If I can't retrieve this from session that will mean I need to get the User from the database. Something I'd like to avoid. – jacekn Jan 08 '12 at 01:33
  • You should configure Spring Security with a `UserDetailsService` which returns an instance of your user object (a custom `UserDetails` instance) when the user authenticates. That way it will be directly available from the `SecurityContextHolder` without creating any new intermediate objects. There's more on using a `UserDetailsService` [in the reference manual](http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#tech-userdetailsservice). – Shaun the Sheep Jan 08 '12 at 23:07
0

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){
Vikky
  • 1,123
  • 14
  • 16