Spring Session with JDBC | Principal is null

I use Spring Session with JDBC and Mysql. The Spring Security Login is successfully but the principal name is null.

Does anyone have an idea, whats the mistake?

Files:

application.yaml:

  session:
    store-type: jdbc
    jdbc:
      initialize-schema: always
      table-name: SPRING_SESSION

Configuration:

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@EnableJdbcHttpSession
@EnableJpaRepositories(basePackageClasses = UsersRepository.class)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/*").authenticated().anyRequest().permitAll()
                .antMatchers("/sources/*").anonymous().anyRequest().permitAll()
                .antMatchers("/public/*").anonymous().anyRequest().permitAll()
                .and()
                .formLogin().
                loginPage("/login").
                loginProcessingUrl("/app-login").
                usernameParameter("app_username").
                passwordParameter("app_password").
                permitAll()
                .and()
                .exceptionHandling().
                accessDeniedPage("/error403");
    }

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

Here is a picture of the database table: link

Controller Mapping:

    @GetMapping(value = {"/", ""})
    public String start(Model model) {
        if(SecurityContextHolder.getContext().getAuthentication() != null){
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if(auth.getPrincipal() instanceof User){
               User user = (User) auth.getPrincipal();
               model.addAttribute("email", user.getEmail());
            }
        System.out.println(auth.getPrincipal().toString());


        } else {
            model.addAttribute("email", "");
        }

        return "start";
    }

Console Output when I call the URL:

User{id=0, email='null', password='null', firstName='null', lastName='null', active=false, roles=null}

The pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sandbox</groupId>
<artifactId>TestApp</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session</artifactId>
        <version>1.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
    </dependency>
</dependencies>
<properties>
    <java.version>1.8</java.version>
</properties>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <executable>true</executable>
            </configuration>

        </plugin>
    </plugins>
</build>
</project>

My Login Page HTML. Ony The Form. (It is a Bootstrap Page, but to much code than Stackoverflow allows. (custom-login.jsp) :

    <form class="form-signin" action="/app-login" method="POST">
    <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
        <c:if test="${param.error ne null}" >
        <p class="error">
            Invalid username or password!
        </p>
        </c:if>
    <label for="inputEmail" class="sr-only">Email address</label>
    <input type="email" name="app_username" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
    <label for="inputPassword" class="sr-only">Password</label>
    <input type="password" name="app_password" id="inputPassword" class="form-control" placeholder="Password" required>
    <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>

UserDetailService:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersRepository usersRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Optional<User> optionalUsers = usersRepository.findByEmail(email);

        optionalUsers
                .orElseThrow(() -> new UsernameNotFoundException("Username not found"));
        return optionalUsers
                .map(CustomUserDetails::new).get();
    }
}

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

    System.out.println(principal);

Output:

User{id=0, email='null', password='null', firstName='null', lastName='null', active=false, roles=null}

2 answers

  • answered 2019-03-13 17:45 Samim

    Your credentials are getting erased after logging in. You can prevent that but password you won't find from Principle or SecurityContext. Try the below code in the above config file:

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    
        auth.eraseCredentials(false);
        auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    

    UPDATE

    Add the following bean and see the result:

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    

    Also please add pom.xml content to you question.

    UPDATE

    Add these lines to your pom if you are using Thymeleaf:

    <properties>
        <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
        <thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
        <thymeleaf-extras-springsecurity4.version>3.0.4.RELEASE</thymeleaf-extras-springsecurity4.version>
    
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    

    Now the main part, you config file:

    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @EnableWebSecurity
    @EnableJdbcHttpSession
    @EnableJpaRepositories(basePackageClasses = UsersRepository.class)
    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private CustomUserDetailsService userDetailsService;
    
        <strike>@Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }</strike>
    
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    
            auth.eraseCredentials(false);
            auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.csrf().disable();
            http.authorizeRequests()
                    .antMatchers("/*").authenticated().anyRequest().permitAll()
                    .antMatchers("/sources/*").anonymous().anyRequest().permitAll()
                    .antMatchers("/public/*").anonymous().anyRequest().permitAll()
                    .and()
                    .formLogin().
                    loginPage("/login").
                    loginProcessingUrl("/app-login").
                    usernameParameter("app_username").
                    passwordParameter("app_password").
                    permitAll()
                    .and()
                    .exceptionHandling().
                    accessDeniedPage("/error403");
        }
    
        @Bean
        public BCryptPasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    

    Finally the controller:

    @ResponseBody
    @GetMapping("/check")
    public String check(Principal principal) {
    
        System.out.println(principal.getName());
        return "ok";
    }
    

  • answered 2019-03-14 05:04 Patel Romil

    I have created one separate class to get the Details. you can call getCurrentUserDetails() to get the instance of USER

    @Component
    public class CurrentUser {
    
        @Autowired
        UserRepository userRepository;
    
        public User getCurrentUserDetails()
        {
            Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            String currentUsername;
            if (principal instanceof UserDetails) 
            {
                currentUsername = ((UserDetails)principal).getUsername().toString();
                User user = userRepository.findByEmail(currentUsername);
                return user;
            }            
            else 
                //return null or your code;     
        }
    }