添加 Spring 安全后,文件从 Angular 8 前端上传到 Spring 启动后端不起作用
File upload not working from Angular 8 front-end to the Spring Boot back-end after adding Spring Security
我在 Spring 引导应用程序中有一个 REST 控制器,它接收从 Angular 前端上传的文件。在添加 Spring 安全性之前,它工作正常。
添加后,它不再起作用。如果在我的应用程序中经过身份验证,接收或发送 JSON 数据的常用 GET 和 POST 请求正在使用 Spring 安全性。
相关代码片段:
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring 安全配置:
@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 控制器:
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 post 文件的方法:
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);
}
如果删除“withCredentials: true”,我会在 Spring 引导控制台中收到以下错误:
警告 58704 --- [0.1-8221-exec-9] .m.m.a.ExceptionHandlerExceptionResolver:已解决 [org.springframework.web.multipart.MultipartException:当前请求不是多部分请求]
使用“withCredentials: true”时,未抛出异常,但在启用 Spring 安全性的情况下,不再达到 java 方法。
我的 Angular 拦截器(当文件 post 完成时调用):
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);
}
}
}
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;
}
}
我一开始禁用了 CSRF 和 CORS,我只使用了 BASIC Auth。
添加 Spring 安全性后,我缺少什么文件上传才能正常工作?我重复一遍:它在没有 Spring 安全性的情况下工作,其他 GET 和 POST 调用在安全性下工作。
在您的 angular 拦截器中,您将 content-type header 设置为“application/json”,但您发送了一个“multipart/form-data”来更改它。
当您设置“withCredentials: true”时,端点的 http 响应是什么?
我解决了!
Marvin Ajcuc,你说对了一部分!问题出在 Angular 拦截器中的内容类型。但是我删除了这个上传文件方法调用的内容类型。如果我在 Angular 拦截器中将其明确设置为“multipart/form-data”,我会在 Spring 引导控制台中收到以下错误:
"[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]"
非常感谢您的提示,马文!
我在 Spring 引导应用程序中有一个 REST 控制器,它接收从 Angular 前端上传的文件。在添加 Spring 安全性之前,它工作正常。 添加后,它不再起作用。如果在我的应用程序中经过身份验证,接收或发送 JSON 数据的常用 GET 和 POST 请求正在使用 Spring 安全性。 相关代码片段:
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring 安全配置:
@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 控制器:
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 post 文件的方法:
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);
}
如果删除“withCredentials: true”,我会在 Spring 引导控制台中收到以下错误: 警告 58704 --- [0.1-8221-exec-9] .m.m.a.ExceptionHandlerExceptionResolver:已解决 [org.springframework.web.multipart.MultipartException:当前请求不是多部分请求]
使用“withCredentials: true”时,未抛出异常,但在启用 Spring 安全性的情况下,不再达到 java 方法。
我的 Angular 拦截器(当文件 post 完成时调用):
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);
}
}
}
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;
}
}
我一开始禁用了 CSRF 和 CORS,我只使用了 BASIC Auth。 添加 Spring 安全性后,我缺少什么文件上传才能正常工作?我重复一遍:它在没有 Spring 安全性的情况下工作,其他 GET 和 POST 调用在安全性下工作。
在您的 angular 拦截器中,您将 content-type header 设置为“application/json”,但您发送了一个“multipart/form-data”来更改它。 当您设置“withCredentials: true”时,端点的 http 响应是什么?
我解决了! Marvin Ajcuc,你说对了一部分!问题出在 Angular 拦截器中的内容类型。但是我删除了这个上传文件方法调用的内容类型。如果我在 Angular 拦截器中将其明确设置为“multipart/form-data”,我会在 Spring 引导控制台中收到以下错误:
"[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]"
非常感谢您的提示,马文!