Open In App

Spring Security – Authentication Providers

Authentication in Spring Security refers to the process of verifying the identity of a user or a client application attempting to access a protected resource. In other words, it’s the process of validating the user’s credentials (such as username and password) to ensure that they are who they claim to be. Spring Security provides various authentication mechanisms, such as form-based authentication, HTTP Basic authentication, and OAuth, among others. The authentication process typically involves collecting the user’s credentials through a login form, validating them against a user database or authentication provider, and creating a security context that represents the authenticated user.

Importance of Authentication Providers in Spring Security

Authentication providers are an essential part of Spring Security as they play a critical role in verifying user credentials during the authentication process. Here are some of the key reasons why we need authentication providers:



Authentication Providers

Explanation of AuthenticationProvider interface:

1. DaoAuthenticationProvider

Example:



Here’s an example of how to configure the DaoAuthenticationProvider with an in-memory UserDetailsService and a BCryptPasswordEncoder:

1. First, you need to configure a DataSource bean to provide the database connection




@Bean 
public DataSource dataSource()
{
    DriverManagerDataSource dataSource
        = new DriverManagerDataSource();
    dataSource.setDriverClassName("org.postgresql.Driver");
    dataSource.setUrl(
        "jdbc:postgresql://localhost:5432/mydatabase");
    dataSource.setUsername("myuser");
    dataSource.setPassword("mypassword");
    return dataSource;
}

2. Next, you need to create a UserDetailsServicebean that will retrieve user information from the database. Here’s an example implementation




@Service
public class MyUserDetailsService implements UserDetailsService {
  
    private final UserRepository userRepository;
  
    public MyUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
  
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(),
            user.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())
        );
    }
  
}

3. Finally, you can configure DaoAuthenticationProvider in your SecurityConfig class




@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
    private final MyUserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;
  
    public SecurityConfig(MyUserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }
  
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }
  
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }
  
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
  
}

2. LdapAuthenticationProvider

Example: 

Here’s an example of how to configure LdapAuthenticationProvider in Spring Security.

1. Define the LdapContextSource bean to configure the connection to the LDAP server




@Bean
public LdapContextSource contextSource() {
    LdapContextSource contextSource = new LdapContextSource();
    contextSource.setUrl("ldap://localhost:389");
    contextSource.setBase("dc=example,dc=com");
    contextSource.setUserDn("cn=admin,dc=example,dc=com");
    contextSource.setPassword("password");
    return contextSource;
}

2. Define the LdapUserSearch bean to configure how to search for a user in the LDAP server




@Bean
public LdapUserSearch ldapUserSearch() {
    return new FilterBasedLdapUserSearch("ou=users,dc=example,dc=com", "(uid={0})", contextSource());
}

3. Define the LdapAuthoritiesPopulator bean to configure how to retrieve the authorities for a user from the LDAP server




@Bean
public LdapAuthoritiesPopulator authoritiesPopulator() {
    return new DefaultLdapAuthoritiesPopulator(contextSource(), "ou=groups");
}

4. Define the LdapAuthenticationProvider bean that wraps all the previous beans and provides the authentication functionality:




@Bean
public LdapAuthenticationProvider authenticationProvider() {
    return new LdapAuthenticationProvider(ldapAuthenticator(), authoritiesPopulator());
}

5. Configure the AuthenticationManagerBuilder to use the LdapAuthenticationProvider bean




@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProvider());
}

3. OpenIDAuthenticationProvider

Example:

Here’s an example of how to configure OpenIDAuthenticationProvider in Spring Security

1. First, configure an OpenID4JavaConsumer bean to handle the OpenID authentication flow and retrieve user information from the OpenID provider.




@Bean
public OpenID4JavaConsumer openID4JavaConsumer() {
    OpenID4JavaConsumer consumer = new OpenID4JavaConsumer();
    consumer.setReturnToUrl(env.getProperty("security.openid.returnToUrl"));
    consumer.setRealm(env.getProperty("security.openid.realm"));
    consumer.setProviderUrl(env.getProperty("security.openid.providerUrl"));
    return consumer;
}

2. Next, create an OpenIDAuthenticationProvider bean and configure it with the OpenID4JavaConsumer bean.




@Bean
public OpenIDAuthenticationProvider openIDAuthenticationProvider() {
    OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
    provider.setConsumer(openID4JavaConsumer());
    return provider;
}

3. Add the OpenIDAuthenticationProvider to the AuthenticationManagerBuilder to handle OpenID authentication requests.




@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(openIDAuthenticationProvider());
}

4. Finally, configure the OpenIDAuthenticationFilter to intercept and handle OpenID authentication requests.




@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // Other security configuration...
        .addFilterAfter(openIDAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
        // Other security configuration...
}
  
@Bean
public OpenIDAuthenticationFilter openIDAuthenticationFilter() {
    OpenIDAuthenticationFilter filter = new OpenIDAuthenticationFilter();
    filter.setConsumer(openID4JavaConsumer());
    filter.setAuthenticationManager(authenticationManager());
    filter.setAuthenticationSuccessHandler(openIDAuthenticationSuccessHandler());
    filter.setAuthenticationFailureHandler(openIDAuthenticationFailureHandler());
    return filter;
}
  
@Bean
public AuthenticationSuccessHandler openIDAuthenticationSuccessHandler() {
    // Define a success handler to handle successful OpenID authentication
}
  
@Bean
public AuthenticationFailureHandler openIDAuthenticationFailureHandler() {
    // Define a failure handler to handle failed OpenID authentication
}

4. JwtAuthenticationProvider

Example:

Here’s an example of how to configure JwtAuthenticationProvider in Spring Security:

1. First, configure a JwtDecoder bean that will be used to decode the JWT and verify its signature.




@Bean
public JwtDecoder jwtDecoder() {
    String secret = "mySecretKey";
    return NimbusJwtDecoder.withSecretKey(new SecretKeySpec(secret.getBytes(), SignatureAlgorithm.HS256.getJcaName()))
            .build();
}

2. Next, create a JwtAuthenticationProvider bean and configure it with the JwtDecoder bean.




@Bean
public JwtAuthenticationProvider jwtAuthenticationProvider() {
    return new JwtAuthenticationProvider(jwtDecoder());
}

3. Add the JwtAuthenticationProvider to the AuthenticationManagerBuilder to handle JWT authentication requests.




@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(jwtAuthenticationProvider());
}

4. Finally, configure the JwtAuthenticationFilter to intercept and handle JWT authentication requests.




@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // Other security configuration...
        .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        // Other security configuration...
}
  
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
    JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
    filter.setAuthenticationManager(authenticationManager());
    filter.setAuthenticationSuccessHandler(jwtAuthenticationSuccessHandler());
    filter.setAuthenticationFailureHandler(jwtAuthenticationFailureHandler());
    return filter;
}
  
@Bean
public AuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
    // Define a success handler to handle successful JWT authentication
}
  
@Bean
public AuthenticationFailureHandler jwtAuthenticationFailureHandler() {
    // Define a failure handler to handle failed JWT authentication
}

5. RememberMeAuthenticationProvider

Example:

Here’s an example of how to configure RememberMeAuthenticationProvider in Spring Security:

1. Define a UserDetailsService implementation that retrieves user details from your application’s database:




@Service
public class UserDetailsServiceImpl implements UserDetailsService {
      
    @Autowired
    private UserRepository userRepository;
  
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                Collections.singletonList(new SimpleGrantedAuthority(user.getRole().name())));
    }
}

2. Configure authentication using the UserDetailsService implementation:




@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
    @Autowired
    private UserDetailsService userDetailsService;
  
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
  
    // ...
}

3. Define a RememberMeAuthenticationProvider bean:




@Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
    return new RememberMeAuthenticationProvider("myAppKey");
}

4. Define a PersistentTokenRepository implementation that stores remember-me tokens in your application’s database:




@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    tokenRepository.setDataSource(dataSource); // configure DataSource
    return tokenRepository;
}

5. Define a TokenBasedRememberMeServices bean that uses the RememberMeAuthenticationProvider and PersistentTokenRepository:




@Bean
public TokenBasedRememberMeServices rememberMeServices() {
    TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("myAppKey", userDetailsService,
            persistentTokenRepository());
    rememberMeServices.setTokenValiditySeconds(604800); // one week
    return rememberMeServices;
}

6. Configure remember-me authentication in your HttpSecurity configuration:




@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/dashboard")
            .and()
        .rememberMe()
            .key("myAppKey")
            .rememberMeServices(rememberMeServices())
            .and()
        .logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/public")
            .and()
        .exceptionHandling()
            .accessDeniedPage("/403")
            .and()
        .csrf()
            .disable();
}

Custom Authentication Provider

Explanation of how to create custom authentication and authorization providers:

1. Create a new class that implements the AuthenticationProvider interface.




public class CustomAuthenticationProvider implements AuthenticationProvider {
      
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // TODO: Implement authentication logic
    }
  
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

2. Implement the authenticate(Authentication authentication) method. This method takes an Authentication object as input and returns an Authentication object as output.




public class CustomAuthenticationProvider implements AuthenticationProvider {
      
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
  
        // TODO: Implement authentication logic
  
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new UsernamePasswordAuthenticationToken(username, password, authorities);
    }
  
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

3. In the authenticate() method, implement the logic to authenticate the user based on the provided credentials. This can be done by querying a database or an external authentication service.




public class CustomAuthenticationProvider implements AuthenticationProvider {
      
    @Autowired
    private UserService userService;
  
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
  
        User user = userService.findByUsername(username);
  
        if (user == null || !password.equals(user.getPassword())) {
            throw new BadCredentialsException("Invalid username or password");
        }
  
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new UsernamePasswordAuthenticationToken(username, password, authorities);
    }
  
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

4. Implement the supports(Class<?> authentication) method. This method returns true if the authentication provider supports the specified authentication token class. In this example, we’re only supporting the UsernamePasswordAuthenticationToken class.




public class CustomAuthenticationProvider implements AuthenticationProvider {
      
    @Autowired
    private UserService userService;
  
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // TODO: Implement authentication logic
    }
  
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

5. Inject the custom authentication provider into your AuthenticationManagerBuilder in your Spring Security configuration.




@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;
  
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(customAuthenticationProvider);
    }
  
    // ...
}

Conclusion


Article Tags :