添加 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]"

非常感谢您的提示,马文!