angularspring-bootfile-uploadspring-security-rest

File upload not working from Angular 8 front-end to the Spring Boot back-end after adding Spring Security


I have a REST Controller in a Spring Boot application which receives a file uploaded from the Angular front-end. Before adding Spring Security it worked fine. After I have added it, it does not work, anymore. The usual GET and POST requests that are receiving or sending JSON data are working with Spring Security, if authenticated in my app. The relevant code snippets:

pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Spring Security config:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired 
    private UserDetailsService userDetailsService;
    
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().cors().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .anyRequest().authenticated().and()
                .httpBasic();
    }
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

REST Controller:

import org.springframework.web.multipart.MultipartFile;

@RestController
@CrossOrigin(origins = "${mvc.client.customer.url}")
@RequestMapping("/api/v1")
public class FileController {
    .......

    @PostMapping("/uploadAndAttach2CustomerEntity2AnalyzeByPhysicalObject")
    public ResponseEntity<DBFile> uploadFileForCE2AByPhysicalObject(@RequestParam("file") MultipartFile file, 
    ....Other params) throws ResourceNotFoundException {
    // File saving logic
    
    }
}

Angular method for posting the file:

  uploadAndAttach2CustomerEntity2AnalyzeByPhysicalObject(file: File, customerEntity2AnalyzeId: string, ce2A_POEAssocId: string, fileType: string): Observable<HttpEvent<any>> {
    var formData: FormData = new FormData();
    formData.append('file', file);
    formData.append('customerEntity2AnalyzeId', customerEntity2AnalyzeId);
    ///Other appended params .....

    const req = new HttpRequest('POST', `${this.baseUrl}/uploadAndAttach2CustomerEntity2AnalyzeByPhysicalObject`, formData, {
      reportProgress: true,
      responseType: 'json',
      //withCredentials: true
    });

    return this.http.request(req);
  }

If I remove "withCredentials: true" I get the following error in the Spring Boot Console: WARN 58704 --- [0.1-8221-exec-9] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.multipart.MultipartException: Current request is not a multipart request]

With "withCredentials: true", the exception in not thrown, but the java method is not reached, anymore, with Spring Security enabled.

My Angular Interceptor (It is called when the post for the file is done):

import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthService } from '../login/auth.service';

@Injectable({
  providedIn: 'root'
})
export class HttpInterceptorService implements HttpInterceptor{
  public SESSION_TOKEN = 'sessionToken';

  constructor(private authenticationService: AuthService) { }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (this.authenticationService.isUserLoggedIn() && req.url.indexOf('basicauth') === -1) {
            const authReq = req.clone({
                headers: new HttpHeaders({
                    'Content-Type': 'application/json',
                    'Authorization': `${sessionStorage.getItem(this.SESSION_TOKEN)}`
                })
            });
            return next.handle(authReq);
        } else {
            return next.handle(req);
        }
    }
}

Authentication Service in Angular:

export class AuthService {
    .....
    
    USER_NAME_SESSION_ATTRIBUTE_NAME = 'authenticatedUser';
    public SESSION_TOKEN = 'sessionToken';

    public username: String;
    public password: String;
    
    authenticationService(username: String, password: String) {
        this.basicAuthToken = this.createBasicAuthToken(username, password)
    
        return this.http.get(AppSettings.ROOT_ADDRESS + `/basicauth`,
          { headers: { authorization: this.createBasicAuthToken(username, password) } }).pipe(map((res) => {
            this.username = username;
            this.password = password;
            this.registerSuccessfulLogin(username, password);
          }));
      }

    createBasicAuthToken(username: String, password: String) {
        return 'Basic ' + window.btoa(username + ":" + password)
    }

    registerSuccessfulLogin(username, password) {
        sessionStorage.setItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME, username);
        sessionStorage.setItem(this.SESSION_TOKEN, this.basicAuthToken);
    }
    
    isUserLoggedIn() {
        let user = sessionStorage.getItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME);
        if (user === null) return false;
        return true;
    }
}

I have disabled CSRF and CORS for the beginning and I have used only BASIC Auth. What I am missing for file upload in order to work after I have added Spring Security? I repeat: It worked without Spring Security and the other GET and POST calls are working with security.


Solution

  • In your angular interceptor you set the content-type header to "application/json" but you send a "multipart/form-data" change that. And when you set "withCredentials: true", what is the http response of the endpoint?