spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.81k stars 5.9k forks source link

@AuthenticationPrincipal return empty User #4155

Closed sergio11 closed 7 years ago

sergio11 commented 7 years ago

Summary

I have a Spring MVC controller where I save the post information associated with the currently authenticated user. This current user is obtained by using the @AuthenticationPrincipal annotation in the method next to the User class that implements the UserDetails interface. The problem is that this user has all the empty fields, however if I get the user through SecurityContextHolder this does not happen.

Actual Behavior

When the controller is executed the user obtained by @AuthenticationPrincipal is empty and the one obtained by SecurityContextHoldercontains all the information:

2016-12-10 19:37:52 INFO  PostController:62 - Información del usuario mediate @CurrentUser: User{id=null, username=null, passwordClear=null, confirmPassword=null, password=null, email=null, enabled=true, fullName=null, posts=[]}
2016-12-10 19:37:52 INFO  PostController:63 - Información del usuario mediate SecurityContextHolder: User{id=1, username=sergio11, passwordClear=null, confirmPassword=null, password=$2a$10$LJvYTNacIvqZWDQWjF7xaeheK1MrF.FkXxovb2QgcF2CMudA1mM/., email=sss4esob@gmail.com, enabled=true, fullName=Sergio Sánchez Sánchez, posts=[]}

Expected Behavior

As I understand using @EnableWebSecurity already enable in the context the resolver argument for the @AuthenticationPrincipal.

So I could use this to get the current user managed: @AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)")

And to be able to persist the Post associated with the current user

Configuration

package config.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import services.security.CustomUserDetailsService;
/**
 *
 * @author sergio
 */
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher;

    @Bean
    public PasswordEncoder passwordEncoder() {
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth
            .authenticationEventPublisher(defaultAuthenticationEventPublisher)
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/signup").anonymous()
                .antMatchers("/admin/**").authenticated()
                .anyRequest().permitAll()
                .and()
                .formLogin().loginPage("/admin/login").permitAll()
                .usernameParameter("username").passwordParameter("password")
                .and()
                .logout()
                    .logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"))
                    .logoutSuccessUrl("/admin/login?logout")
                .and()
                .exceptionHandling().accessDeniedPage("/403")
                .and()
                .csrf();
    }

    @Bean
    public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
    }
}
package config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

/**
 *
 * @author sergio
 */
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

}

Version

Spring Security 4.2.0.RELEASE

Sample

Github Repository

Stack Overflow

rwinch commented 7 years ago

Summary

This issue is because DelegatingFlashMessagesConfiguration eagerly initializes the handlerExceptionResolver which prevents the AuthenticationPrincipalArgumentResolver from being registered until after Spring MVC has constructed the DispatcherServlets custom argument resolvers.

Details

DelegatingWebMvcConfiguration the container finds all the WebMvcConfigurer. Then DelegatingFlashMessagesConfiguration is created but it requires the handlerExceptionResolver which adds all the argument resolvers before WebMvcSecurityConfiguration is added as a WebMvcConfigurer.

If you comment out the @EnableFlashMessages it resolves this problem.