Skip to content

Custom Authentication Provider#

Custom Authentication Provider Example#

Configuration#

ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.spring.security.spring.security.custom.authenticationProvider.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     *
     * contact: Not Secure
     * notice: Not Secure
     * balance: Secure
     * Card: Secure
     * Loan: Secure
     * Account: Secure
     *
     */

    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/v1/accounts/**").authenticated()
                .antMatchers("/v1/balance").authenticated()
                .antMatchers("/v1/loan").authenticated()
                .antMatchers("/v1/card").authenticated()
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

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

}

Create Custom Authentication Provider#

  • So, let's create a class with name CustomerAuthenticationProvider which will implements the AuthenticationProvider interface and @Override 2 methods authenticate() and supports() as below:
CustomerAuthenticationProvider.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.spring.security.spring.security.custom.authenticationProvider.config;

import com.spring.security.spring.security.custom.authenticationProvider.entity.CustomerEntity;
import com.spring.security.spring.security.custom.authenticationProvider.repository.CustomerRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class CustomerAuthenticationProvider implements AuthenticationProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomerAuthenticationProvider.class);

    @Autowired
    private CustomerRepository customerRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        LOGGER.info("CustomerAuthenticationProvider is triggered");
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        List<CustomerEntity> customerEntities = this.customerRepository.findByEmail(username);
        if (customerEntities.size() > 0) {
            if (passwordEncoder.matches(password, customerEntities.get(0).getPassword())) {
                List<GrantedAuthority> authorities = new ArrayList<>();
                authorities.add(new SimpleGrantedAuthority(customerEntities.get(0).getRole()));
                return new UsernamePasswordAuthenticationToken(username, password, authorities);
            } else {
                throw new BadCredentialsException("Invalid Password");
            }
        }
        throw new BadCredentialsException("No user registered with this details");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
  • As we know about the Authentication Provider, it is a place where we will implement the business logic to validate the user and perform the authentication.

 #zoom

  • So by default, the AuthenticationProvider will use UserDetails service for getting user information from the database and PasswordEncoder for password validation. But in this custom AuthenticationProvider we can implement our own logic and in this example we will not use the UserDetails to get user information instead we will use a repository to directly get the user information from database. See the method authenticate() above.
  • Then if you look into the method supports() so you can see this provider is only support for authentication object type UsernamePasswordAuthenticationToken.
  • Finally, you can try to put a log into the CustomerAuthenticationProvider and BankUserDetailsService class, to make sure when we run the application then the request will go to filter then it goes to AuthenticationManager then the AuthenticationManager will loop providers and check the supports, then if true it will go to our CustomerAuthenticationProvider and in this class we will get the user without calling the BankUserDetailsService.
BankUserDetailsService.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.spring.security.spring.security.custom.authenticationProvider.service;

import com.spring.security.spring.security.custom.authenticationProvider.entity.CustomerEntity;
import com.spring.security.spring.security.custom.authenticationProvider.model.SecurityCustomer;
import com.spring.security.spring.security.custom.authenticationProvider.repository.CustomerRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BankUserDetailsService implements UserDetailsService {

    private static final Logger LOGGER = LoggerFactory.getLogger(BankUserDetailsService.class);

    @Autowired
    private CustomerRepository customerRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LOGGER.info("BankUserDetailsService is triggered!");
        List<CustomerEntity> customerEntities = this.customerRepository.findByEmail(username);

        if (customerEntities.isEmpty()) {
            throw new UsernameNotFoundException("There are no user with email: " + username);
        }
        return new SecurityCustomer(customerEntities.get(0));
    }
}

Testing#

  • Now, let's start your application and try to execute the request, you should see the results as images below.

 #zoom

  • We can call api successfully. Then if you check the log you can see there is only one log CustomerAuthenticationProvider is triggered. So it mean our custom AuthenticationProvider works correctly and the BankUserDetailsService is not triggered

 #zoom

See Also#

References#