17

I have a spring boot application with WebSecurityConfigurerAdapter configured like this -

http.csrf().disable()
                    .exceptionHandling()
                    .authenticationEntryPoint(restAuthenticationEntryPoint)
                    .and()
                .authorizeRequests()
                    .antMatchers("/user/*", "/habbit/*").authenticated()
                    .and()
                .formLogin()
                    .loginProcessingUrl("/login")
                    .permitAll()
                    .usernameParameter("email")
                    .passwordParameter("pass")
                    .successHandler(authenticationSuccessHandler)
                    .failureHandler(new SimpleUrlAuthenticationFailureHandler())
                    .and()
                .logout()
                    .logoutUrl("/logout")
                    .invalidateHttpSession(true);

Can I add something like my own controller that would, after successful authentication, return back a custom object with some details about the authenticated user?

Update: To clarity, i'm using an angular application as the client. Currently I need to make 2 requests form my client to the server: 1. POST request to /login URL for authentication. 2. GET request to retrieve authenticated user data.

My aim is to have the 1st request return to me user information so I don't have to make the 2dn request. Currently the 1st request only authenticates the user, creates a session on the server and send back a '200 OK' status response with no data. I want it to return a success response with data about the logged in user.

Answered:

The correct answer is in comments so i will write it here: I needed to redirect from my successHandler to my controller which in turn returns the currently logged in user info ( in my case controller is in url '/user/me':

 @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws ServletException, IOException {
        clearAuthenticationAttributes(request);
        getRedirectStrategy().sendRedirect(request, response, "/user/me");
    }
Tomas
  • 1,212
  • 4
  • 15
  • 26
  • Do you need the user while handling some future request, or do you need an "event" right at the login? (both is supported by spring (security) but it works in complete different ways) - for the first one, have a look at this question: http://stackoverflow.com/q/8764545/280244, for the second have a look at this question http://stackoverflow.com/questions/15493237/how-to-correctly-update-the-login-date-time-after-successful-login-with-spring-s – Ralph Feb 06 '16 at 13:28
  • 1
    I have an angular front-end and after successful authentication POST request I would like to have some user details in the response, to show them in my webpage. – Tomas Feb 06 '16 at 13:35
  • Tell me please: How did you manage login on POST request ? –  Dec 13 '16 at 18:35
  • I submitted the POST request with form-urlencoded header and encoded the request body (userame + password) using jQuery's $.param Code example: `var req = { method: 'POST', url: '/login', headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'}, data: $.param({email: $scope.email, pass: $scope.password}) }; $http(req).then( function(responseData) { // success, response data contains user information }, function() { // failure } );` – Tomas Dec 14 '16 at 07:42

2 Answers2

14

If I understand your problem right, I can suggest next way.

First of all you have to implement class, that will contain user information. This class must be inherited from org.springframework.security.core.userdetails.User:

public class CustomUserDetails extends User {

    public CustomUserDetails(String username, String password,
         Collection<? extends GrantedAuthority> authorities) {            
        super(username, password, authorities);
    }

    //for example lets add some person data        
    private String firstName;
    private String lastName;

    //getters and setters
}

Next step, you have create you own implementation of interface org.springframework.security.core.userdetails.UserDetailsService:

@Service
public class CustomUserDetailService implements UserDetailsService{

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException{         

        if(StringUtils.isEmpty(userName)) 
            throw new UsernameNotFoundException("User name is empty");

        //if you don't use authority based security, just add empty set
        Set<GrantedAuthority> authorities = new HashSet<>();
        CustomUserDetails userDetails = new CustomUserDetails(userName, "", authorities);            

        //here you can load user's data from DB or from 
        //any other source and do:
        //userDetails.setFirstName(firstName);
        //userDetails.setLastName(lastName);

        return userDetails;
    }

}

As you see, this class has just one method, where you can load and set custom user details. Note, that I marked this class with @Service annotation. But you can register it in your Java-config or XML context.

Now, to access your user data after successful authentication, you can use next approach, when Spring will automatically pass principal in controller's method:

@Controller
public class MyController{

    @RequestMapping("/mapping")
    public String myMethod(Principal principal, ModelMap model){
        CustomUserDetails userDetails = (CustomUserDetails)principal;
        model.addAttribute("firstName", userDetails.getFirstName());
        model.addAttribute("lastName", userDetails.getLastName());
    }
}

Or another one way:

@Controller
public class MyController{

    @RequestMapping("/mapping")
    public String myMethod(ModelMap model){
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        CustomUserDetails userDetails = (CustomUserDetails)auth.getPrincipal();
        model.addAttribute("firstName", userDetails.getFirstName());
        model.addAttribute("lastName", userDetails.getLastName());
    }
}

This method can be used in other places, where Spring does not pass principal automatically.

To go to specific address after successful authentication you can use SimpleUrlAuthenticationSuccessHandler. Just create it in your config:

@Bean
public SavedRequestAwareAuthenticationSuccessHandler successHandler() {
    SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    successHandler.setTargetUrlParameter("/succeslogin");
    return successHandler;
}

and use it in your configuration:

http.formLogin()
    .loginProcessingUrl("/login")
    .permitAll()
    .usernameParameter("email")
    .passwordParameter("pass")
    .successHandler(successHandler())

after that you can create controller, that will send response from speciafied url:

@Controller
@RequestMapping("/sucesslogin")
public class SuccessLoginController{

     @RequestMapping(method = RequestMethod.POST)
     public String index(ModelMap model, Principal principal){
         //here you can return view with response
     }

}

Of cause, you can return not only view, but JSON response (using @ResponseBody annotation), or something else, depends on you front-end. Hope this will be helpful.

deepthought-64
  • 566
  • 5
  • 16
Ken Bekov
  • 13,696
  • 3
  • 36
  • 44
  • It would solve my problem if the CustomUserDetails would be returned with the successful authentication response. E.g. I send a POST request, spring authenticated me and returns to me a success response along with this CustomUserDetails object. Is that possible? – Tomas Feb 06 '16 at 14:30
  • Sure, you can redirect from `authenticationSuccessHandler` to controller's methos, and this controller's method can return anything you would like to return. – Ken Bekov Feb 06 '16 at 14:44
  • @KenBekov Can you tell me how to gain login on POST request ? I cant deal with it (httpBasic only) –  Dec 13 '16 at 18:43
  • 1
    Great explanation. I feel so glad when someone explains so good. Thanks. – gschambial Aug 05 '17 at 09:02
  • when /mapping is called and what should need to return from /mapping endpoint and i am not able to redirect even /successlogin and is something need to do in onAuthentication in successhandler? – Bharti Ladumor May 03 '19 at 05:45
  • @KenBekov is this code and example of how to map the security user to a custom user? And how could I pull other AD properties? – Al Grant May 26 '19 at 07:41
  • @KenBekov could you please look at https://stackoverflow.com/questions/56320408/ldap-authentication-with-customuserdertails – Al Grant May 27 '19 at 06:08
9

In the accepted answer you need two calls to get the data you want. Simply return the data after the login in a custom AjaxAuthenticationSuccessHandler like this.

@Bean
public AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() {
    return new AjaxAuthenticationSuccessHandler() {

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.getWriter().write(new ObjectMapper().writeValueAsString(new UserAuthenticationResponse(authentication.getName(), 123l)));
            response.setStatus(200);
        }

    };
}

and register the successhandler:

http.successHandler(ajaxAuthenticationSuccessHandler())
Simon Ludwig
  • 1,754
  • 1
  • 20
  • 27