'Java HttpParameters getInputStream method empty when sending the request from Angular, but works as expected when sending from Postman

My intention is to use JWT in order to generate auth token. Everything works as expected when I am sending email+password as json from postman, but when I am sending from Angular using HttpClient, I get the following exception:

Method threw 'com.fasterxml.jackson.databind.exc.MismatchedInputException' exception. No content to map due to end-of-input

I also tried to replace line

creds = mapper.readValue(req.getInputStream(), UserBE.class);

with:

String c = IOUtils.toString( req.getInputStream());

This way I noticed that result is empty. (PLEASE SEE SCREENSHOTS)

JwtAuthenticationFilter class:

FAILING LINE: creds = mapper.readValue(req.getInputStream(), UserBE.class);

    private AuthenticationManager authenticationManager;

public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;

    setFilterProcessesUrl("/user/login");
}

@Override
public Authentication attemptAuthentication(HttpServletRequest req,
                                            HttpServletResponse res) throws AuthenticationException {

    ObjectMapper mapper = new ObjectMapper();
    UserBE creds = null;
    try {
        creds = mapper.readValue(req.getInputStream(), UserBE.class); **THIS IS THE LINE WHERE CODE FAILS**
    } catch (IOException e) {
        e.printStackTrace();
    }

    return authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                    creds.getEmail(),
                    creds.getPassword(),
                    new ArrayList<>())
    );
}

@Override
protected void successfulAuthentication(HttpServletRequest req,
                                        HttpServletResponse res,
                                        FilterChain chain,
                                        Authentication auth) throws IOException {
    String token = JWT.create()
            .withSubject(((User) auth.getPrincipal()).getUsername())
            .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
            .sign(Algorithm.HMAC512(SECRET.getBytes()));

    String body = ((User) auth.getPrincipal()).getUsername() + " " + token;

    res.getWriter().write(body);
    res.getWriter().flush();
}

}

WebSecurityConfig class:

private final BCryptPasswordEncoder bCryptPasswordEncoder;

private final UserService userService;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf().disable().authorizeRequests()
            .antMatchers("/user/create").permitAll()
            .antMatchers("/user/confirm/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager()))
            .addFilter(new JwtAuthorizationFilter(authenticationManager()))
            // this disables session creation on Spring Security
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
    config.setAllowedOrigins(Collections.singletonList("*"));
    config.setAllowCredentials(true);
    config.setAllowedOrigins(Collections.singletonList("*"));
    config.setAllowedMethods(Collections.singletonList("*"));
    config.setAllowedHeaders(Collections.singletonList("*"));
    source.registerCorsConfiguration("/**", config);

    return source;
}

}

ANGULAR HttpClient REQUEST:

  login(email: string, password: string): Observable<any> {
let options = {headers: new HttpHeaders().set('Content-Type', 'application/json')};
console.log({email, password});
return this.http.post<any>(this.baseUrl + "/user/login", {email, password}, options);

}

AGAIN, this is working as expected when sending from POSTMAN, but not from Angular. Any thoughts?

SCREENSHOTS:

Exception MESSAGE + EXCEPTION LINE (BLUE HIGHLIGHTED LINE)

Tried a different way. Just to to convert req.getInputStream to String and see what is receiving from Angular Request. Looks Like result is empty. Please also see next screenshot which is the Request from Postman (WHICH WORKES AS EXPECTED)

Showing and proving that request from POSTMAN has a result

Please note that I made researches and tried a lot of things I've found. Nothing helped.



Solution 1:[1]

Found the problem (FINALLY!): I had to add cors on HttpSecurity, and change CORS configuration. So I changed followings:

  1. Added .cors() at the beginning of the expression. See code below:

     http.cors().and().csrf().disable().authorizeRequests()
             .antMatchers("/user/create").permitAll()
             .antMatchers("/user/confirm/**").permitAll()
             .anyRequest().authenticated()
             .and()
             .addFilter(new JwtAuthenticationFilter(authenticationManager()))
             .addFilter(new JwtAuthorizationFilter(authenticationManager()))
             // this disables session creation on Spring Security
             .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
  2. Changed corsConfiguration Bean as following:

         CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
         config.setAllowCredentials(true);
         config.setAllowedOriginPatterns(List.of("*"));
         source.registerCorsConfiguration("/**", config);
    
         return source;
    

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sami H