I have a problem about fixing 401 Unauthorized issue when I upload a file through Cloudflare R2 Storage in my Spring Boot example.
I get Token value, access key, secret key and jurisdiction-specific endpoints for S3 clients from Account API Tokens of R2 Storage.
Bucket name is stored in Eastern Europe (EEUR)
Here are the dependencies defined in pom.xml:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${cloudflare-aws-sdk-version}</version>
<scope>compile</scope>
</dependency>
<!-- AWS SDK’s Apache HTTP-client integration -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
<version>${cloudflare-aws-sdk-version}</version>
</dependency>
<!-- force the httpclient version to 4.5.13 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${apache-httpclient.version}</version>
</dependency>
Here is the application.yml file:
cloudflare:
r2:
endpoint: https://<account-id>.r2.cloudflarestorage.com
accessKey: access-key
secretKey: secret-key
bucket: buckey-name
Here is the CloudflareProperties class:
@Getter
@Setter
@ConfigurationProperties(prefix = "cloudflare.r2")
public class CloudflareProperties {
private String endpoint;
private String accessKey;
private String secretKey;
}
This is the CloudflareR2Config class:
@Configuration
@EnableConfigurationProperties(CloudflareProperties.class)
@RequiredArgsConstructor
public class CloudflareR2Config {
private final CloudflareProperties cloudflareProperties;
@Bean
public S3Client s3Client() {
return S3Client.builder()
.httpClientBuilder(ApacheHttpClient.builder())
.region(Region.of("eeur"))
.endpointOverride(URI.create(cloudflareProperties.getEndpoint()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(
cloudflareProperties.getAccessKey(),
cloudflareProperties.getSecretKey())))
.serviceConfiguration(S3Configuration.builder().pathStyleAccessEnabled(true).build())
.build();
}
}
And this is the service class:
@Service
@RequiredArgsConstructor
public class MediaService {
private final S3Client s3Client;
@Value("${cloudflare.r2.bucket}")
private String bucket;
public String uploadFile(MultipartFile file) {
String originalFilename = Optional.ofNullable(file.getOriginalFilename())
.orElseThrow(() -> new UnsupportedMediaTypeException("Filename is missing"))
.toLowerCase();
String contentType = Optional.ofNullable(file.getContentType())
.orElseThrow(() -> new UnsupportedMediaTypeException("Content-Type is unknown"));
String folder = switch (getFileExtension(originalFilename)) {
case "jpg", "jpeg", "png" -> "images";
case "mp4", "mov" -> "videos";
default -> throw new UnsupportedMediaTypeException("Unsupported file type: " + contentType);
};
String key = folder + "/" + UUID.randomUUID() + "-" + originalFilename;
PutObjectRequest putRequest = PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.contentType(contentType)
.build();
try {
s3Client.putObject(putRequest, RequestBody.fromBytes(file.getBytes()));
} catch (IOException e) {
throw new FileUploadException("File upload to Cloudflare R2 failed", e);
}
return key;
}
private String getFileExtension(String filename) {
int lastDot = filename.lastIndexOf('.');
if (lastDot == -1 || lastDot == filename.length() - 1) {
throw new IllegalArgumentException("Invalid file extension in filename: " + filename);
}
return filename.substring(lastDot + 1);
}
}
This is the controller class:
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/media")
public class MediaController {
private final MediaService mediaService;
@PostMapping("/upload")
public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile file) {
String key = mediaService.uploadFile(file);
return ResponseEntity.ok(key);
}
}
When I send a request to localhost:7777/api/media/upload, I got this error:
{
"error": "Internal Server Error",
"message": "Unauthorized (Service: S3, Status Code: 401, Request ID: null) (SDK Attempt Count: 1)"
}
How can I fix the issue?