Spring security post v5 - how to get authentication manager for filter

887 Views Asked by At

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.

1

There are 1 best solutions below

0
CJJ On BEST ANSWER

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.