I am using Spring security 6.1.3 and Spring boot 3.1.3. For learning purposes, I am trying to connect to the secured service via Basic Auth and receive a JWT token. I keep getting a null AuthenticationManager, any way I try to create it.
Request
POST http://localhost:8081/login
Basic Auth in header
JSON Payload: {
"username": "shopping_list_username",
"password": "shopping_list_password"
}
ApplicationSecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableGlobalAuthentication
public class ApplicationSecurityConfig {
@Value("${application.access.username}")
private String username;
@Value("${application.access.password}")
private String password;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.addFilter(new JwtRestAPIAuthenticationFilter(httpSecurity.getSharedObject(AuthenticationManager.class)))
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(HttpMethod.GET, "/**").hasAnyAuthority("ADMIN")
.requestMatchers(HttpMethod.GET, "/login").hasAnyAuthority("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.formLogin(withDefaults());
return httpSecurity.build();
}
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User
.withUsername(username)
.password(password)
.authorities("ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
JwtRestAPIAuthenticationFilter.java
public class JwtRestAPIAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public JwtRestAPIAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
UsernameAndPasswordRequest authenticationRequest =
new ObjectMapper().readValue(request.getInputStream(), UsernameAndPasswordRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
return authenticationManager.authenticate(authentication);
} catch(IOException e){
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String token = Jwts.builder()
.setSubject(authResult.getName())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusWeeks(2)))
.signWith(Keys.hmacShaKeyFor(Utils.SECURE_KEY.getBytes()))
.compact();
response.addHeader("Authorization", "Bearer " + token);
}
}
Error
java.lang.NullPointerException: Cannot invoke "org.springframework.security.authentication.AuthenticationManager.authenticate(org.springframework.security.core.Authentication)" because the return value of "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.getAuthenticationManager()" is null
at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:85)
I tried creating it in a bean
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
I tried getting it form HttpSecurity object
http.getSharedObject(AuthenticationManager.class)
I tried to adapt my code to the suggestions in Spring's official migration article but I couldn't make it work.
I found a solution for my problem, not sure if it's best practice, but it works.
I injected AuthenticationConfiguration in the ApplicationSecurityConfig
public class ApplicationSecurityConfig {
private final AuthenticationConfiguration authenticationConfiguration;
@Autowired
public ApplicationSecurityConfig(AuthenticationConfiguration configuration) {
this.authenticationConfiguration = configuration;
}
//......
}
And retrieved the authenticationManager in the Bean:
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
Sent it to the filter by calling the method :
httpSecurity
.addFilter(new JwtRestAPIAuthenticationFilter(authenticationManager()))
Now the jwt is sent back via the response header. After this, I will implement a different service that will call the /login and use the jwt for subsequent requests. Thanks.