简体   繁体   中英

How to correctly retrieve the logged user information from a Spring MVC\Boot Controller method?

I am pretty new in Spring Security and I have the following problem.

I am working on a Spring Boot prject using Spring Security to protect all the resources into /Extranet/ ".

So basically my WebSecurityConfig configuration class contains this code:

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = CustomUserDetailsService.class)
@EnableAutoConfiguration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired 
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/Extranet/**").access("hasRole('ROLE_USER')")
            .anyRequest().permitAll()
            .and()
            .httpBasic()
            .and()
            .csrf().disable();
    }
}

It works fine, infact to access to a resource as /Extranet/login I have to set the Basic Authentication and specify the correct username and password (performing the request using Postman tool to test it).

Ok, this works fine.

In my Spring Security configuration is involed this CustomUserDetails class that implements the Spring Security interface UserDetails .

public class CustomUserDetails extends User implements UserDetails {

    private static final long serialVersionUID = 1L;


    public CustomUserDetails(User user){
        super(user);
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        for(UserRole role : this.getUserRoles() ){
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getName());
            authorities.add(grantedAuthority);
        }

        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String getUsername() {
        return super.getUsername();
    }

}

An instance of this object contains the user details of the user currently logged.

Ok my doubt is: how can I retrieve this object from a controller method? (I think that is should be into the context and that I can retrieve it in some way).

I have tryied to do in this way:

@RestController
@RequestMapping("/Extranet")
public class AccessController {

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public ResponseEntity<String> login(CustomUserDetails userInfo) {

        System.out.println("login() START");

        return ResponseEntity.ok("LOGGED IN");
    }
}

but in this way I obtain an exception like this:

[ERROR] 2017-01-23 14:18:04 [com.betrivius.controller.exceptionHandler.ControllerExceptionHandler.handleException(ControllerExceptionHandler.java:106)] [http-nio-8080-exec-1] ControllerExceptionHandler - Request: http://localhost:8080/Extranet/login  throws: 
org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.betrivius.security.bean.CustomUserDetails]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.betrivius.security.bean.CustomUserDetails.<init>()
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:105) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]

It seems that it can't instantialete the CustomUserDetails object. Why?

From what I know I can retrieve the CustomUserDetails object related to the logged user. How can I do it?

I also try to do in this way:

@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<String> login(Principal principal) {

    System.out.println("login() START");

    return ResponseEntity.ok("LOGGED IN");
}

In this way the principal parameter is correctly instantiated and contains an instance of the previous CustomUserDetails object containing the information of the logged user.

So is it the correct way to access to the current logged user information?

Another doubt is: Why I can pass this Principal principal parameter to my login() method? What exactly happen under the hood? Who is that effectively pass it to the login() method?

I think that should happen something like this:

1) There is a POST HttpRequest toward the"/Extranet/login" resource. The dispatcher servlet send it to the login() method.

2) The Principal principal was put into the Spring Context after that the user was enabled for this resource (before that the controller method was called) so the Spring factory can retrieve it from the context and pass it to the login() method.

But I am absolutly not sure about it. How exactly works? What am I missing?

You probably need then @AuthenticationPrincipal annotation:

@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<String> login(@AuthenticationPrincipal CustomUserDetails userInfo) {

    System.out.println("login() START");

    return ResponseEntity.ok("LOGGED IN");
}

If that still doesn't solve the problem, try debugging using the old method:

@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<String> login() {

    CustomUserDetails userInfo = (CustomerUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    ...
}    

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM