5.1.3 Applying custom authentication logic

Overriding the supports methods of the AuthenticationProvider

  1. @Component
  2. public class CustomAuthenticationProvider implements AuthenticationProvider {
  3. //....
  4. @Override
  5. public boolean supports(Class<?> authenticationType) {
  6. return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
  7. }
  8. }

implementing the authentication logic

  1. @Component
  2. public class CustomAuthenticationProvider implements AuthenticationProvider {
  3. @Autowired
  4. private UserDetailsService userDetailsService;
  5. @Autowired
  6. private PasswordEncoder passwordEncoder;
  7. @Override
  8. public Authentication authenticate(Authentication authentication) {
  9. String username = authentication.getName();
  10. String password = authentication.getCredentials().toString();
  11. UserDetails u = userDetailsService.loadUserByUsername(username);
  12. if (passwordEncoder.matches(password, u.getPassword())) {
  13. return new UsernamePasswordAuthenticationToken(username, password, u.getAuthorities());
  14. } else {
  15. throw new BadCredentialsException("Something went wrong!");
  16. }
  17. }
  18. //...
  19. }

Registering the AuthenticationProvider in the configuration class

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. private AuthenticationProvider authenticationProvider;
  5. @Override
  6. protected void configure(AuthenticationManagerBuilder auth) {
  7. auth.authenticationProvider(authenticationProvider);
  8. }
  9. }

5.2 Using the SecurityContext

The SecurityContext interface

  1. public interface SecurityContext extends Serializable {
  2. Authentication getAuthentication();
  3. void setAuthentication(Authentication authentication);
  4. }

Spring Security offers three strategies to manage the SecurityContext with an object in the role of a manager. It’s named the SecurityContextHolder:

  • MODE_THREADLOCAL
  • MODE_INHERITABLETHREADLOCAL
  • MODE_GLOBAL

    5.2.1 Using a holding strategy for the security context

    Obtaining the SecurityContext from the SecurityContextHolder

    1. @RestController
    2. public class HelloController {
    3. @Autowired
    4. private HelloService helloService;
    5. @GetMapping("/hello")
    6. public String hello() {
    7. SecurityContext context = SecurityContextHolder.getContext();
    8. Authentication a = context.getAuthentication();
    9. return "Hello, " + a.getName() + "!";
    10. }
    11. }

    Spring injects Authentication value in the parameter of the method

    1. @GetMapping("/hello")
    2. public String hello(Authentication a) {
    3. return "Hello, " + a.getName() + "!";
    4. }

    5.2.2 Using a holding strategy for asynchronous calls

    An @Async, the method is executed on a separate thread

    1. @GetMapping("/bye")
    2. @Async
    3. public void goodbye() {
    4. SecurityContext context = SecurityContextHolder.getContext();
    5. String username = context.getAuthentication().getName();
    6. }

    To enable the functionality of the @Async annotation, you need must to do: ```java @Configuration @EnableAsync public class ProjectConfig {

}

  1. If you try the code as it is now, it throws a NullPointerException <br />you could solve the problem by using the MODE_INHERITABLETHREADLOCAL strategy.<br />This can be set either by calling the SecurityContextHolder.setStrategyName() method or by using the system property spring.security.strategy<br />Using InitializingBean to set SecurityContextHolder mode
  2. ```java
  3. @Configuration
  4. @EnableAsync
  5. public class ProjectConfig {
  6. @Bean
  7. public InitializingBean initializingBean() {
  8. return () -> SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
  9. }
  10. }

5.2.3 Using a holding strategy for standalone applications

5.2.4 Forwarding the securtiy context with DelegatingSecurityContextRunnable

Defining a Callable object and executing it as a task on a separate thread

  1. @GetMapping("/ciao")
  2. public String ciao() throws Exception {
  3. Callable<String> task = () -> {
  4. SecurityContext context = SecurityContextHolder.getContext();
  5. return context.getAuthentication().getName();
  6. };
  7. //...
  8. }

Defining an ExecutorService and submitting the task

  1. @GetMapping("/ciao")
  2. public String ciao() throws Exception {
  3. Callable<String> task = () -> {
  4. SecurityContext context = SecurityContextHolder.getContext();
  5. return context.getAuthentication().getName();
  6. };
  7. ExecutorService e = Executors.newCachedThreadPool();
  8. try {
  9. var contextTask = new DelegatingSecurityContextCallable<>(task);
  10. return "Ciao, " + e.submit(contextTask).get() + "!";
  11. } finally {
  12. e.shutdown();
  13. }
  14. }

5.2.5 Forwarding the security context with DelegatingSecurityContextExecutorService

image.png
Propagating the SecurityContext

  1. @GetMapping("/hola")
  2. public String hola() throws Exception {
  3. Callable<String> task = () -> {
  4. SecurityContext context = SecurityContextHolder.getContext();
  5. return context.getAuthentication().getName();
  6. };
  7. ExecutorService e = Executors.newCachedThreadPool();
  8. e = new DelegatingSecurityContextExecutorService(e);
  9. try {
  10. return "Hola, " + e.submit(task).get() + "!";
  11. } finally {
  12. e.shutdown();
  13. }
  14. }

5.3 Understanding HTTP Basic and form-based login authentications

5.3.1 Using and configuring HTTP Basic

Setting the HTTP Basic authentication method

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http.httpBasic();
  6. }
  7. }

Configuring the realm name for the response of failed authentications

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http.httpBasic(c -> {
  6. c.realmName("OTHER");
  7. });
  8. http.authorizeRequests().anyRequest().authenticated();
  9. }
  10. }

Implementing an AuthenticationEntryPoint

  1. public class CustomEntryPoint implements AuthenticationEntryPoint {
  2. @Override
  3. public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
  4. httpServletResponse.addHeader("message", "Luke, I am your father!");
  5. httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value());
  6. }
  7. }

Setting the custion AuthenticationEntryPoint

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http.httpBasic(c -> {
  6. c.realmName("OTHER");
  7. c.authenticationEntryPoint(new CustomEntryPoint());
  8. });
  9. http.authorizeRequests().anyRequest().authenticated();
  10. }
  11. }

5.3.2 Implementing authentication with form-based login

Changing the authentication method to a form-based login

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
  5. @Autowired
  6. private CustomAuthenticationFailureHandler authenticationFailureHandler;
  7. @Override
  8. protected void configure(HttpSecurity http) throws Exception {
  9. http.formLogin()
  10. http.authorizeRequests()
  11. .anyRequest().authenticated();
  12. }
  13. }

Defining the action method of the controller for the home.html page

  1. @Controller
  2. public class HelloController {
  3. @GetMapping("/home")
  4. public String home() {
  5. return "home.html";
  6. }
  7. }

Setting a default success URL for the login form

  1. @Override
  2. protected void configure(HttpSecurity http)
  3. throws Exception {
  4. http.formLogin()
  5. .defaultSuccessUrl("/home", true);
  6. http.authorizeRequests()
  7. .anyRequest().authenticated();
  8. }

Implementing an AuthenticationSuccessHandler

  1. @Component
  2. public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
  3. @Override
  4. public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
  5. var authorities = authentication.getAuthorities();
  6. var auth = authorities.stream()
  7. .filter(a -> a.getAuthority().equals("read"))
  8. .findFirst();
  9. if (auth.isPresent()) {
  10. httpServletResponse.sendRedirect("/home");
  11. } else {
  12. httpServletResponse.sendRedirect("/error");
  13. }
  14. }
  15. }

Implementing an AuthenticationFailureHandler

  1. @Component
  2. public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
  3. @Override
  4. public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) {
  5. httpServletResponse.setHeader("failed", LocalDateTime.now().toString());
  6. }
  7. }

Registering the handler objects in the configuration class

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
  5. @Autowired
  6. private CustomAuthenticationFailureHandler authenticationFailureHandler;
  7. @Override
  8. protected void configure(HttpSecurity http) throws Exception {
  9. http.formLogin()
  10. .successHandler(authenticationSuccessHandler)
  11. .failureHandler(authenticationFailureHandler)
  12. http.authorizeRequests()
  13. .anyRequest().authenticated();
  14. }
  15. }

Using form-based login and HTTP Basic together

  1. @Configuration
  2. public class ProjectConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
  5. @Autowired
  6. private CustomAuthenticationFailureHandler authenticationFailureHandler;
  7. @Override
  8. protected void configure(HttpSecurity http) throws Exception {
  9. http.formLogin()
  10. .successHandler(authenticationSuccessHandler)
  11. .failureHandler(authenticationFailureHandler)
  12. .and()
  13. .httpBasic();
  14. http.authorizeRequests()
  15. .anyRequest().authenticated();
  16. }
  17. }