はじめに
Spring Bootで作成したアプリケーション(ローカル環境)から、Amazon S3に接続して所定のバケットにファイルをアップロードする機能を実装するまでの流れをまとめました。
使用技術
Spring Boot 2.5.6
Spring Security
spring-cloud-starter-aws
Thymeleaf
maven
AWS(IAM,S3)
AWS側の準備
Spring BootからS3へ接続するために、AWSで設定するのは以下2点です。
1.IAMでS3への権限を持ったユーザを作成し、アクセスキーを確認する
2.パブリックアクセスをオフにしたバケットを作成する
IAMでS3への権限を持ったユーザを作成し、アクセスキーを確認する
IAMから「ユーザーを追加」を選択します。
「アクセスキー・プログラムによるアクセス」にチェックを入れます。
S3を操作出来る権限を付与します。タグの追加は省略しても大丈夫です。
作成が完了すると、アクセスキーとシークレットアクセスキーが確認できます。スクショかメモかcsvで保存しましょう。
シークレットアクセスキーの確認を忘れた場合は、「ユーザー名」をクリック→「認証情報」タブから、アクセスキーを再度生成することが出来ます。
パブリックアクセスをオフにしたバケットを作成する
バケットを作成する際は「パブリックアクセスをすべてブロック」のチェックを外して作成します。チェック入れたままだと外部(アプリケーション)からのアップロードが出来ません。
バックエンド
依存関係
以下を追加
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
properties
application.propertiesに以下を追加
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.max-file-size=10MB
cloud.aws.credentials.accessKey=
cloud.aws.credentials.secretKey=
cloud.aws.stack.auto=false
cloud.aws.region.auto=false
cloud.aws.region.static=ap-northeast-1
上記ではaccessKeyとsecretKeyに値を記載していませんが、実際にアプリを動かす場合は「AWS側の準備」で確認したアクセスキー、シークレットキーの値を記載してください。
※プロパティファイルにシークレットキーを記載したままGithub等へ連携すると悪用される危険性がありますので、ご注意ください。
Config
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;
@Configuration
public class S3Config {
@Value("${cloud.aws.region.static}")
private String s3ClientRegion;
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Bean
public AmazonS3 s3Client() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(s3ClientRegion).build();
}
}
@Valueでプロパティファイルに記載したアクセスキー等の値を取得します。
Beanに登録しているAmazonS3型ですが、参考文献によると「AmazonS3Webサービスにアクセスするためのインターフェース」とのことです。
Form
import org.springframework.web.multipart.MultipartFile;
public class FileUploadForm {
private MultipartFile multipartFile;
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime createAt;
///getter & setter
Service
@Service
public class FileUploadService {
private final AmazonS3 s3Client;
public FileUploadService(AmazonS3 s3Client) {
this.s3Client = s3Client;
}
public String fileUpload(FileUploadForm fileUploadForm,String s3PathName)
throws IOException{
DateTimeFormatter fm = DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss");
String extension = FilenameUtils.getExtension(fileUploadForm.getMultipartFile().getOriginalFilename()).toLowerCase();
String fileName = fileUploadForm.getCreateAt().format(fm) +"." + extension;
File uploadFile = new File(fileName);
//try-with-resources
try (FileOutputStream uploadFileStream = new FileOutputStream(uploadFile)){
byte[] bytes = fileUploadForm.getMultipartFile().getBytes();
uploadFileStream.write(bytes);
//引数:S3の格納先オブジェクト名,ファイル名,ファイル
s3Client.putObject(s3PathName, fileName, uploadFile);
uploadFile.delete();
return fileName;
} catch (AmazonServiceException e) {
e.printStackTrace();
throw e;
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
fileUploadメソッドでは、ファイルを受け取ったFormクラスとS3の格納先名を引数で受け取り、ファイルをアップロードする処理を行っています。
あくまでもサンプルコードなので、画像をアップロードする前の「ファイル形式が正しいか」等のチェックは省略しております。
Controller
@Controller
public class DiaryRecordController {
private final DiaryRecordService diaryRecordService;
private final FileUploadService fileUploadService;
public DiaryRecordController(DiaryRecordService diaryRecordService,
FileUploadService fileUploadService) {
this.diaryRecordService = diaryRecordService;
this.fileUploadService = fileUploadService;
}
@GetMapping("/index/create")
public String showCreateContent(@ModelAttribute("diaryRecordForm") DiaryRecordForm form,
@ModelAttribute("fileUploadForm") FileUploadForm file,Model model) {
return "UserCalendar/Create";
}
@PostMapping("/index/create/insert")
public String createContent(@AuthenticationPrincipal AccountUserDetails details,
@ModelAttribute("diaryRecordForm") @Validated DiaryRecordForm form,BindingResult resultForm,
@ModelAttribute("fileUploadForm") FileUploadForm file,
Model model) throws Exception{
if(resultForm.hasErrors()) {
return "UserCalendar/Create";
}
String imageName = null;
LocalDateTime dateTime = LocalDateTime.now();
//ファイルが空でない場合に、ファイルをアップロードする
if(!Objects.isNull(file.getMultipartFile())){
String s3Path = "spring-infra-wp-copy/" + details.getUsername();
file.setCreateAt(dateTime);
imageName = fileUploadService.fileUpload(file, s3Path);
}
//~~~データベースへレコードをinsertする処理
return "redirect:/index";
}
}
Controllerクラスでは、ファイルをPOST通信で受け取ってServiceクラスのアップロード処理を呼び出しています。ファイルのバリデーションも必要ですが、今回は省略しています。
fileUploadメソッドの引数に渡すS3の格納先は、「バケット名/ログイン中のユーザ名」としています。
フロントエンド
html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>sample</title>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/style.css}" type="text/css">
</head>
<body>
<div th:replace="flagment/header :: user-header"></div><hr>
<h2>食事記録登録</h2>
<div class="container">
<form class="row" id="category" enctype="multipart/form-data" th:action="@{/index/create/insert}" method="post" >
<div class="col-sm-6" th:object="${diaryRecordForm}">
<!-->省略<-->
<div th:object="${fileUploadForm}">
<input type="file" th:field="*{multipartFile}">
</div>
<button type="submit">登録</button>
</div>
</form>
</div>
</body>
</html>
結果・動作確認
今回作成したばかりのS3バケットには、ファイルが何も入っていません。
ローカルでアプリを起動させ、ファイルを選択して「登録」を押下すると...
S3バケットにファイルが格納されました。ファイル名もServiceクラス内で指定した名前になっています。
今回の格納先は「バケット名/ログイン中のユーザ名」としていますが、指定のフォルダ(ユーザ名のフォルダ)が無い場合はフォルダを作って格納されるみたいです。便利~!
終わりに
本来は「画像形式以外は受け付けない」といったバリデーションを設けて不正なファイルを弾く処理が必要ですが、本記事では省略しております。気になった方は調べてみてください。
ファイルアップロード処理のとっかかりになれば幸いです。
参考文献