0

I've been trying to encrypt passwords following the tutorial found here https://www.baeldung.com/spring-security-registration-password-encoding-bcrypt. I created my bean, but had the quite common problem that my autowired fields never got injected. I have tried to sort this problem now for quite a while, following various advice on here such as the following resource Why is my Spring @Autowired field null? but none of the help seems to be working, and I'm really not too sure where I am going wrong. I even tried putting the beans in the xml, although I couldn't get this to work either.

Here is my code so far:

User class, where the autowired field is null

@Service
@Configurable
public class User {

    @Id
    private UUID id;
    private long alias;
    private String username;
    private String password;

    @Autowired
    private PasswordEncoder passwordEncoder;

Config class where the bean is getting set up:

@Configuration
public class Config {
    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }
}

I'm quite new to spring so I'm sorry if it's a really glaring fix, I just don't know how else to try make it work. Thank you for any help.

Updated code with qualifiers:

config class:

@Configuration
public class Config {
    @Bean("bCryptPasswordEncoder")
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

user class:

    @Autowired
    @Qualifier("bCryptPasswordEncoder")
    private PasswordEncoder passwordEncoder;

How the user is being instantiated:

@PostMapping("/users")
    public String newUser(@RequestBody User newUser) {
        if (repository.existsByUsername(newUser.getUsername())){
            return new UserAlreadyExistsException(newUser.getUsername()).getMessage(newUser.getUsername(), repository);
        }
        User u = new User(getAlias(repository), newUser.getUsername(), newUser.getPassword());
        repository.save(u);
        return "{\"message\" : \"user\", \"user\" : " + u.toString() + "}";
    }

Meg
  • 11
  • 4
  • 3
    Don't use field injection. Use constructor injection. Your application will fail to start rather than NPE when it tries to access the field. It won't (or at least *shouldn't*) fix your issue, but constructor injection is much safer. It is that the method the Spring team recommend that you use. – Michael Feb 09 '21 at 11:51
  • Put a breakpoint in the `encoder()` method and make sure it's actually being invoked (or less optimally, put a log statement or do a println in there). It's possible that the config class exists in a package that isn't being picked up by Spring's package scan. Just guessing – Michael Feb 09 '21 at 11:56
  • 2
    I doubt that `User` should be managed by Spring and you are probably creating new instances yourself. Also looking at your code and the code in the tutorial you aren't following the tutorial but loosly base your code on that tutorial. – M. Deinum Feb 09 '21 at 12:00
  • Are you able to post the file and package structure of your project? @Michael's suggestion that the config isn't being picked up seems most likely to me. Also, how are you instantiating the `User` class? If it's injected into something else (which works), then this should work, if you're doing `new User()`, then it won't. – DPWork Feb 09 '21 at 13:03
  • Hi, I had a test to make sure it went into the encoder() method and it actually is, so I know it's being picked up in at least that way. I'll add a bit more of the code into the original question about how the user is being instantiated to see if that gives a bit more help? – Meg Feb 09 '21 at 15:06

2 Answers2

0

You are trying to inject interface, spring does not know which implementation to choose from.

To fix this add the following:

@Bean("bCryptPasswordEncoder")
public PasswordEncoder encoder() { 
    return new BCryptPasswordEncoder();
}

and

@Autowired
@Qualifier("bCryptPasswordEncoder")
private PasswordEncoder encoder;

see:

baeldung qualifier

if you are using constructor Injection(highly recommended) you can add the @Qualifier("bCryptPasswordEncoder") in the constuctor before the variable, like so:

public User(@Qualifier("bCryptPasswordEncoder") PasswordEncoder encoder) {
    this.encoder = encoder;
}

P.S.

I would highly recommend using the Spring 5 delegating password encoder for future compatibility

baeldung delegating password encoder examples

P.P.S User is a data member and should not hold password encoder, it is a good design pattern to create separation between data Members and business logic(search spring layer architecture),

If this is a new project I would go a step further and look at the Onion architecture: Onion architecture

P.P.P.s also I would recommend you look at lombok project :)

Roie Beck
  • 1,113
  • 3
  • 15
  • 30
  • Thank you very much for giving me a bunch of really useful information, I will have a look at the constructor injection and switching round the structure after I get it working this way (it's a team project, and I'd rather not have completely changed everything before we next meet), however I tried the qualifiers (and had done before) but it doesn't seem to have fixed anything, and this.passwordEncoder is still null? Thank you again! – Meg Feb 09 '21 at 12:45
  • copy & paste you code with the qualifier please have you added the : @Bean("bCryptPasswordEncoder") to the bean? also keep in mind it is case sensitive – Roie Beck Feb 09 '21 at 12:46
  • have updated the code in the original question! – Meg Feb 09 '21 at 12:56
  • try remove @Configurable and re-run – Roie Beck Feb 09 '21 at 13:00
  • 1
    If there is only one Bean that implements the interface, then autowiring with that interface will work fine. – DPWork Feb 09 '21 at 13:01
  • 1
    @DPWork you are correct, but he posted another question where he used the Noop password encoder, so I guessed... – Roie Beck Feb 09 '21 at 13:04
  • 1
    Don't have any other beans in the project, but I did take out the @Configurable and it still didn't do anything... – Meg Feb 09 '21 at 15:03
0

I created a @Bean in a @Configuration class as you did:

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

And tried to use @Autowired to a private attribute as you did:

@Autowired
private PasswordEncoder encoder;

I was getting an error saying that encoder is null when trying to encode my passwords as well.

But got it fixed following Michael's suggestion (in the comments):

Don't use field injection. Use constructor injection.

Example:

I have a UserConfig class responsible for populating my database with initial data:

public UserConfig(UserRepository repository, PasswordEncoder passwordEncoder) {
    User user1 = new User(
            "firstUser",
            passwordEncoder.encode("123123"),
            "user1@gmail.com"
            /* . . .*/
    );

    User user2 = new User(
            "anotherUser",
            passwordEncoder.encode("111222"),
            "user2@gmail.com"
            /* . . .*/ 
    );

    repository.saveAll(
            List.of(user1, user2)
    );
}

And now it works as expected.

JCarlosR
  • 1,598
  • 3
  • 19
  • 32