I’m having a problem uploading an image to AWS S3 through Spring server.
The error is: The authorization header is malformed; the authorization component "Credential== {access-key}/{YYYYMMDD}/{region}/s3/aws4_request" is malformed. (Service: Amazon S3; Status Code: 400; Error Code: AuthorizationHeaderMalformed; Request ID: {Request ID}; S3 Extended Request ID: {Extended Request ID}; Proxy: null)
and my code
gradle
implementation 'org.springframework.cloud:spring-cloud-starter-aws:3.0.3'
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.638'
S3Config
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3 amazonS3(){
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}
}
ImageUploadService (The import statement was omitted considering the length)
@Slf4j
@RequiredArgsConstructor
@Component
public class ImageUploadService {
private final AmazonS3 amazonS3;
@Value("${cloud.aws.s3.bucketName}")
private String bucketName;
public String upload(MultipartFile image){
if(Objects.isNull(image) || image.isEmpty() || Objects.isNull(image.getOriginalFilename())){
throw new S3ImageException(ErrorCode.IMAGE_NOT_EXISTS);
}
return this.uploadImage(image);
}
private String uploadImage(MultipartFile image){
this.validateImageFileExtension(Objects.requireNonNull(image.getOriginalFilename()));
try{
return this.uploadImageToS3(image);
}catch (IOException e){
throw new S3ImageException(ErrorCode.IO_EXCEPTION_ON_IMAGE_UPLOAD);
}
}
private void validateImageFileExtension(String fileName){
int lastDotIndex = fileName.lastIndexOf(".");
if (lastDotIndex == -1) {
throw new S3ImageException(ErrorCode.EXTENSION_NOT_EXISTS);
}
String extension = fileName.substring(lastDotIndex + 1).toLowerCase();
List<String> allowedExtensionList = List.of("jpg", "jpeg", "png", "gif");
if (!allowedExtensionList.contains(extension)) {
throw new S3ImageException(ErrorCode.INVALID_IMAGE_FILE_EXTENSION);
}
}
private String uploadImageToS3(MultipartFile image) throws IOException {
String originalFileName = image.getOriginalFilename();
String extension = Objects.requireNonNull(originalFileName).substring(originalFileName.lastIndexOf("."));
String s3FileName = UUID.randomUUID().toString().substring(0, 10) + originalFileName;
InputStream is = image.getInputStream();
byte[] bytes = IOUtils.toByteArray(is);
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("image/" + extension);
metadata.setContentLength(bytes.length);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// point of error
try{
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, s3FileName, byteArrayInputStream, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead);
amazonS3.putObject(putObjectRequest); // <- point of error
}catch(Exception e){
log.error(e.getMessage());
throw new S3ImageException(ErrorCode.PUT_IMAGE_EXCEPTION);
}finally{
byteArrayInputStream.close();
is.close();
}
return amazonS3.getUrl(bucketName, s3FileName).toString();
}
public void deleteImageFromS3(String imageAddress){
String key = getKeyFromImageAddress(imageAddress);
try{
amazonS3.deleteObject(new DeleteObjectRequest(bucketName, key));
}catch (Exception e) {
throw new S3ImageException(ErrorCode.IO_EXCEPTION_ON_IMAGE_DELETE);
}
}
private String getKeyFromImageAddress(String imageAddress){
try{
URL url = new URL(imageAddress);
String decodingKey = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8);
return decodingKey.substring(1);
}catch (MalformedURLException e){
throw new S3ImageException(ErrorCode.IO_EXCEPTION_ON_IMAGE_DELETE);
}
}
}
ImageUploadCOntroller
@RestController
@CrossOrigin(origins = "http://localhost:3000")
@RequiredArgsConstructor
@RequestMapping(value = "${requestMapping.imageUpload}", produces="application/json;charset=UTF-8")
public class ImageUploadController {
final ImageUploadService imageUploadService;
@PostMapping("${imageUpload.testUrl}")
public ResponseEntity<String> uploadImage(@RequestPart(value = "image", required = false)MultipartFile image){
return ResponseEntity.ok().body(imageUploadService.upload(image));
}
}
What I did:
1. I tried changing the upload method of ImageUploadService. But it doesn’t work
public String upload(MultipartFile multipartFile) throws IOException{
String fileName = UUID.randomUUID() + "-" + multipartFile.getOriginalFilename();
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(multipartFile.getInputStream().available());
amazonS3.putObject(bucketName, fileName, multipartFile.getInputStream(), objectMetadata);
return amazonS3.getUrl(bucketName, fileName).toString();
}
2. I checked whether accessKey, secretKey, region, and bucketName are correct. But it doesn’t work
3. I deleted the S3 bucket and AWS IAM account, created it again, and ran the original code. But it doesn’t work