- 5.1.3 Applying custom authentication logic
- 5.2 Using the SecurityContext
- 5.2.1 Using a holding strategy for the security context
- 5.2.2 Using a holding strategy for asynchronous calls
- 5.2.3 Using a holding strategy for standalone applications
- 5.2.4 Forwarding the securtiy context with DelegatingSecurityContextRunnable
- 5.2.5 Forwarding the security context with DelegatingSecurityContextExecutorService
- 5.3 Understanding HTTP Basic and form-based login authentications
- 5.3.1 Using and configuring HTTP Basic
- 5.3.2 Implementing authentication with form-based login
5.1.3 Applying custom authentication logic
Overriding the supports methods of the AuthenticationProvider
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
//....
@Override
public boolean supports(Class<?> authenticationType) {
return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
}
}
implementing the authentication logic
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails u = userDetailsService.loadUserByUsername(username);
if (passwordEncoder.matches(password, u.getPassword())) {
return new UsernamePasswordAuthenticationToken(username, password, u.getAuthorities());
} else {
throw new BadCredentialsException("Something went wrong!");
}
}
//...
}
Registering the AuthenticationProvider in the configuration class
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationProvider authenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider);
}
}
5.2 Using the SecurityContext
The SecurityContext interface
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
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
-
5.2.1 Using a holding strategy for the security context
Obtaining the SecurityContext from the SecurityContextHolder
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello")
public String hello() {
SecurityContext context = SecurityContextHolder.getContext();
Authentication a = context.getAuthentication();
return "Hello, " + a.getName() + "!";
}
}
Spring injects Authentication value in the parameter of the method
@GetMapping("/hello")
public String hello(Authentication a) {
return "Hello, " + a.getName() + "!";
}
5.2.2 Using a holding strategy for asynchronous calls
An @Async, the method is executed on a separate thread
@GetMapping("/bye")
@Async
public void goodbye() {
SecurityContext context = SecurityContextHolder.getContext();
String username = context.getAuthentication().getName();
}
To enable the functionality of the @Async annotation, you need must to do: ```java @Configuration @EnableAsync public class ProjectConfig {
}
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
```java
@Configuration
@EnableAsync
public class ProjectConfig {
@Bean
public InitializingBean initializingBean() {
return () -> SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
}
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
@GetMapping("/ciao")
public String ciao() throws Exception {
Callable<String> task = () -> {
SecurityContext context = SecurityContextHolder.getContext();
return context.getAuthentication().getName();
};
//...
}
Defining an ExecutorService and submitting the task
@GetMapping("/ciao")
public String ciao() throws Exception {
Callable<String> task = () -> {
SecurityContext context = SecurityContextHolder.getContext();
return context.getAuthentication().getName();
};
ExecutorService e = Executors.newCachedThreadPool();
try {
var contextTask = new DelegatingSecurityContextCallable<>(task);
return "Ciao, " + e.submit(contextTask).get() + "!";
} finally {
e.shutdown();
}
}
5.2.5 Forwarding the security context with DelegatingSecurityContextExecutorService
Propagating the SecurityContext
@GetMapping("/hola")
public String hola() throws Exception {
Callable<String> task = () -> {
SecurityContext context = SecurityContextHolder.getContext();
return context.getAuthentication().getName();
};
ExecutorService e = Executors.newCachedThreadPool();
e = new DelegatingSecurityContextExecutorService(e);
try {
return "Hola, " + e.submit(task).get() + "!";
} finally {
e.shutdown();
}
}
5.3 Understanding HTTP Basic and form-based login authentications
5.3.1 Using and configuring HTTP Basic
Setting the HTTP Basic authentication method
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
}
}
Configuring the realm name for the response of failed authentications
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic(c -> {
c.realmName("OTHER");
});
http.authorizeRequests().anyRequest().authenticated();
}
}
Implementing an AuthenticationEntryPoint
public class CustomEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
httpServletResponse.addHeader("message", "Luke, I am your father!");
httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value());
}
}
Setting the custion AuthenticationEntryPoint
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic(c -> {
c.realmName("OTHER");
c.authenticationEntryPoint(new CustomEntryPoint());
});
http.authorizeRequests().anyRequest().authenticated();
}
}
5.3.2 Implementing authentication with form-based login
Changing the authentication method to a form-based login
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private CustomAuthenticationFailureHandler authenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
http.authorizeRequests()
.anyRequest().authenticated();
}
}
Defining the action method of the controller for the home.html page
@Controller
public class HelloController {
@GetMapping("/home")
public String home() {
return "home.html";
}
}
Setting a default success URL for the login form
@Override
protected void configure(HttpSecurity http)
throws Exception {
http.formLogin()
.defaultSuccessUrl("/home", true);
http.authorizeRequests()
.anyRequest().authenticated();
}
Implementing an AuthenticationSuccessHandler
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
var authorities = authentication.getAuthorities();
var auth = authorities.stream()
.filter(a -> a.getAuthority().equals("read"))
.findFirst();
if (auth.isPresent()) {
httpServletResponse.sendRedirect("/home");
} else {
httpServletResponse.sendRedirect("/error");
}
}
}
Implementing an AuthenticationFailureHandler
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) {
httpServletResponse.setHeader("failed", LocalDateTime.now().toString());
}
}
Registering the handler objects in the configuration class
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private CustomAuthenticationFailureHandler authenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
http.authorizeRequests()
.anyRequest().authenticated();
}
}
Using form-based login and HTTP Basic together
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private CustomAuthenticationFailureHandler authenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
.httpBasic();
http.authorizeRequests()
.anyRequest().authenticated();
}
}