-1

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.

CJJ
  • 199
  • 1
  • 2
  • 8
  • returning jwts directly to browsers and clients is very insecure, so why are you building an insecure custom security solution? jwts should never be used as sessions and spring security knows this thats why there is no default implementation for it. So why do you want to build an insecure application? – Toerktumlare Aug 31 '23 at 20:25
  • I'm trying to connect two spring boot services. I thought the "standard secure" way was like this: Service 1 sends login post to service 2, service 2 replies with token, subsequent requests from service 1 will contain that JWT. Please let me know how I could make this strategy more secure. – CJJ Sep 01 '23 at 05:19
  • By having an authorization server, and implementing the oauth2 client credential flow. How will you implement service 3? Another login flow and suddenly every service has to login every time with each other service and everyone has their custom login. Thats where there are standards, and in your case it is called ”client credentials flow” – Toerktumlare Sep 01 '23 at 08:00

1 Answers1

0

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.

CJJ
  • 199
  • 1
  • 2
  • 8