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.
CodePudding user response:
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?
CodePudding user response:
I solved it! Marvin Ajcuc, you are partially right! The problem was with the content type in the Angular interceptor. But I removed the content type for this upload file method call. If I set it explicitly to "multipart/form-data" in the Angular interceptor, I get the following error in the Spring Boot console:
"[org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found]"
Thank you very much for your hint, Marvin!