0

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:

  • the auth object returned to angular do have a valid json response
  • i do not read any header values that are supposed to be returned from springboot application

enter image description here

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

enter image description here

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

thanks a lot ! apparently, headers are lazy loaded in angular !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.