Will
Will

Reputation: 71

SpringBoot UserDetailsService loadUserByUsername Method Never Called with custom WebSecurityConfigurerAdapter

I'm following the tutorial in this video: https://www.youtube.com/watch?v=X80nJ5T7YpE&list=PLqq-6Pq4lTTYTEooakHchTGglSvkZAjnE&index=12

My code is at the state of the 7:30 mark. For whatever reason, I can't seem to get the SecurityConfigurer.java Class to recognize my custom UserDetailsService class called MyUserDetailsService.java.

The expected behavior is that when I go to the /hello endpoint I've specified in HelloResource.Java class, a login page should be generated. I should be able to login to the webpage with the loadUserByUsername method's hard-coded return User value with the username and password of "foo".

No matter what change I make though, it always seems that the loadUserByUsername method is not called (I've proven this with breakpoints/print statements). This leads me to believe that there might be some component scanning problem, vut I have yet to see anything work!

I've already tried the solutions at these links: One, Two, Three, plus a few more. Here are my relevant files:

JWTMain.java

package com.JWTTest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("{com.JWTTest}")
public class JwtMain {

    public static void main(String[] args) {
        SpringApplication.run(JwtMain.class, args);
    }
}

HelloResource.java

package com.JWTTest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloResource {

    @RequestMapping("/hello")
    public String HelloString(){
        return "Hello";
    }

}

MyUserDetailsService.java

package com.JWTTest;

import org.jvnet.hk2.annotations.Service;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.ArrayList;

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User("foo", "foo", true, true, true, true, new ArrayList<>());
    }
}

SecurityConfigurer.java

package com.JWTTest;

import org.apache.catalina.security.SecurityConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@ComponentScan(basePackageClasses = MyUserDetailsService.class)
@EnableWebSecurity(debug = true)
@Import(value = {SecurityConfig.class})
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/spring_security
spring.jpa.properties.hibernate.default_schema=users
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

build.gradle

plugins {
    id 'org.springframework.boot' version '2.2.1.RELEASE'
    id 'io.spring.dependency-management' version '1.0.8.RELEASE'
    id 'java'
}

group = 'com.JWTTest'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    implementation 'org.springframework.boot:spring-boot-starter-jersey'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-web-services'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.data:spring-data-rest-hal-browser'
    implementation 'org.springframework.session:spring-session-core'


    implementation 'org.springframework.boot:spring-boot-starter-security'

    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '2.2.1.RELEASE'
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.15'




    compileOnly 'org.projectlombok:lombok'

    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-devtools")

    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'io.projectreactor:reactor-test'

    implementation 'org.springframework.boot:spring-boot-starter-cache'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

}
test {
    useJUnitPlatform()
}

Besides that I can't make heads or tails of what's going on. Let me know, thanks.

Upvotes: 2

Views: 1652

Answers (2)

Will
Will

Reputation: 71

The solution:

@SpringBootApplication
@ComponentScan 

should be in the main method. As long as @EnableWebSecurity is in the SecurityConfigurer class, Springboot is aware that one is trying to override the Springboot provided Security class. The problem with my main method was Springboot wasn't scanning my classes properly.

I also learned that one could exclude the Springboot provided Security class by putting this annotation above the main class:

@SpringBootApplication(exclude = { SecurityAutoConfiguration.class })

Upvotes: 0

MartinBG
MartinBG

Reputation: 1648

In MyUserDetailsService.java you're importing org.jvnet.hk2.annotations.Service while the correct one is org.springframework.stereotype.Service.

Upvotes: 1

Related Questions