davidvera
davidvera

Reputation: 1489

Angular not reading headers from Springboot response

I created an API that handles users and if authentication is successful, I do return user information to front end using http servlet response:

@Override
protected void successfulAuthentication(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain chain,
                                        Authentication authResult) {

    // String userName = ((User) authResult.getPrincipal()).getUsername();
    String userName = ((UserPrincipalManager) authResult.getPrincipal()).getUsername();

    String[] claims = getClaimsFromUser((UserPrincipalManager) authResult.getPrincipal());

    String token = JWT.create()
            .withIssuer(SecurityConstants.TOKEN_ISSUER)
            .withAudience(SecurityConstants.TOKEN_AUDIENCE)
            .withIssuedAt(new Date())
            .withSubject(userName)
            .withArrayClaim(SecurityConstants.AUTHORITIES, claims)
            .withExpiresAt(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
            .sign(Algorithm.HMAC256(SecurityConstants.getTokenSecret().getBytes()));


    UserService userService = (UserService) SpringApplicationContext.getBean("userServiceImpl");
    UserDto userDto = userService.getUser(userName);

    try {
        UserResponse userResponse = new ModelMapper().map(userDto, UserResponse.class);
        response.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
        response.addHeader(SecurityConstants.USER_ID, userDto.getUserId());
        response.getWriter().write(new ObjectMapper().writeValueAsString(userResponse));
        response.getWriter().flush();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

As I use spring security, I implemented the following configuration:

@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
    // Configure AuthenticationManagerBuilder
    AuthenticationManagerBuilder authenticationManagerBuilder =
            http.getSharedObject(AuthenticationManagerBuilder.class);

    authenticationManagerBuilder.userDetailsService(userDetailsService)
            .passwordEncoder(bCryptPasswordEncoder);
    AuthenticationManager authenticationManager = authenticationManagerBuilder.build();

    // Create AuthenticationFilter
    AuthenticationFilter authenticationFilter = getAuthenticationFilter(authenticationManager);

    IpAddressMatcher hasIpAddress = new IpAddressMatcher(Objects.requireNonNull(environment.getProperty("gateway.ip")));
    IpAddressMatcher hasIpv4Address = new IpAddressMatcher(Objects.requireNonNull(environment.getProperty("gateway.ipv4")));

    return http
            //.cors(AbstractHttpConfigurer::disable)
            //.cors(corsCustomizer -> corsCustomizer.configurationSource(corsConfigurationSource()))
             .cors(Customizer.withDefaults())
            //.csrf((csrf) -> csrf.disable())
            .csrf(AbstractHttpConfigurer::disable)
            // Aucune url n'est disponible sans passer par le gateway si le gateway est sur un autre serveur
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    .requestMatchers("/**")
                            .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))

                            // on autorise tous les points d'entrée d'actuator
                    .requestMatchers(new AntPathRequestMatcher("/actuator/**"))
                    .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
                    .requestMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
                    .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
                    .requestMatchers(HttpMethod.GET, SecurityConstants.EMAIL_VERIFICATION_URL)
                    //.permitAll()
                    .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
                    .requestMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_REQUEST_URL)
                    .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
                    //.permitAll()
                    .requestMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_URL)
                    .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
                    //.permitAll()
                    .requestMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**", "/v3/api-docs/**").permitAll()
                    // on peut appeler cette url directement
                    .requestMatchers(HttpMethod.GET, "/hello")
                    .access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
                    //.permitAll()
                    //.anyRequest().access((authentication, context) -> {
                    //    if(authentication.get().isAuthenticated())
                    //             return getAccess(authentication, context, hasIpAddress, hasIpv4Address);
                    //    return new AuthorizationDecision(false);
                    //})
            )
            .addFilterAfter(authenticationFilter, AuthenticationFilter.class)
            .addFilterAfter(new AuthorizationFilter(authenticationManager, jwtTokenProvider), AuthorizationFilter.class)
            .authenticationManager(authenticationManager)
            .sessionManagement((session) -> session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .build();
}

private AuthorizationDecision getAccess(
        Supplier<Authentication> authentication,
        RequestAuthorizationContext context,
        IpAddressMatcher hasIpAddress,
        IpAddressMatcher hasIpv4Address) {
    if(hasIpAddress.matches(context.getRequest()))
        return new AuthorizationDecision(
                hasIpAddress.matches(context.getRequest()));
    return new AuthorizationDecision(
            hasIpv4Address.matches(context.getRequest()));
}

// customize login URL
public AuthenticationFilter getAuthenticationFilter(AuthenticationManager authenticationManager) {
    final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager, servletContext);
    filter.setFilterProcessesUrl(SecurityConstants.SIGN_IN_URL);
    return filter;
}

// cors configuration source
@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOriginPatterns(List.of("*"));
    //configuration.setAllowedOrigins(List.of("*"));
    configuration.setAllowedMethods(List.of("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS", "HEAD"));
    configuration.setAllowedHeaders(List.of("Content-Type", "Authorization", "Origin", "x-access-token", "XSRF-TOKEN", "userId"));
    configuration.addAllowedHeader("Authorization");
    configuration.addAllowedHeader("userId");
    configuration.addExposedHeader("Authorization");
    configuration.addExposedHeader("userId");
    configuration.setMaxAge(3600L);
    configuration.setAllowCredentials(Boolean.TRUE);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

I also added cors

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
                .addMapping("/**")
                .exposedHeaders("Authorization", "userId")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS", "HEAD")
                .allowedHeaders("Content-Type", "Authorization", "Origin", "x-access-token", "XSRF-TOKEN", "userId")
                .exposedHeaders("Authorization", "userId")
                .maxAge(3600L)
                .allowCredentials(Boolean.TRUE);
    }
}

So far, so good... In front end I have a login component (using angular 17). My submit button, is just redirecting user after authentication.

submit() { this.hasError = false; // this.router.navigate(['../admin/dashboard']) const loginSubscr = this.authService .login(this.f.email.value, this.f.password.value) .pipe(first()) .subscribe((user: UserModel | undefined) => { if (user) { this.router.navigate([this.returnUrl]); } else { this.hasError = true; } }); this.unsubscribe.push(loginSubscr); }

My auth service is retrieving the response from server and store data in local storage...

login(email: string, password: string): Observable<UserType> {
  this.isLoadingSubject.next(true);
  return this.authHttpService.login(email, password)
    .pipe(
      map((auth: any) => {
        console.log("map here")
        const result = this.setAuthFromLocalStorage(auth);
        return result;
      }),
      switchMap(() => this.getUserByToken()),
      catchError((err) => {
        console.error('err', err);
        return of(undefined);
      }),
      finalize(() => this.isLoadingSubject.next(false))
  );
}

Now, my http request is a simple post :

login(email: string, password: string): Observable<any> {
  const headers = new HttpHeaders(
      { 'Content-Type': 'application/json' }
  );
  //const observe = "response" as 'body';
  const observe = "response";
  const responseType = "json";

  const options: object = {
    headers: headers,
    observe: observe,
    responseType: responseType
  };

  let body = {
    'email': email,
    'password': password
  }

  return this.http.post<AuthModel>(API_USERS_URL + "/login", body,  options);
}

Now my issue is the following:

enter image description here

But when reading data in network console in Chrome, the headers are correctly retrieved:

enter image description here

Upvotes: 0

Views: 182

Answers (1)

Oscar F
Oscar F

Reputation: 1149

You can't see headers of you http call like it on chrome console network tab, because differents headers not available in HttpHeaders method as field ( Even less not public field ). you need to call a method to get them. try this in service call subscriber to get all http headers name: resp => resp.headers.keys().

Another way, if you want to get as example Authorization header value use resp.headers.get('Authorization'). Hope this could be helpful.

Upvotes: 2

Related Questions