CEOS 18th Backend Study - Carrot Market
- ํ์ ๊ณ ์ ๋ฒํธ
userId - ํธ๋ํฐ๋ฒํธ
phone
โ ํธ๋ํฐ ๋ฒํธ๋ ์ซ์์ด์ง๋ง ์ฐ์ฐ์ด ์๊ณ ๊ฒ์์ด ํธํ๋๋ก varchar/String์ผ๋ก ์ค์ - ์ด๋ฉ์ผ
email
๋น๊ทผ๋ง์ผ์ ์ฐ์ ํธ๋ํฐ ๋ฒํธ๋ก ํ์๊ฐ์
์ ํ ํ ์ํ๋ค๋ฉด ์ด๋ฉ์ผ๋ ๋ฑ๋กํ ์ ์๋ค
๋น๋ฐ๋ฒํธ? ๋น๊ทผ๋ง์ผ์์ ์ ์กํ๋ ์ธ์ฆ๋ฒํธ?
- ๋๋ค์
nickname - ํ๋กํ ์ฌ์ง
profileImage - ๋งค๋์จ๋
manners - ์ฌ๊ฑฐ๋ํฌ๋ง๋ฅ
- ์๋ต๋ฅ
- ์๋ต์๊ฐ
townIdstateName,districtName,townNameex. ์์ธ์ ์์ด๊ตฌ ๋ฐฉ๋ฐฐ๋
์ด๋ ๊ฒ ๋๋ ์ ์ ์ฅํ๋ ๊ฒ ๋ง๋์ง
์์ธ์ ์์ด๊ตฌ ๋ฐฉ๋ฐฐ๋์ผ๋ก ์ค์ ํ๊ณ ์ ํ ๋
๋ฐฉ๋ฐฐ๋ผ๊ณ ๊ฒ์ํด๋ ๋จ๊ณ ์์ด๋ผ๊ณ ๊ฒ์ํด๋ ๊ด๋ จ ๋๋ค๊ฐ ๋จ๋๋ฐ
์ด๊ตฌ๋ ๋ฐฐ๋ ์ด๋ ๊ฒ ๊ฒ์ํ๋ฉด ์ ๋ฌ๋ค โ ๊ฒ์์ด ์ด๋ป๊ฒ ์ด๋ฃจ์ด์ง๋ ๊ฑฐ์ง???
์ฒ์์ ํ์ ๊ตฌ์ญ๋ณ๋ก ์์ ์ธ ๊ฐ์ ํ
์ด๋ธ๋ก ๋๋์๋ค๊ฐ ๊ทธ๋ ๊ฒ๊น์ง ๋๋์ด์ผํ๋ ์ถ์๋๋ฐ
๊ทผ๋ฐ ํ
์ด๋ธ์ด๋ ์ปฌ๋ผ๋ณ๋ก ๊ตณ์ด ๋ถ๋ฆฌํด์ผํ๋ ์ถ๊ธฐ๋ ํ๊ณ ์๋ฌดํผ ๊ณ ๋ฏผ
๊ทธ๋ฆฌ๊ณ ๊ทผ์ฒ ๋๋ค๋ ์ด๋ป๊ฒ ์ค์ ํ๋๊ฑฐ์ง
์ ์ ๋น ์ต๋ ๋ ๊ฐ์ ๋๋ค ์ ๋ณด
userTownIduserIdtownId- ๋๋ค ๋ฒ์
townRange
๐range๋ฅผ ์ฐ๋ฉด mysql ์์ฝ์ด๋ผ ์๋ฌ๊ฐ ๋๋ค!! ๋๋ ์๊ณ ์ถ์ง ์์๋ค ๐ฅน๐ฅน - ๋๋ค ์ธ์ฆ ์๊ฐ
townAuthTime - ๋๋ค ์ธ์ฆ ์ฌ๋ถ
isTownAuth
์ ์ ๋น ์ต๋ 2๊ฐ์ ์ฃผ์๋ฅผ ์ค์ ํ ์ ์๊ณ ,
์ฃผ์๋ง๋ค ๋ฒ์, ์ธ์ฆ ์๊ฐ, ์ธ์ฆ ์ฌ๋ถ๊ฐ ๋ฐ๋ก ๊ด๋ฆฌ๋์ด ํ
์ด๋ธ ๋ถ๋ฆฌ - ํ๋งค ๊ฒ์๊ธ ๊ณ ์ ๋ฒํธ
postId - ์ ๋ชฉ
title-> @Notnull - ์นดํ
๊ณ ๋ฆฌ
categoryId-> @Notnull - ๊ฑฐ๋๋ฐฉ์
tradeMethod - ๊ฐ๊ฒฉ
price - ๊ฐ๊ฒฉ ์ ์ ์ฌ๋ถ
isPriceOffer - ์์ธํ ์ค๋ช
description-> @Notnull - ๊ฑฐ๋ ํฌ๋ง ์ฅ์
wishPlace - ํ๋งค์ user ->
seller - ๋ณด์ฌ์ค ๋๋ค ์ค์
townRange - ํ๋งค ์ํ
postStatus
ํ๋งค์๋ ๋ณธ์ธ์ด ์ฌ๋ฆฐ ๊ฒ์๊ธ์์ ํ๋งค ์ํ๋ฅผ ํ๋งค ์๋ฃ๋ก ๋ฐ๊พธ๋ฉด ๊ตฌ๋งค ํ์ ์ธ๋ฐ
๊ตฌ๋งค์๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌ๋์ด์ผํ๋์ง ๊ณ ๋ฏผ - ๋ํ์ฌ์ง
thumbnail - ๋๋จธ์ง ์ฌ์ง
image1~9 - ๋ธ๋๋
brand
โ ์นดํ ๊ณ ๋ฆฌ์ ๋ฐ๋ผ ๋ธ๋๋๋ฅผ ์ ๋ ฅํ๋ ์นธ์ด ๋จ๊ธฐ๋ ํ๊ณ ์ ๋จ๊ธฐ๋ ํ๋ค ์ ๊ธฐ
- ์นดํ
๊ณ ๋ฆฌ ๊ณ ์ ๋ฒํธ
categoryId - ์นดํ
๊ณ ๋ฆฌ ์ด๋ฆ
name
- ์ฑํ
๋ฐฉ ๊ณ ์ ๋ฒํธ
chatRoomId - ํ๋งค์/๊ตฌ๋งค์ ์ ๋ณด user ->
seller/buyer
โ ์ฑํ ๋ฐฉ ์ด๋ฆ์ ์๋๋ฐฉ ๋๋ค์ - ํ๋งค ๊ฒ์๊ธ ์ ๋ณด
postId - ์ ์ฝ์ ์ฑํ ์
- ์ฑํ
๊ณ ์ ๋ฒํธ
chatId - ์ฑํ
๋ฐฉ ๋ฒํธ
chatRoomId - ์ฑํ
๋ด์ฉ
content - ์๋๋ฐฉ์ด ์ฝ์๋์ง ์ฌ๋ถ
isRead - ๋๊ฐ ๋ณด๋ด๊ณ ๋ฐ์๋์ง user ->
sender/receiver
โsender์ปฌ๋ผ๋ง ์์ผ๋ฉด ์ฑํ ๋ฐฉ์ด๋ ์ฐ๊ฒฐํด์ ๋ฐ์ ์ฌ๋ ์ ์ ์์ง ์๋?
- ๊ฑฐ๋ ํ๊ธฐ ๊ณ ์ ๋ฒํธ
reviewId - ์์ฑ์/๋์์
reviewer/reviewee - ์ด๋ค ํ๋งค ๊ฒ์๊ธ์ ๋ํ ๋ฆฌ๋ทฐ์ธ์ง
postId - ๊ตฌ๋งค์๊ฐ ์ ์ ํ๊ธฐ์ธ์ง ํ๋งค์๊ฐ ์ ์ ํ๊ธฐ์ธ์ง
reviewType - ๊ฑฐ๋์ ํธ๋
reviewLevel
์ด ๋ฆฌ๋ทฐ๋ก ๋งค๋์จ๋๊ฐ ๋ณํ๋๋ฐ - ์์ฑ์๊ฐ
created์ ๋ง์ง๋ง ์์ ์๊ฐmodified์ปฌ๋ผ์ ๊ฑฐ์ ๋ชจ๋ ํ ์ด๋ธ์ด ๊ฐ์ง๊ณ ์๋ ์ปฌ๋ผ์ด๊ธฐ ๋๋ฌธ์@MappedSuperClass๋ก ์ํฐํฐ ์์ฑ @MappedSuperclass- ๋งคํ ์ ๋ณด๋ง ๋ฐ๋ ๋ถ๋ชจ ํด๋์ค, ์์๊ณผ ๊ด๋ จ๋ ๊ฒ ์๋
- ์์๊ด๊ณ ๋งคํ ์๋๊ณ ์ํฐํฐ๊ฐ ์๋์ด์ ํ
์ด๋ธ๊ณผ ๋งคํ๋์ง ์๋๋ค
โ ์กฐํ, ๊ฒ์ ๋น์ฐํ ๋ถ๊ฐ(em.find(BaseEntity) ๋ถ๊ฐ) - ๋ถ๋ชจ ํด๋์ค๋ฅผ ์์ ๋ฐ๋ ์์ ํด๋์ค์ ๋งคํ ์ ๋ณด๋ง ์ ๊ณต
- ํ ์ด๋ธ๊ณผ ๊ด๊ณ ์๊ณ , ๋จ์ํ ์ํฐํฐ๊ฐ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ๋งคํ ์ ๋ณด๋ฅผ ๋ชจ์ผ๋ ์ญํ
- ๋ณต์กํ Object๋ค์ ๋จ๊ณ๋ณ๋ก ๊ตฌ์ถํ ์ ์๋ ์์ฑ ๋์์ธ ํจํด์ผ๋ก
- ๋ณต์กํ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์ ์ํ๋ ํด๋์ค์ ํํํ๋ ๋ฐฉ๋ฒ์ ์ ์ํ๋ ํด๋์ค๋ฅผ ๋ณ๋๋ก ๋ถ๋ฆฌํด,
- ์๋ก ๋ค๋ฅธ ํํ์ด๋ผ๋ ์ด๋ฅผ ์์ฑํ ์ ์๋ ๋์ผํ ์ ์ฐจ๋ฅผ ์ ๊ณตํ๋ ํจํด
- ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ ๋์์ ๊ฐ์ ์ค์ ๊ฐ๋ฅํ ์์ฑ์๋ฅผ ๋ง์ด ์ฌ์ฉํ๋๋ฐ, ์์ฑ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
- ํ์๊ฐ ์๋ ๊ฐ๋ null๋ก ์ฑ์์ฃผ๊ฑฐ๋,
- ex.์ฃผ์๋ฅผ ๋บ ์์ฑ์ ํจ์๋ฅผ ๋ค์ ๋ง๋ค์ด์ผ ํ๊ณ
- ๋ช ํํ๊ฒ ์ด๋ค ๊ฐ์ ์ง์ ํ๋ ์ง ์ ์ ์๊ธฐ ๋๋ฌธ์ ๊ฐ๋ ์ฑ์ด ์ข์ง ์๋ค
- ์์ฑ์๋ฅผ ๊ฐ๋ ์ฑ ์ข๊ฒ ๋ง๋ค์ด์ฃผ๋ ๋๊ตฌ
ํด๋์ค ๋ด๋ถ์์ Builder ํด๋์ค๋ฅผ ๋ฐ๋ก ์ ์ํด ์ฌ์ฉํ ์ ์๊ณ
๊ฐ์ ์ค์ ํ๊ณ ์๊ธฐ์์ ์ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ํจ์๋ฅผ ์ฐ์์ ์ผ๋ก ์ฒด์ด๋ํ๋ฏ ์ฌ์ฉํ ์ ์๋ค
@Builder
๋น๋ ํด๋์ค์ ์ด๋ฅผ ๋ฐํํ๋ builder() ๋ฉ์๋ ์์ฑ@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder์ด๋ ธํ ์ด์ ์ ์ ์ธํ๋ฉด ์ ์ฒด ์ธ์๋ฅผ ๊ฐ๋ ์์ฑ์๋ฅผ ์๋์ผ๋ก ๋ง๋๋๋ฐ, ์ด๋ฅผ private ์์ฑ์๋ก ์ค์ - ํด๋์ค ์ ์ฒด์ Builder๋ฅผ ์ ์ฉํ ์๋ ์๊ณ ํน์ ์์ฑ์์์๋ง ์ ์ฉํ ์๋ ์๋ค
@Getter @Builder //ํด๋์ค ์ ์ฒด ํ๋๋ฅผ ๋น๋๋ก ์ฌ์ฉ
public class User {
private Long id;
private String phone;
private String nickname;
}public class User {
...
@Builder //phone, nickname๋ง ๋น๋ ์ฌ์ฉ
public User(String phone, String nickname) {
this.phone = phone;
this.nickname = nickname;
}
}- JPA ๊ด๋ จ๋ Component๋ง ๋ก๋
ApplicationContext ์ ์ฒด๊ฐ ์๋ JPA์ ํ์ํ ์ค์ ๋ค์ ๋ํด์๋ง Bean์ ๋ฑ๋กํ๋ค
โ ์ปดํฌ๋ํธ ์ค์บ์ ํ์ง ์์, @Component ๋น๋ค์ด ๋ฑ๋ก๋์ง ์๋๋ค - @Transactional ์ด๋ ธํ ์ด์ ํฌํจ โ ํ ์คํธ ์ข ๋ฃ ํ ๋กค๋ฐฑ๋ ๊ฐ์ด ์ํ๋๋ค
- ๋ํดํธ๋ก h2 ๋๋ผ์ด๋ฒ ์ฌ์ฉ
- ymlํ์ผ์์ DB๋ฅผ MySql๋ก ์ค์ ํด ๋์๊ธฐ ๋๋ฌธ์ h2 ์์กด์ฑ์ด ์์ผ๋ฉด DataSource๋ฅผ ์ฐพ์ ์ ์๋ค๋ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋ค
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
@PropertyMapping("spring.test.database")
public @interface AutoConfigureTestDatabase {
@PropertyMapping(skip = SkipPropertyMapping.ON_DEFAULT_VALUE)
Replace replace() default Replace.ANY;
EmbeddedDatabaseConnection connection() default EmbeddedDatabaseConnection.NONE;
// ...
}@AutoConfigureTestDatabase์@DataJpaTest์์ ์ค์ ์ ์๋์ผ๋ก ํด์ฃผ๋ ๋ง์ ์ด๋ ธํ ์ด์ ์ค ํ๋- ๋ํดํธ๊ฐ
Replace.ANY์replace์์ฑ๊ณผ
๋ํดํธ๊ฐEmbeddedDatabaseConnection.NONE์connection์์ฑ์ ์ค์ ํ ์ ์๋ค EmbeddedDatabaseConnection์ enum ๊ฐ์๋ H2, DERBY, HSQLDB ๋ฑ์ด ์๋๋ฐ MySql์ ์๋ค
โ MySql๋ก ์ค์ ํ๋ค๋ฉด ์ฐพ์ ์ ์๊ธฐ ๋๋ฌธ์ ์๋ฌ ๋ฐ์!!replace๊ธฐ๋ณธ๊ฐ์ดANY์ด๊ธฐ ๋๋ฌธ์ Embedded Database ๋ฅผ ์ฐพ๊ฒ ๋ ๊ฒ์ด๊ณ
โ Embedded Database๋ฅผ ์ฐ์ง ์๋๋กreplace๊ฐ์@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
Replace.NONE์ผ๋ก ์ค์ ํ๋ฉด ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ์ค์ Database๋ฅผ ์ฌ์ฉํ ์ ์๋ค
AssertJ๋ assertion์ ์ ๊ณตํ๋ ์๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ํ ์คํธ ์ฝ๋์ ์๋ฌ ๋ฉ์ธ์ง์ ๊ฐ๋ ์ฑ์ ๋์ฌ์ค๋ค
import static org.assertj.core.api.Assertions.assertThat;
...
assertThat(actual).isEqualTo(expected)๋ชจ๋ ํ
์คํธ ์ฝ๋๋ assertThat() ๋ฉ์๋์์ ์ถ๋ฐํ๊ณ , AssertJ์์ ์ ๊ณตํ๋ ๋ค์ํ ๋ฉ์๋๋ฅผ ์ฐ์ ํธ์ถ ํ๋ฉด์ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค
assertThat(ํ
์คํธ ํ๊ฒ).๋ฉ์๋1().๋ฉ์๋2().๋ฉ์๋3(); public Long registerPost(RegisterPostRequestDto requestDto) {
//๋ก๊ทธ์ธ๋ ์ ์ ์ ์ฌ๋ฐ๋ฅธ ์ ๋ณด๊ฐ ๋์ด์จ๋ค๊ณ ๊ฐ์
User seller = userRepository.findById(requestDto.getUser_id()).get();
Post post = requestDto.toEntity(seller);
TradeMethod tradeMethod = TradeMethod.valueOf(requestDto.getTradeMethod());
post.setTradeMethod(tradeMethod);
Category category = categoryRepository.findByName(requestDto.getCategory());
post.setCategory(category);
postRepository.save(post);
return post.getId();
}- RequestBody๋ก ์ฌ์ฉ์ ์ ๋ณด ๋ฐ ๊ฒ์๊ธ ๋ฑ๋ก์ ํ์ํ ์ ๋ณด ๋ฐ๊ธฐ
๋ถ๋์ดํ๊ฒ ์ฌ์ฉ์ ์ ๋ณด๋ RequestBody๋ก ๋ฐ์ RegisterPostRequestDto-toEntity๋ฉ์๋ : DTO๋ก ๋ฐ์ ์ ๋ณด Post Entity๋ก ๋ฐ๊ฟ์ฃผ๊ธฐ
์ฐ๊ด ๊ด๊ณ๋ฅผ ์ํด userId๋ก User Entity ์ฐพ์์ ์ฌ์ฉ์ ์ ๋ณด๋ง ๋ฐ๋ก ๋๊ฒจ์ค๋คpublic Post toEntity(User seller) { return Post.builder() .seller(seller) .thumbnail(thumbnail) .title(title) .price(price) .isPriceOffer(isPriceOffer) .description(description) .wishPlace(wishPlace) .townRange(townRange) .build(); }
- TradeMethod ๊ฑฐ๋ํ๊ธฐ/๋๋ํ๊ธฐ์ ๊ฑฐ๋๋ฐฉ์์ String์ผ๋ก ๋์ด์ค๋๋ฐ Enum๊ฐ์ผ๋ก ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์ค์ ํด์ค๋ค
์นดํ ๊ณ ๋ฆฌ๋ String์ผ๋ก ๋์ด์ค๊ธฐ ๋๋ฌธ์CategoryRepository์์ ์ํฐํฐ ์ฐพ์์ ์ฐ๊ด ๊ด๊ณ ์ค์ ํด์ฃผ๊ธฐ - ๊ทธ๋ฆฌ๊ณ save ํด์ฃผ๊ณ ์ผ๋จ Service์์๋ postId ๋ฆฌํดํด์ฃผ์๋น Controller์์๋ ok ๋ฐํ
- ์ ๋ ฌ์กฐ๊ฑด์ด ์ต์ ์์ด ์๋ ๊ฒ ๊ฐ๊ธด ํ๋ฐ ์ฐ์ Pageable ์ ์ฉํ findAll๋ก ๊ฐฑ์ ์์ผ๋ก ๊ฐ์ ธ์ค๋ ค๊ณ ํ๋ค
- ๊ทผ๋ฐ ์๊ฐํด๋ณด๋ ๊ทผ์ฒ ๋๋ค์ ๊ฒ์๋ฌผ๋ง ๊ฐ์ ธ์์ผํ๊ณ
- ๋ ์๊ฐํด๋ณด๋๊น ์ฌ์ฉ์๊ฐ ๋ ๊ฐ์ ๋๋ค๋ฅผ ์ค์ ํ ์ ์๋๋ฐ
์ฌ์ฉ์์ ํ์ฌ ๋๋ค๋
ํ๋งค์๊ฐ ์ด๋ ๋๋ค๋ฅผ ํ์ฌ๋ก ์ค์ ํ๊ณ ์ฌ๋ฆฐ ๊ฒ์๋ฌผ์ธ์ง๋ ์์์ผํ ๊ฑฐ ๊ฐ์๋ฐ
๊ทธ๊ฑฐ๋ ํฌ์คํธ ์ํฐํฐ์ ์ปฌ๋ผ์ด ์์ด์ผํ ๊ฒ ๊ฐ๋ค - ํ์ด ์ํฐํฐ์ ์๋์ ๊ฒฝ๋๋ฅผ ์ถ๊ฐํ๊ธด ํ๋๋ฐ
์๋ฅผ ๋ค์ด ๊ทผ์ฒ ๋๋ค ๋ฒ์๋ฅผ ์๋ยฑ50, ๊ฒฝ๋ยฑ50 ์ผ๋ก ์ค์ ํ์ ๋
๊ทธ๋์ ์ ๋ง๋ก ๊ทธ ์์น์ ๋๋ค ์ด๋ฆ์ ์๋ ค๋ฉด api๊ฐ ํ์ํ ๊ฒ ๊ฐ๋ค
@Transactional(readOnly = true)
public PostListResponseDto getPostList(Pageable pageable) {
Page<Post> findPosts = postRepository.findByIsDel(false, pageable);
Page<PostDto> postDtos = findPosts.map(post -> new PostDto(post,
chatRoomRepository.getTotalChatRoom(post),
userTownRepository.findByUser(post.getSeller()).get(0).getTown().getTownName()));
//ํธ์์ ์ฒซ ๋ฒ์งธ ์ฃผ์๋ก ๊ฐ์
return new PostListResponseDto(postDtos.getTotalPages(), postDtos.getNumber(), postDtos.getContent());
}- ํ์ฌ ์ฌ์ฉ์์ ๋๋ค๋ก ์ค์ ๋ ๊ทผ์ฒ ๋๋ค์ ๊ฒฐ๊ณผ๋ง ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ์ ์ฉํ์ง ๋ชปํ๋ค
๊ทธ๋ฅ ์ ๋ ฌ ์กฐ๊ฑด์
Page<Post> findByIsDel(boolean isDel, Pageable pageable);
modifiedAt์ ASC ์์๋ก Page ๊ฐ์ฒด ์์ฑ + ์ญ์ ์ฌ๋ถ ํ์ธ
๋ฌดํ์คํฌ๋กค๋ก ๊ตฌํ์ด ๋์ด์๋๋ฐ, ์ ๋ชจ๋ฅด๊ฒ ์ง๋ง ํ๋ก ํธ ์ธก์์ ์คํฌ๋กค ์ด๋ฒคํธ๊ฐ ์ผ์ด๋๊ฑฐ๋ ํ๋ ์ํฉ์
๋ฒก์ผ๋ก ๋ค์ ํ์ด์ง ๋ฒํธ๋ก ์์ฒญํ๋ฉด, ์ผ์ ๊ฐ์์ ๊ฒ์๋ฌผ ์ ๋ณด๊ฐ ๋ด๊ธด ๋ค์ ํ์ด์ง ๋ฐํ
์ ๋ชจ๋ฅด๊ฒ ์ง๋ง ๋ฌดํ์คํฌ๋กค ํ์์ด๋ ๊ฒ์ํ ํ์์ด๋ ๊ทธ๊ฒ์ ํ๋ก ํธ๊ฐ ํด์ผํ๋ ์ผ์ด ์๋๊น..? โ - ์ฐพ์์จ ๊ฒ์๋ฌผ๋ค์์ map์ผ๋ก ๊ฐ ๊ฒ์๋ฌผ ํ๋์ฉ์ ์ ๋ณด๋ฅผ ๋ด์
PostDto์์ฑ- post Entity ์์ฒด๋ฅผ ๋๊ฒจ์ ๊ฐ ์ ๋ณด ๋ฝ๊ณ ,
@Query("SELECT COALESCE(COUNT(cr.id), 0) FROM ChatRoom cr WHERE cr.post = :post") int getTotalChatRoom(@Param("post") Post post);
- ์ฑํ
๋ฐฉ ๊ฐ์๋
ChatRoomRepository์ ์ฟผ๋ฆฌ ์์ฑํด์ ๊ณ์ฐ - ํ๋งค์ ๋๋ค ์ ๋ณด : post Entity์ seller ์ ๋ณด๋ฅผ ์ด์ฉํด
UserTownRepository์์findByUser๋ก UserTown ๋ฆฌ์คํธ๋ฅผ ๋ฝ์ ๋ค์์,
ํธ์์ 0๋ฒ์งธ ์ธ๋ฑ์ค ๊ฐ์ UserTown Entity โ ์ Town์ผ๋ก ๋์ด๊ฐ์ ๋๋ค ์ด๋ฆ ๊ฐ ๋ฐ์์ค๊ธฐ..
- ๋ง์ง๋ง์ผ๋ก
PostListResponseDto์ Page ๊ฐ์ฒด๊ฐ ์ ๊ณตํด์ฃผ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด
์ ์ฒด ํ์ด์ง ์์, ํ์ฌ ํ์ด์ง ์,
๊ทธ๋ฆฌ๊ณ ๊ฐ ๊ฒ์๋ฌผ ์ ๋ณด์ ๋ฆฌ์คํธ๋ฅผ ๋ด์์ ResponseBody๋ก ๋ฐํ
์์๋ฆฌ์คํธ ์๋ค
3๋ฒ ๊ฒ์๊ธ์ isDel=1๋ก ์ญ์ ๋ ๊ฒ์๊ธ์ด๋ผ ๋ํ๋์ง ์๋๋น๐๐ป๐๐ป
public PostResponseDto getPost(Long postId) {
Optional<Post> findPost = postRepository.findById(postId);
if (findPost.isPresent() && !findPost.get().isDel()) {
//์กฐํ์ ์ฌ๋ ค์ฃผ๊ธฐ!
postRepository.updateView(postId);
Post post = findPost.get();
//ํธ์์ ์ฒซ ๋ฒ์งธ ์ฃผ์๋ก ๊ฐ์ ..
String sellerTown = userTownRepository.findByUser(post.getSeller()).get(0).getTown().getTownName();
return new PostDetailResponseDto(postId, post, sellerTown, chatRoomRepository.getTotalChatRoom(post));
}
else {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "์๋ชป๋ ๊ฒ์๋ฌผ ์์ฒญ");
}
}- @PathVariable๋ก ๋ฐ์์จ
postId๋ฅผ ์ด์ฉํดpostRepository์์ ๊ฒ์๋ฌผ ์ฐพ๊ธฐ - ๊ฒ์๋ฌผ์ด ์์ผ๋ฉด ํด๋น ๊ฒ์๋ฌผ์ ์กฐํ์ ์ฌ๋ ค์ฃผ๊ธฐ + ์ญ์ ๋์ง ์์์ผ๋ฉด!
@Modifying @Query("UPDATE Post p set p.view = p.view + 1 where p.id = :postId") void updateView(@Param("postId") Long postId);
- ๊ทธ๋ฆฌ๊ณ post Entity ๋ฐ์์ค๊ณ , ํ๋งค์ ์ฃผ์ ์ ๋ณด ์ฐพ์ ๊ฑฐ๋
์ฑํ ๋ฐฉ ๋ฆฌํฌ์งํ ๋ฆฌ์์ ์ฑํ ๋ฐฉ ๊ฐ์ ์ฐพ์์PostDetailResponseDto์์ฑํด์ ๋ฐํpublic PostDetailResponseDto(Long postId, Post post, String sellerTown, int totalChatRoom) { this.post_id = postId; this.seller_profileImage = post.getSeller().getProfileImage(); this.seller_nickname = post.getSeller().getNickname(); this.seller_town = sellerTown; this.seller_manners = post.getSeller().getManners(); this.title = post.getTitle(); this.category = post.getCategory().getName(); this.description = post.getDescription(); this.wishplace = post.getWishPlace(); this.view = post.getView(); this.total_ChatRoom = totalChatRoom; }
- ๊ฒ์๋ฌผ์ด ์์ผ๋ฉด
404๋ฐํ
์กฐํ์๊ฐ 1๋ก ์ฆ๊ฐํ๊ณ ์ฑํ
๋ฐฉ ๊ฐ์๋ 0์ผ๋ก ์ ๋ฐํ๋จ๐๐
์ญ์ ๋ ๊ฒ์๊ธ์ 404 BAD REQUEST
public void deletePost(Long postId) {
postRepository.deletePost(postId);
}- Post Entity์
isDel์ปฌ๋ผ ์ถ๊ฐ
DB์์ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์ญ์ ํ๋ ๊ฒ์ด ์๋๋ผisDel์ปฌ๋ผ์ ์ด์ฉํด ๋ ผ๋ฆฌ์ ์ผ๋ก ์ญ์ ํ๋ ๋ก์ง์ผ๋ก ๊ตฌํ
Post Entity๋ ๋ฆฌ๋ทฐ, ์ฑํ ๋ฐฉ, ๊ทธ๋ฆฌ๊ณ ๊ตฌํํ์ง ์์์ง๋ง ์์๋ฆฌ์คํธ ๋ฑ
์ฌ๋ฌ ์ํฐํฐ์ ์ฐ๊ฒฐ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋ ผ๋ฆฌ์ ์ผ๋ก ์ญ์ ํ๋ ๊ฒ์ด ๋ซ๋ค๊ณ ํ๋น@Modifying @Query("UPDATE Post p SET p.isDel = true WHERE p.id = :postId") void deletePost(@Param("postId") Long postId);
์ญ์ ํ ๋ค์ ์กฐํํ๋ ค๊ณ ํ๋ฉด ์กฐํํ ์ ์์!!
DB์๋ ์ ๋ฐ์๋์ด ์์๐๐
- ์ด๊ธฐ DB์ ๊ฐ์ ์ ๋ฃ์ด๋์์ผ ํ๋ค
์ฌ์ฉ์๋ ๋๋ค ๋ฃ๊ณ UserTown ๋๋ฌธ์ ๋์ด ์ฐ๊ฒฐํด ๋์ด์ผ ํ๊ณ , ์นดํ ๊ณ ๋ฆฌ๋ ๋ฏธ๋ฆฌ ์์ฑํด๋์ด์ผ ํ์ Category๋Post์ฐ๊ด ๊ด๊ณ@ManyToOne์ผ๋ก ํ๋ค๊ฐ ์์ธ์ง@OneToOne์ผ๋ก ๋ฐ๊ฟจ๋๋ฐ
@ManyToOne์ด ๋ง์์- ๋ชจ๋ ๊ฒ์๊ธ ์กฐํ API์์ ๊ณ์
406 not acceptable์๋ฌ๊ฐ ๋ด๋๋ฐ
DTO์@Getter๋ถ์ฌ์ ํด๊ฒฐ
JSON๊ณผ ๊ด๋ จ๋jackson๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์ด์ ๋๋ ์ค๋ฅ๋ผ๊ณ ํ๋ค
์๊ฐ๋ณด๋ค ๋น๊ทผ๋ง์ผ์ DB์ ๋ก์ง์ ๋งค์ฐ ๋ณต์กํ ๊ฑฐ ๊ฐ๋ค
์ค์ ๋ก ์ด๋ป๊ฒ ๊ตฌํ๋์ด ์๋์ง ์ ๋ง ๊ถ๊ธํ๋ค
- ์๋ก ๋ค๋ฅธ ๊ธฐ๊ธฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ๋ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ ์ค ํ๋๋ก,
Base64์ ํํ๋ฅผ ๊ฐ์ง๋ค Header์Body(๋๋ Payload), ๊ทธ๋ฆฌ๊ณSignature์ธ ๋ถ๋ถ์ผ๋ก ๋๋ ์ง๋ค
- JWT์ metadata๋ค์ ๋ํ๋ธ๋ค
- Sign์ ์ฌ์ฉ๋ Algorithms, format, ๊ทธ๋ฆฌ๊ณ ContentType ๋ฑ์ ์ ๋ณด
Claim๋จ์๋ก ์ ์ฅ
Claim
- ์ฌ์ฉ์์ ์์ฑ์ด๋ ๊ถํ, ์ ๋ณด์ ํ ์กฐ๊ฐ ๋๋ Json์ ํ๋๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค
Claim์๋ JWT ์์ฑ์๊ฐ ์ํ๋ ์ ๋ณด๋ค์ ์์ ๋กญ๊ฒ ๋ด์ ์ ์๋๋ฐ
> Json ํ์์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ ๋จ์ผ ํ๋๋ ๊ฐ๋ฅํ๊ณ ,
> Object์ ๊ฐ์ complexibleํ ํ๋๋ ์ถ๊ฐํ ์ ์๋ค > > ```java Claims claims = Jwts.claims(); //์ผ์ข ์ Map claims.put("userName", userName); ... Jwts.builder() .setClaims(claims) ...- Claim์ userName์ ๋ด์๋๋ฉด ๋ฐ๋ก ์ฌ์ฉ์ id๋ฅผ ์ ๋ ฅ๋ฐ์ง ์์๋ ํ ํฐ์ ๋ค์ด์๋ ๊ฐ์ ๊บผ๋ผ ์ ์๋ค
- Header์ Body๋ Base64 ํํ๋ก ์ธ์ฝ๋ฉ๋์ด ์ํธํ๋์ด ์์ง ์์๋ฐ
๊ณต๊ฒฉ์๊ฐ ๋ด์ฉ์ ๋ฐ๊ฟ ์๊ฐ ์๋ค - Signature๋ก ์๋ช ์ ํตํด ์ํธํ ๊ณผ์ ์ ๊ฑฐ์น๋ค
- ์๋ช ์ดํ Header์ Body์ ๋ด์ฉ์ด ๋ฐ๋๋ค๋ฉด Signature์ ๊ฒฐ๊ณผ๊ฐ์ด ๋ฐ๋์ด ๋ฐ์๋ค์ฌ์ง์ง ์๋๋ค
-
๊ฐํธํ๊ณ , ์ธ์ ์ด๋ ์ฟ ํค์ ๋ฌ๋ฆฌ ์ถ๊ฐ์ ์ธ ์ ์ฅ์๊ฐ ํ์ํ์ง ์๊ณ ,
ํ ๋ฒ ๋ฐ๊ธ๋๋ฉด ์ ํจ๊ธฐ๊ฐ์ด ์๋ฃ๋ ๋๊น์ง๋ ๊ณ์ ์ฌ์ฉ์ด ๊ฐ๋ฅํ์ง๋ง, -
์ค๊ฐ์ ์ญ์ ๊ฐ ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์
Access Token์ด ํ์ทจ๋๋ฉด, ํ ํฐ์ด ๋ง๋ฃ๋๊ธฐ ์ ๊น์ง ํ ํฐ์ ๊ฐ์ง ์ฌ๋์ ๋๊ตฌ๋ ๊ถํ ์ธ์ฆ์ด ๊ฐ๋ฅํด์ง๋ค๋ ๋ฌธ์ ์ ์ด ๋ฐ์ํ ์ ์๋ค
โ ์ด๋ฌํ ๋ฌธ์ ์ ์ ๋ณด์ํ๊ธฐ ์ํด Access Token์ ๋ง๋ฃ ๊ธฐ๊ฐ์ ์งง๊ฒ ์ฃผ๊ณ , Refresh Token์ ์ถ๊ฐ์ ์ผ๋ก ๋ฐ๊ธํด ํด๊ฒฐ
Refresh Token์Access Token์ ๋นํด ํจ์ฌ ๋ ๊ธด ์ ํจ ๊ธฐ๊ฐ์ผ๋ก ๋ฐ๊ธ๋๋ฉฐ,
Refresh Token์ ๊ฒฝ์ฐ ์ ๊ทผ์ ๋ํ ๊ถํ์ ๊ฐ์ง ๊ฒ์ด ์๋๋ผAccess Token์ฌ๋ฐ๊ธ์๋ง ์ฌ์ฉ๋๋ค๋ ํน์ง์ด ์๋ค
Access Token์ ํจ ๊ธฐ๊ฐ 30๋ถ ~ 1์๊ฐ ์ ๋
Refresh Token์ ํจ ๊ธฐ๊ฐ 1์ฃผ์ผ ~ 1๋ฌ ์ ๋
Refresh Token์ญ์ ํ์ทจ๋ ์ ์๋ ๋ฌธ์ ๊ฐ ์๋๋ฐ,
์ต์ด ๋ก๊ทธ์ธ ์ ๋ก๊ทธ์ธ ์์ฒญ ip๋ฅผ ์ ์ฅํ๊ณ ,
์ฌ๋ฐ๊ธ ์์ฒญ์ด ์์ ๋, ์์ฒญ์ด ์จ ip์ ์ ์ฅ๋ ip๋ฅผ ๋น๊ตํ์ฌ
๋ค๋ฅธ ๊ฒฝ์ฐ ํ ํฐ์ ์ฌ๋ฐ๊ธํ์ง ์๊ฑฐ๋ ์๋ฆผ์ ๋ณด๋ด๋ ๋ฑ์ ์ถ๊ฐ์ ์ธ ์กฐ์น๋ฅผ ์ทจํ ์ ์๋ค
- Open Authorization
- ์ธํฐ๋ท ์ฌ์ฉ์๋ค์ด ํน์ ์น ์ฌ์ดํธ๋ฅผ ์ ๊ทผํ๊ณ ์ ํ ๋, ์ ๊ทผํ๋ ค๋ ์น ์ฌ์ดํธ์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๊ณตํ์ง ์๊ณ ,
์๋ํํฐ ์ ํ๋ฆฌ์ผ์ด์ (๊ตฌ๊ธ, ์นด์นด์ค, ํ์ด์ค๋ถ ๋ฑ)์ ์ฐ๊ฒฐ์ ํตํด '์ธ์ฆ ๋ฐ ๊ถํ'์ ๋ถ์ฌ๋ฐ์ ์ ์๋ ํ๋กํ ์ฝ - ์ธ๋ถ์๋น์ค์ ์ธ์ฆ ๋ฐ ๊ถํ๋ถ์ฌ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฒ์ฉ์ ์ธ ํ๋กํ ์ฝ
-
Spring Boot OAuth 2 Client
- ์ธ๋ถ OAuth 2.0 ์๋น์ค์ ๋ํ ์ธ์ฆ์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๋ชจ๋
- ๊ฐ๋จํ ์ค์ ๋ง์ผ๋ก OAuth 2.0 ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ ์๋น์ค์ ์ธ์ฆ์ ์ฒ๋ฆฌํ ์ ์๋ค
-
Spring Boot OAuth 2 Server
- OAuth 2.0 ์๋ฒ๋ฅผ ๋น ๋ฅด๊ฒ ๊ตฌ์ถํ ์ ์๋๋ก ์ง์ํ๋ ๋ชจ๋
- ๊ฐ๋จํ ์ค์ ๋ง์ผ๋ก OAuth 2.0 ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ ์๋ฒ๋ฅผ ๊ตฌ์ถํ ์ ์๋ค
- ์ฌ์ฉ์๊ฐ ์๋ํํฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํํ๋ฉด ๋ก๊ทธ์ธ์ ์ํด ํด๋น ์น ์ฌ์ดํธ๋ก ๋ฆฌ๋ค์ด๋ ์ ๋๋ค
(User โ Client)- ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด, ํน์ ์น์ฌ์ดํธ์์ ์์ฒญํ ํน์ ๋ฐ์ดํฐ์ ๋ํ ์ก์ธ์ค ๊ถํ์ ๋ถ์ฌํ ์ง ๋ฌป๋ ๋ฉ์์ง๊ฐ ํ์๋๊ณ ,
์ํ๋ ์ต์ ์ ์ ํํ๋ฉด ์ธ์ฆ ์ฝ๋ ๋๋ ์ค๋ฅ ์ฝ๋์ ํจ๊ป ํน์ ์ฌ์ดํธ๋ก ๋ฆฌ๋ค์ด๋ ์ ๋๋ค
(Client โ Authorization Server)- ํ์ฌ ๋ฆฌ์์ค์ ์์ ์ ๋ฐ๋ผ ๋ก๊ทธ์ธ ์ฑ๊ณต ๋๋ ์คํจ (Client โ Resource Server)
- ํด๋ผ์ด์ธํธ๋ ๊ถํ ๋ถ์ฌ ์๋ฒ์์ ๊ถํ ๋ถ์ฌ ์ฝ๋๋ฅผ ์์ฒญํ๊ณ , ์ด๋ฅผ
Access Token์ผ๋ก ๊ตํ - ์ฌ์ฉ์์ ๋ฆฌ์์ค์ ์ก์ธ์คํด์ผ ํ๋ ์น ์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ค
- ๊ฐ์ฅ ๋์ค์ ์ด๊ณ ๋ง์ด ์ฌ์ฉ๋๋ ๋ฐฉ์
- ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ด
Access Token์ ์ง์ ๋ฐ๊ธ๋ฐ๋ ๊ฒ์ด ์๋๋ผ
์ฌ์ฉ์ ์์ด์ ํธ(์น ๋ธ๋ผ์ฐ์ ๋ฑ)๋ฅผ ํตํด ์ธ๊ฐ ๊ณผ์ ์ ๊ฑฐ์ณAccess Token์ ๋ฐ๊ธ๋ฐ๋ ๋ฐฉ์ - ํด๋ผ์ด์ธํธ๊ฐ ๊ถํ ๋ถ์ฌ ์ฝ๋๋ฅผ ๋จผ์ ์์ฒญํ๋ ๊ฒ์ด ์๋๋ผ, ์ง์ ์ก์ธ์ค ํ ํฐ์ ์์ฒญํ๋๋ฐ,
๋ณด์ ์ทจ์ฝ์ ๋๋ฌธ์ ๊ถ์ฅ๋์ง ์๋๋ค
- ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ด ์์ ์ ์ด๋ฆ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ฉํ์ฌ
Access Token์ ์ง์ ๋ฐ๊ธ๋ฐ๋ ๋ฐฉ๋ฒ - ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ฒด์ ์ธ์ฆ์ ์ฌ์ฉ๋จ
- ์ผ๋ฐ์ ์ธ ๋ก๊ทธ์ธ ๋ฐฉ๋ฒ
-
์ฌ์ฉ์์ ์ ๋ณด๋ ์ธ์ ์ ์ฅ์์ ์ ์ฅ๋๊ณ , ์ฟ ํค๋ ๊ทธ ์ ์ฅ์๋ฅผ ํต๊ณผํ ์ ์๋ ์ถ์ ์ฆ ์ญํ
-
์ฟ ํค๊ฐ ๋ด๊ธด HTTP ์์ฒญ์ด ๋์ค์ ๋ ธ์ถ๋๋๋ผ๋ ์ฟ ํค ์์ฒด์๋ ์ ์๋ฏธํ ๊ฐ์ ๊ฐ๊ณ ์์ง ์์์ ์ฟ ํค์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด์ ์ธ์ฆ์ ๊ฑฐ์น๋ ๊ฒ ๋ณด๋ค ์์ ํ๋ค
-
๊ฐ๊ฐ์ ์ฌ์ฉ์๋ ๊ณ ์ ์ Session ID๋ฅผ ๋ฐ๊ธ ๋ฐ๊ธฐ ๋๋ฌธ์ ์ผ์ผ์ด ํ์ ์ ๋ณด๋ฅผ ํ์ธํ ํ์๊ฐ ์์ด ์๋ฒ ์์์ ์ ๊ทผํ๊ธฐ ์ฉ์ดํ๋ค
-
์ธ์ ํ์ด์ฌํน ๊ณต๊ฒฉ
- ์ฟ ํค์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด์ ์ธ์ฆ์ ๊ฑฐ์น๋ ๊ฒ ๋ณด๋ค ์์ ํ์ง๋ง, ํด์ปค๊ฐ ์ฟ ํค๋ฅผ ํ์ทจํ ํ ๊ทธ ์ฟ ํค๋ฅผ ์ด์ฉํด HTTP ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๋ ์ฌ์ฉ์๋ก ์ค์ธํด ์ ๋ณด๋ฅผ ์ ๋ฌํ๊ฒ ๋๋ค
- HTTPS ํ๋กํ ์ฝ ์ฌ์ฉ๊ณผ ์ธ์ ์ ๋ง๋ฃ ์๊ฐ์ ๋ฃ์ด ์ด๋ ์ ๋ ๋ณด์ํ ์ ์๋ค
-
์๋ฒ์์ ์ธ์ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ์ ์ธ ์ ์ฅ๊ณต๊ฐ์ด ํ์ํ๋ค
Http Request- ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ์ ๋ณด์ ํจ๊ป ์ธ์ฆ ์์ฒญ
AuthenticationFilter๊ฐ ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ ,
> ๊ฐ๋ก์ฑ ์ ๋ณด๋ฅผ ํตํดUsernamePasswordAuthenticationToken์ด๋ผ๋ ์ธ์ฆ์ฉ ๊ฐ์ฒด ์์ฑํด์
AuthenticationManager์ ๊ตฌํ์ฒด์ธProviderManager์๊ฒ ์์ฑํUsernamePasswordAuthenticationToken๊ฐ์ฒด ์ ๋ฌ
AuthenticationManager๋ ๋ฑ๋ก๋AuthenticationProvider๋ค์ ์กฐํํ๊ณ ์ธ์ฆ ์๊ตฌ
AuthenticationProvider๋ ์ค์ DB์์ ์ฌ์ฉ์ ์ธ์ฆ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋UserDetailsService์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋๊ฒจ์ค๋ค
UserDetailsService๋AuthenticationProvider์๊ฒ ๋๊ฒจ๋ฐ์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํตํด,
> DB์์ ์ฐพ์ ์ฌ์ฉ์ ์ ๋ณด์ธUserDetails๊ฐ์ฒด๋ฅผ ๋ง๋ ๋ค
AuthenticationProvider๋ค์UserDetails๊ฐ์ฒด๋ฅผ ๋๊ฒจ๋ฐ๊ณ ์ฌ์ฉ์ ์ ๋ณด ๋น๊ต์ธ์ฆ์ด ์๋ฃ๋๋ฉด, ๊ถํ ๋ฑ์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด์
Authentication๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค๋ค์ ์ต์ด์
AuthenticationFilter์Authentication๊ฐ์ฒด๊ฐ ๋ฐํ๋๊ณ
Authenticaton๊ฐ์ฒด๋ฅผSecurityContext์ ์ ์ฅ
-
ํ์ฌ ์ ๊ทผํ๋ ์ฃผ์ฒด์ ์ ๋ณด์ ๊ถํ์ ๋ด๋ ์ธํฐํ์ด์ค
-
Authentication๊ฐ์ฒด๋SecurityContext์ ์ ์ฅ๋๋ฉฐ,
SecurityContextHolder๋ฅผ ํตํดSecurityContext์ ์ ๊ทผํ๊ณ ,
SecurityContext๋ฅผ ํตํดAuthentication์ ์ ๊ทผํ ์ ์๋ค
-
Authentication์ implementsํAbstractAuthenticationToken์ ํ์ ํด๋์ค
์ฆ,Authentication์ ๊ตฌํ์ฒด์ด๊ณ , ๊ทธ๋์AuthenticationManager์์ ์ธ์ฆ๊ณผ์ ์ ์ํํ ์ ์๋ค -
์ถํ ์ธ์ฆ์ด ๋๋๊ณ
SecurityContextHolder์ ๋ฑ๋ก๋Authentication๊ฐ์ฒด -
User์ ID๋ฅผ
Principal๋ก, Password๋ฅผCredential๋ก ์์ฑํ ์ธ์ฆ ๊ฐ์ฒด์ฌ๊ธฐ์์ ๋งํ๋
Principal์ญํ ์ ํ๋ User์ ID ๋๋ Username์ ๋ก๊ทธ์ธ ์ ID์ PW์ ID๋ฅผ ๋ฃํ๋ค
๋ก๊ทธ์ธ ์ email์ ID๋ก ์ฌ์ฉํ๋ค๋ฉด email์ด, ์ ํ๋ฒํธ๋ฅผ ID๋ก ์ฌ์ฉํ๋ค๋ฉด ์ ํ๋ฒํธ๊ฐ ๊ณง Username์ด ๋๋ค -
UsernamePasswordAuthenticationToken์ ์ฒซ ๋ฒ์งธ ์์ฑ์๋ ์ธ์ฆ ์ ์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ,
๋ ๋ฒ์งธ๋ ์ธ์ฆ์ด ์๋ฃ๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}- ๋ง๋ค์ด์ง
UsernamePasswordAuthenticationToken์AuthenticationManager์ ์ธ์ฆ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐ ์ฌ์ฉ๋๋ค - ์ธ์ฆ์ ๋ํ ๋ถ๋ถ์
AuthenticationManager๋ฅผ ํตํด์ ์ฒ๋ฆฌํ๊ฒ ๋๋๋ฐ,
์ค์ง์ ์ผ๋ก๋AuthenticationManager์ ๋ฑ๋ก๋AuthenticationProvider์ ์ํด ์ฒ๋ฆฌ๋๋ค - ์ธ์ฆ์ ์ฑ๊ณตํ๋ฉด ๋ ๋ฒ์งธ ์์ฑ์๋ฅผ ์ด์ฉํด ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ
SecurityContext์ ์ ์ฅํ๋ค
AuthenticationManager์ ๊ตฌํ์ฒดAuthenticationProvider์์๋ ์ค์ ์ธ์ฆ์ ๋ํ ๋ถ๋ถ์ ์ฒ๋ฆฌํ๋๋ฐ,
์ธ์ฆ ์ ์Authentication๊ฐ์ฒด๋ฅผ ๋ฐ์์ ์ธ์ฆ์ด ์๋ฃ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ์ญํ ์ ํ๋ค- Customํ
AuthenticationProvider๋ฅผ ์์ฑํ๊ณAuthenticationManager์ ๋ฑ๋กํ๋ฉด ๋๋ค
AuthenticationManager๋ฅผ implementsํ ๊ตฌํ์ฒดProviderManager๋
AuthenticationProvider๋ฅผ ๊ตฌ์ฑํ๋ ๋ชฉ๋ก์ ๊ฐ๋๋ค
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}- Spring Security์ interface์ด๊ณ , ๊ตฌํ์ฒด๋ ์ง์ ๊ฐ๋ฐํด์ผํ๋ค (customize)
username์ ๊ธฐ๋ฐ์ผ๋ก ๊ฒ์ํUserDetails๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ํ๋์ ๋ฉ์๋loadUserByUsername๋ง์ ๊ฐ์ง๊ณ ์๊ณ , ์ผ๋ฐ์ ์ผ๋ก ์ด๋ฅผ implementsํ ํด๋์ค์UserRepository๋ฅผ ์ฃผ์ ๋ฐ์ DB์ ์ฐ๊ฒฐํ์ฌ ์ฒ๋ฆฌํ๋คUserDetailsService๋ DB์ ์ ์ฅ๋ ํ์์ ๋น๋ฐ๋ฒํธ์ ๋น๊ตํ๊ณ ,
์ผ์นํ๋ฉดUserDetails์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค
- ์ธ์ฆ์ ์ฑ๊ณตํ์ฌ ์์ฑ๋
UserDetails๊ฐ์ฒด๋Authentication๊ฐ์ฒด๋ฅผ ๊ตฌํํUsernamePasswordAuthenticationToken์ ์์ฑํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค
- ๋ณด์ ์ฃผ์ฒด์ ์ธ๋ถ ์ ๋ณด๋ฅผ ํฌํจํ์ฌ ์์ฉํ๋ก๊ทธ๋จ์ ํ์ฌ ๋ณด์ ์ปจํ ์คํธ์ ๋ํ ์ธ๋ถ ์ ๋ณด๊ฐ ์ ์ฅ๋๋ค
SecurityContextHolder๋ThreadLocal์ ์ ์ฅ๋์ด,Thread๋ณ๋กSecurityContextHolder์ธ์คํด์ค๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์,
์ฌ์ฉ์ ๋ณ๋กAuthentication๊ฐ์ฒด๋ฅผ ๊ฐ์ง ์ ์๋ค
- ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด
Authentication์ ๋ณด๊ดํ๋ ์ญํ SecurityContext๋ฅผํตํดAuthentication์ ์ ์ฅํ๊ฑฐ๋ ๊บผ๋ด์ฌ ์ ์๋ค
SecurityContextHolder.getContext().setAuthentication(authentication);
SecurityContextHolder.getContext().getAuthentication(authentication);โ UsernamePasswordAuthenticationToken ๊ฐ์ฒด
- ํ์ฌ ์ฌ์ฉ์(Principal)๊ฐ ๊ฐ์ง๊ณ ์๋ ๊ถํ ์๋ฏธ
ROLE_ADMIN์ด๋ROLE_USER์ ๊ฐ์ดROLE_*์ ํํ๋ก ์ฌ์ฉํ๋คGrantedAuthority๊ฐ์ฒด๋UserDetailsService์ ์ํด ๋ถ๋ฌ์ฌ ์ ์๊ณ ,- ํน์ ์์์ ๋ํ ๊ถํ์ด ์๋์ง ๊ฒ์ฌํด ์ ๊ทผ ํ์ฉ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ค
๋ณ๊ฒฝ
์คํ๋ง ๋ถํธ 3.0 ์ด์๋ถํฐ ์คํ๋ง ์ํ๋ฆฌํฐ 6.0.0 ์ด์์ ๋ฒ์ ์ด ์ ์ฉ๋๋ฉฐ
Deprecated๋ ์ฝ๋ ๋ณ๊ฒฝ
//.httpBasic().disable()
.httpBasic(HttpBasicConfigurer::disable)- UI์ชฝ์ผ๋ก ๋ค์ด์ค๋ ์ค์
- Http basic Auth ๊ธฐ๋ฐ์ผ๋ก ๋ก๊ทธ์ธ ์ธ์ฆ์ฐฝ์ด ๋จ๋๋ฐ, JWT๋ฅผ ์ฌ์ฉํ ๊ฑฐ๋ผ ๋จ์ง ์๋๋ก ์ค์
+formLogin.disable(): formLogin ๋์ JWT๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ disable๋ก ์ค์
//.csrf.disable()
//.cors().and()
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())- API๋ฅผ ์์ฑํ๋๋ฐ ํ๋ก ํธ๊ฐ ์ ํด์ ธ์์ง ์๊ธฐ ๋๋ฌธ์ csrf ์ค์ ์ฐ์ ๊บผ๋๊ธฐ
- Cross Site Request Forgery : ์ฌ์ดํธ ๊ฐ ์์กฐ ์์ฒญ
- ์น ์ฌ์ดํธ ์ทจ์ฝ์ ๊ณต๊ฒฉ ๋ฐฉ๋ฒ ์ค ํ๋๋ก, ์ฌ์ฉ์๊ฐ ์์ ์ ์์ง์๋ ๋ฌด๊ดํ๊ฒ ๊ณต๊ฒฉ์๊ฐ ์๋ํ ํ์๋ฅผ ํน์ ์น ์ฌ์ดํธ์ ์์ฒญํ๊ฒ ํ๋ ๊ณต๊ฒฉ
- Spring Security์์๋ CSRF์ ๋ํ ์๋ฐฉ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค
- ๊ทผ๋ฐ ์ด ์ข์ ๊ธฐ๋ฅ์ ์ disable?
- ์คํ๋ง ์ํ๋ฆฌํฐ ๋ฌธ์์์๋ ์ผ๋ฐ ์ฌ์ฉ์๊ฐ ๋ธ๋ผ์ฐ์ ์์ ์ฒ๋ฆฌํ ์ ์๋ ๋ชจ๋ ์์ฒญ์ CSRF ๋ณดํธ๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํ๊ณ ,
๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ์ง ์๋ ํด๋ผ์ด์ธํธ๋ง ์ฌ์ฉํ๋ ์๋น์ค๋ฅผ ๋ง๋๋ ๊ฒฝ์ฐ CSRF ๋ณดํธ๋ฅผ ๋นํ์ฑํํ๋ ๊ฒ์ด ์ข๋ค๊ณ ํจ - ์ฌ๊ธฐ์์ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ์ง ์๋ ํด๋ผ์ด์ธํธ๋ง ์ฌ์ฉํ๋ ์๋น์ค โ ๋๋ถ๋ถ์ REST API ์๋น์ค๋ผ๊ณ ์ดํดํจ
์ฆ ๋๋ถ๋ถ์ ๊ฐ์ด๋๋ REST API ์๋ฒ ๊ธฐ์ค์ผ๋ก disable์ ์ ์ฉํ๊ณ ์๋ค
- ์คํ๋ง ์ํ๋ฆฌํฐ ๋ฌธ์์์๋ ์ผ๋ฐ ์ฌ์ฉ์๊ฐ ๋ธ๋ผ์ฐ์ ์์ ์ฒ๋ฆฌํ ์ ์๋ ๋ชจ๋ ์์ฒญ์ CSRF ๋ณดํธ๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํ๊ณ ,
- Cross-Origin Resource Sharing : ์๋ก ๋ค๋ฅธ Orgin ๊ฐ์ ์ํธ์์ฉ ์ ๋ธ๋ผ์ฐ์ ์์ ์ด๋ฅผ ์ค์งํ๊ธฐ ์ํด ์ ๊ณตํ๋ ๊ธฐ๋ณธ ๋ณดํธ ๊ธฐ๋ฅ, ํ๋กํ ์ฝ
- HTTP ์์ฒญ์ ๊ธฐ๋ณธ์ ์ผ๋ก Cross-Site HTTP Requests๊ฐ ๊ฐ๋ฅ (๋ค๋ฅธ ๋๋ฉ์ธ ์ฌ์ฉ ๊ฐ๋ฅ)
ํ์ง๋ง Cross-Site HTTP Requests๋ Same Origin Policy๋ฅผ ์ ์ฉ๋ฐ๊ธฐ ๋๋ฌธ์,
ํ๋กํ ์ฝ, ํธ์คํธ๋ช , ํฌํธ๊ฐ ๊ฐ์์ผ๋ง ์์ฒญ์ด ๊ฐ๋ฅํ๋ค cors()๋ก cors์ ๋ํ ์ปค์คํ ์ค์ ํ์ฉaddAllowedOrigin(): ํ์ฉํ URL ์ค์ addAllowedHeader(): ํ์ฉํ Header ์ค์ addAllowedMethod(): ํ์ฉํ Http Method ์ค์
//.authorizeRequests()
//.requestMatchers("/api/**").permitAll()
//.requestMatchers("/api/**/users/join", "/api/**/users/login").permitAll()
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/**").permitAll()
.requestMatchers("/api/v1/users/join", "/api/v1/users/login").permitAll())-
ํน์ ํ ๊ฒฝ๋ก์ ํน์ ํ ๊ถํ์ ๊ฐ์ง ์ฌ์ฉ์๋ง ์ ๊ทผํ ์ ์๋๋ก ํ๋ ์ค์
-
authorizeRequests(): ์ํ๋ฆฌํฐ ์ฒ๋ฆฌ์ HttpServletRequest๋ฅผ ์ด์ฉํ๋ค๋ ๊ฒ, ๊ฐ ๊ฒฝ๋ก๋ณ ๊ถํ ์ฒ๋ฆฌ -
requestMatchers(): ํน์ ํ ๊ฒฝ๋ก ์ง์ - ๋ง์ฝ spring-security 5.8 ์ด์์ ๋ฒ์ ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋
antMatchers,mvcMatchers,regexMatchers๊ฐ ๋ ์ด์ ์ฌ์ฉ๋์ง ์๊ธฐ ๋๋ฌธ์,
requestMatchers๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค๊ณ ํจ
URL ํจํด
/*๊ณผ/**/*: ๊ฒฝ๋ก์ ๋ฐ๋ก ํ์์ ์๋ ๋ชจ๋ ๊ฒฝ๋ก ๋งคํ
ex.
AAA/*:AAA/BBB,AAA/CCCํด๋น,AAA/BBB/CCCํด๋นํ์ง ์์/**: ๊ฒฝ๋ก์ ๋ชจ๋ ํ์ ๊ฒฝ๋ก(๋๋ ํ ๋ฆฌ) ๋งคํ
ex.
AAA/**:AAA/BBB,AAA/CCC,AAA/BBB/CCC,AAA/.../.../DDD/...,AAA/BBB/CCC/.../.../...์ ๋ถ ํด๋น - ๋ง์ฝ spring-security 5.8 ์ด์์ ๋ฒ์ ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋
-
permitAll(): ๋ชจ๋ ์ฌ์ฉ์๊ฐ ์ธ์ฆ ์ ์ฐจ ์์ด ์ ๊ทผํ ์ ์์ -
authenticated(): ์ธ์ฆ๋ ์ฌ์ฉ์๋ง ์ ๊ทผ ๊ฐ๋ฅ -
hasRole(): ์์คํ ์์์ ํน์ ๊ถํ์ ๊ฐ์ง ์ฌ๋๋ง์ด ์ ๊ทผํ ์ ์์ -
anyRequest().authenticated(): ๋๋จธ์ง ๋ชจ๋ ๋ฆฌ์์ค๋ค์ ๋ฌด์กฐ๊ฑด ์ธ์ฆ์ ์๋ฃํด์ผ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค๋ ์๋ฏธ
//.sessionManagement()
//.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionManagement((sessionManagement) -> sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))- ์คํ๋ง ์ํ๋ฆฌํฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก session์ ์ฌ์ฉํด ์น์ ์ฒ๋ฆฌํ๋๋ฐ,
JWT๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ session์ stateless๋ก ์ค์ , ์ธ์ ์ฌ์ฉํ์ง ์์
- Spring Seurity ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ํด๋์ค ์ค ํ๋๋ก ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง ํด๋์ค
- ํจ์ค์๋๋ฅผ ์ํธํํด์ฃผ๋ ๋ฉ์๋,
String๋ฐํ - ๋๊ฐ์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ธ์ฝ๋ฉํ๋๋ผ๋ ๋งค๋ฒ ๋ค๋ฅธ ๋ฌธ์์ด์ ๋ฐํํ๋ค
- ์ ์ถ๋ ์ธ์ฝ๋ฉ ๋์ง ์์ ํจ์ค์๋(์ผ์น ์ฌ๋ถ๋ฅผ ํ์ธํ๊ณ ์ ํ๋ ํจ์ค์๋)์ ์ธ์ฝ๋ฉ ๋ ํจ์ค์๋์ ์ผ์น ์ฌ๋ถ ํ์ธ
- ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ์ผ์น ์ฌ๋ถ๋ฅผ ํ์ธํ๊ณ ์ ํ๋ ์ธ์ฝ๋ฉ ๋์ง ์์ ํจ์ค์๋,
๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ์ธ์ฝ๋ฉ๋ ํจ์ค์๋ ์ ๋ ฅ boolean๋ฐํ
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'- JWT ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํต์ฌ API๋ฅผ ์ ๊ณตํ๊ณ JWT์ ์์ฑ ๋ฐ ๊ฒ์ฆ์ ๋ค๋ฃฐ ์ ์๋ค
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'jjwt-impl์์กด์ฑ์ ์ถ๊ฐํ์ง ์์ ์ฑJwts.builder()๋ฅผ ํธ์ถํ๊ฒ ๋๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'jjwt-impl์ ๊ตฌํ์ฒด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก,jjwt-jackson์ธ์๋jjwt-gson์ด ์๋คjjwt-jackson์์กด์ฑ์ ์ถ๊ฐํ์ง ์์ผ๋ฉดcompact๋ฉ์๋๋ฅผ ์ฒ๋ฆฌํ๋ ๋์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค
โjjwt-impl์์ ๊ตฌํ์ฒด๋ฅผ ์ฐพ์๋ณด์ง๋ง ์๊ธฐ์ ์ค๋ฅ๊ฐ ๋ฐ์
jjwt-api๋ ํจํค์ง ๊ด๋ฆฌ์ ์์ด์implemenation๊ณผruntimeonly๋ก ๊ตฌ๋ถํ์ฌ ์์กด์ฑ ์ถ๊ฐ๋ฅผ ๊ถ์ฅํ๊ณ ์๋ค
๊ฒฝ๊ณ ์์ด ์ธ์ ๋ ๋ณํ ์ ์๋ ํจํค์ง๋runtimeonly๋ก ๊ด๋ฆฌํ๊ณ ๊ทธ๋ ์ง ์์ ๊ฒ์implemenation์ผ๋ก ๊ด๋ฆฌํด
์์ ์ ์ผ๋กjjwt-api๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ฒ ๋ค๋ ์๋
์ฆ,jjwt-impl,jjwt-jackson๋๋jjwt-gson์ ๊ฒฝ๊ณ ์์ด ์ธ์ ๋ ๋ณํํ ์ ์๊ณ
jjwt-api๋ ํ์ํธํ์ฑ์ ๋ง์ถฐ๊ฐ๋ฉฐ ๊ฐ๋ฐํ๋ค๋ ์๋ฏธ
์ค์ ๋ก ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ํ์ํธํ์ฑ์ ๋ํ ์ธ๊ธ๊ณผ@Deprecated๋ฅผ ํตํด ์ฝ๋๋ฅผ ์ ์งํ๋ ค๋ ๋ ธ๋ ฅ์ ์ดํด๋ณผ ์ ์๋ค
- JWT ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ ์ญํ ์ ํ๋ ํฉํ ๋ฆฌ ํด๋์ค
public static String createToken(String userName, Key key, long expireTimeMs) {
Claims claims = Jwts.claims(); //์ผ์ข
์ Map
claims.put("userName", userName);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expireTimeMs))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}- Header ์ค์
.setHeaderParam("key", "value")๋๋.setHeader(header)์ ๊ฐ์ ๋ฐฉ์ ์ฌ์ฉ ๊ฐ๋ฅ
-
setClaims(): JWT์ ํฌํจ์ํฌ Custom Claims ์ถ๊ฐ - ์ฃผ๋ก ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด.claim("key", "value")๋๋.setClaims(claims)์ ๊ฐ์ ๋ฐฉ์ ์ฌ์ฉ ๊ฐ๋ฅ
-
setSubject(): JWT์ ๋ํ ์ ๋ชฉ -
setIssuedAt(): JWT ๋ฐํ ์ผ์ - ํ๋ผ๋ฏธํฐ ํ์ ์java.util.Date -
setExpiration(): JWT์ ๋ง๋ฃ๊ธฐํ - ํ๋ผ๋ฏธํฐ ํ์ ์java.util.Date -
signWith(): ์๋ช ์ ์ํKey(java.security.Key)๊ฐ์ฒด ์ค์ //.signWith(SignatureAlgorithm.HS256, key) .signWith(key, SignatureAlgorithm.HS256)
- ํน์ ๋ฌธ์์ด(String)์ด๋ byte๋ฅผ ์ธ์๋ก ๋ฐ๋ ๋ฉ์๋๋ก ์ฌ์ฉ์ด ์ค๋จ๋์๋๋ฐ,
๋ง์ ์ฌ์ฉ์๊ฐ ์์ ํ์ง ์์ ์์์ ์ธ ์ํธ ๋ฌธ์์ด์ ํค ์ธ์๋ก ์ฌ์ฉํ๋ ค๊ณ ์๋ํ๋ฉฐ ํผ๋์ค๋ฌ์ํ๊ธฐ ๋๋ฌธ์ด๋ผ๊ณ ํ๋ค
String์ด ์๋๋ผKey๊ฐ์ ์์ฑํ๊ณ ์๋ช ์ ์งํํด์ผ ํ๋ค
- ํน์ ๋ฌธ์์ด(String)์ด๋ byte๋ฅผ ์ธ์๋ก ๋ฐ๋ ๋ฉ์๋๋ก ์ฌ์ฉ์ด ์ค๋จ๋์๋๋ฐ,
-
compact(): JWT ์์ฑํ๊ณ ์ง๋ ฌํ
ํ ํฐ์ ์์ฑํ๊ธฐ ์ํ Key
String keyBase64Encoded = Base64.getEncoder().encodeToString(key.getBytes());
SecretKey key = Keys.hmacShaKeyFor(keyBase64Encoded.getBytes());- ์ฌ์ฉํ๊ณ ์ ํ๋
plain secretKey(์ํธํ ๋์ง ์์, ์ฒซ ๋ฒ์งธ ์ค์key)๋ฅผbyte๋ฐฐ์ด๋ก ๋ณํํด์ฃผ๊ณ , - HMAC-SHA ์๊ณ ๋ฆฌ์ฆ์ ํตํด ์ํธํํด์ฃผ๋
Keys.hmacShaKeyFor๋ฅผ ํตํด ์ํธํ๋Key๊ฐ์ฒด๋ก ๋ง๋ค์ด์ฃผ๋ ์ฝ๋
secretKey๊ฐ256bit๋ณด๋ค ์ปค์ผ ํ๋ค๋Exception- ์ํ๋ฒณ ํ ๊ธ์๋น8bit์ด๋ฏ๋ก 32๊ธ์ ์ด์์ด์ด์ผ ํ๋ค๋ ๋ป- ํ๊ธ์ ํ ๊ธ์ ๋น
16bit์ธ๋ฐ 16๊ธ์์ด๋ฉด ์์ฑ๋ ๊น? โ ์์ฑ๋๋ค
Jwts.parserBuilder()๋ฉ์๋๋กJwtParserBuilder์ธ์คํด์ค ์์ฑ- JWS ์๋ช ๊ฒ์ฆ์ ์ํ
SecretKey๋๋๋น๋์นญ ๊ณต๊ฐํค์ง์ > >TOKEN๋ฐ๊ธ ์ ์ฌ์ฉํ๋secretKeybuild()๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด thread-safeํJwtParser๊ฐ ๋ฐํ๋๋คparseClaimsJws(jwtString)๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ์ค๋ฆฌ์ง๋ signed JWT๊ฐ ๋ฐํ๋๋ค- ๊ฒ์ฆ์ ์คํจํ๋ฉด
Exception๋ฐ์
Jws<Claims> jws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token); -
parseClaimsJws(token)- ํ๋ผ๋ฏธํฐ๋ก ์ฃผ์ด์ง
JWT ํ ํฐํ์ฑ JWT ํ ํฐ์ ๊ตฌ์ฑ ์์ Header, Body(Payload), Signature๋ฅผ ๋ถ์ํ๊ณ ,
์๋ช ์ ํ์ธํด JWT์ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆJWT ํ ํฐ์์ฑ ์์Claim์ ๋ณด๋ฅผ ์ถ์ถํ ์ ์๋ค
- ํ๋ผ๋ฏธํฐ๋ก ์ฃผ์ด์ง
-
parseClaimsJwt()parseClaimsJws()๊ฐ ์๋๋ผparseClaimsJwt()๋ฅผ ์ฌ์ฉํ๋ฉด ์ค๋ฅ ๋ฐ์- ์ฒ์์
TOKEN์ ์์ฑํ ๋signWith()๋ฅผ ํตํด ์๋ช ์ ํ๊ธฐ ๋๋ฌธ์
๋ณตํธํ ์์๋ ์๋ช ์ ๋ํ ๊ฒ์ฆ์ ์งํํด์ผ ํ๋ค parseClaimsJwt()๋ ์๋ช ๊ฒ์ฆ ์์ด ๋จ์ํ ํค๋์ ํด๋ ์๋ง ์ถ์ถํ๋คparseClaimsJwt()๋ฅผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉดTOKEN์์ฑ ์signWith()๋ฅผ ํตํด ์๋ช ์ ๋ํ ์ ๋ณด๋ฅผ ๋๊ฒจ์ฃผ์ง ์์ผ๋ฉด ๋๋ค
Claims claims = jws.getBody();-
getBody()TOKEN์Claim์ ๋ณด ๋๋ ํ ํฐ์ ํฌํจ๋ ๋ฐ์ดํฐ,
์ฆ,TOKEN์์ฑ ์ ํฌํจํ ์ฌ์ฉ์ ์ ๋ณด, ๊ถํ, ๋ง๋ฃ ์๊ฐ ๋ฑ์ ์ถ์ถํ ์ ์๋ค
-
์ด ์ธ์๋
getHeader()์getSignature()๋ฅผ ํตํด ๊ฐ๊ฐTOKEN์ ๋ฉํ๋ฐ์ดํฐ์ ์๋ช ์ ์ถ์ถํ ์ ์๋ค
String username = claims.get("username", String.class); // "username" ํด๋ ์ ๊ฐ ์ถ์ถ
String role = claims.get("role", String.class); // "role" ํด๋ ์ ๊ฐ ์ถ์ถ
Date expiration = claims.getExpiration();
Date issuedAt = claims.getIssuedAt();-
get()- ํค์ ๊ฐ์ ์์ผ๋ก ์ ์ฅ๋
Claim์ ํค๋ฅผ ํตํด ๊ฐ์ ์ฐพ์ ์ ์๋ค
public abstract <T> T get(String claimName, Class<T> requiredType)
Claimํค์ ํ์ ์ ๋ง๋ ๊ฐ ๋ฐํ
- ํค์ ๊ฐ์ ์์ผ๋ก ์ ์ฅ๋
-
์ด ์ธ์๋
TOKEN๋ง๋ฃ ์๊ฐ์ ์ถ์ถํ๋getExpiration()์ด๋
TOKEN์์ฑ ์๊ฐ์ ์ถ์ถํ๋getIssuedAt()๋ฑ์ ๋ฉ์๋๊ฐ ์๋ค
- ์ค๋ณต ์ฒดํฌ
UserDuplicatedException()- ํ์๊ฐ์
BCryptPasswordEncoder.encode()- ๋น๋ฐ๋ฒํธ ์ํธํํด์ ์ ์ฅ
public ResponseEntity<Void> signUp(SignUpDto signUpDto) {
//์ค๋ณต์ฒดํฌ
userRepository.findByPhone(signUpDto.getPhone())
.ifPresent(user -> {
throw new UserDuplicatedException();
});
//ํ์๊ฐ์
userRepository.save(User.builder()
.phone(signUpDto.getPhone())
.nickname(signUpDto.getNickname())
.role(Role.USER)
.password(passwordEncoder.encode(signUpDto.getPassword()))
.build()
);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
- ๋ก๊ทธ์ธ์ฉ ID ํ์ธ
UserNotFoundException- ๋น๋ฐ๋ฒํธ ํ์ธ
InvalidPasswordException()TOKEN๋ฐํ
public SignInResponseDto signIn(SignInDto signInDto) {
//์ ํ๋ฒํธ ํ์ธ
User user = userRepository.findByPhone(signInDto.getPhone())
.orElseThrow(UserNotFoundException::new);
//๋น๋ฐ๋ฒํธ ํ์ธ
if (!passwordEncoder.matches(signInDto.getPassword(), user.getPassword())) {
throw new InvalidPasswordException();
}
//TOKEN ๋ฐํ
String accessToken = jwtTokenProvider.createAccessToken(user.getId(), signInDto.getPhone(), user.getRole().toString());
return SignInResponseDto.builder().accessToken(accessToken).build();
}
- ๋ชจ๋
POST์ ๊ทผ ๋ง๊ธฐ
- JwtAuthenticationFilter ์ธ์ฆ ๊ณ์ธต ์ถ๊ฐํ๊ธฐ
- ๋ชจ๋ ์์ฒญ์ ๊ถํ ๋ถ์ฌํ๊ธฐ
TOKEN์ฌ๋ถ ํ์ธ
- TOKEN ์์ผ๋ฉด ๊ถํ ๋ถ์ฌ
- TOKEN์ด ์์ผ๋ฉด ๊ถํ ๋ถ์ฌํ์ง ์๊ธฐ
TOKEN์ ํจ์ฑ ๊ฒ์ฆ
- TOKEN์ ์ ํจ์๊ฐ์ด ์ง๋ฌ๋์ง ํ์ธํ๊ธฐ
TOKEN์์ userName(id) ๊บผ๋ด์ Controller์์ ์ฌ์ฉํ๊ธฐ
-
์ฆ๋ช ํ๋ค๋ผ๋ ์๋ฏธ๋ก, ์๋ฅผ ๋ค์ด ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ด์ฉํ์ฌ ๋ก๊ทธ์ธ ํ๋ ๊ณผ์
-
ํด๋น ์ฌ์ฉ์๊ฐ ๋ณธ์ธ์ด ๋ง๋์ง ํ์ธํ๋ ๊ณผ์
-
๊ถํ๋ถ์ฌ๋ ํ๊ฐ์ ๊ฐ์ ์๋ฏธ๋ก ์ฌ์ฉ๋๊ณ , ์ด๋ค ๋์์ด ํน์ ๋ชฉ์ ์ ์คํํ๋๋ก ํ์ฉ(Access) ํ๋ ๊ฒ ์๋ฏธ
-
ํด๋น ์ฌ์ฉ์๊ฐ ์์ฒญํ๋ ์์์ ์คํํ ์ ์๋ ๊ถํ์ด ์๋๊ฐ๋ฅผ ํ์ธํ๋ ๊ณผ์
์์ ๋ก๊ทธ์ธ์์ ์ค์ ํ๋ SecurityConfig์ SecurityFilterChain ์ฌ์ ์ ์ด์ฉ
โ @EnableWebSecurity
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/*/*/signup", "/api/*/*/signin").permitAll()
.requestMatchers(HttpMethod.GET).permitAll()
.requestMatchers(HttpMethod.POST, "/api/**").authenticated())- ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ์ ๋๊ตฌ๋ ๊ถํ ์์ด ์ธ์ ๋ ์ ๊ทผํ ์ ์์ง๋ง
- ๋ฆฌ๋ทฐ ์ฐ๊ธฐ ๋ฑ ๋ค๋ฅธ ๋ชจ๋ ์์ฒญ์ ๋ํด์๋ ๊ถํ ํ์
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class)addFilterBefore()- JWT ์ธ์ฆ ํํฐ
JwtAuthenticationFilter๋ฅผUsernamePasswordAuthenticationFilter์ด์ ์ ์ถ๊ฐํ๋ ์ญํ - ํ ํฐ์ด ์๋์ง ๋งค๋ฒ ํญ์ ํ์ธํด์ผ ํ๋ค
public HttpSecurity addFilterBefore( @NotNull jakarta.servlet.Filter filter, Class<? extends jakarta.servlet.Filter> beforeFilter)
- JWT ์ธ์ฆ ํํฐ
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException { ... }Filter์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ํด๋์ค์์ ์ค๋ฒ๋ผ์ด๋ํ ๋ฉ์๋ ์ค ํ๋- HTTP ์์ฒญ์ ํํฐ๋งํ๊ณ , ํํฐ๊ฐ ์ ์ฉ๋ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ์ญํ
-
- Header์์ TOKEN ๊บผ๋ด๊ธฐ
- TOKEN ์ฌ๋ถ์ ์ ํจ์ฑ ํ์ธ
- TOKEN์ด ์ ํจํ๋ฉด - ๊ถํ ๋ถ์ฌ
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);- ํ์ฌ ์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ
authentication์ผ๋ก ๋ณ๊ฒฝ SecurityContextHolder.getContext()- ํ์ฌ ์ฌ์ฉ์ ๋ฐ ์ธ์ฆ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋
SecurityContextHolder๊ฐ์ฒด์์ - ํ์ฌ ์ฌ์ฉ์์ ๊ด๋ จ๋ ์ ๋ณด๊ฐ ์ ์ฅ๋๋ ๋ณด์ ์ปจํ ์คํธ ๊ฐ์ ธ์ค๊ธฐ
- ํ์ฌ ์ฌ์ฉ์ ๋ฐ ์ธ์ฆ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋
.setAuthentication(authentication)- ํ์ฌ ์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด
authentication์ผ๋ก ์ค์
- ํ์ฌ ์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด
filterChain.doFilter(request, response);doFilter()public abstract void doFilter( jakarta.servlet.ServletRequest request, jakarta.servlet.ServletResponse response)
Filter์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํํฐ์์ ์ ์๋ ๋ฉ์๋- ํํฐ๊ฐ ์์ฒญ(request) ๋ฐ ์๋ต(response)์ ์ฒ๋ฆฌํ๋ ๋ฉ์๋
- ํํฐ๋ ์ด ๋ฉ์๋๋ฅผ ํตํด ์์ฒญ๊ณผ ์๋ต์ ๊ฐ๋ก์ฑ๊ณ ์์ ํ ์ ์๋ค
ex. ์์ฒญ์ ๊ฐ๋ก์ฑ ๊ถํ ํ์ธํ๊ธฐ - ํ์ฌ ํํฐ์์ ์์ฒญ ๋ฐ ์๋ต์ ์ฒ๋ฆฌํ๊ณ ,
์ดํ์ ์คํ๋ ๋ค์ ํํฐ๋ฅผ ํธ์ถํ๊ธฐ ์ํดFilterChain์doFilter()๋ฅผ ํธ์ถํ๋๋ฐ,
์ด ๋, ๋ค์ ํํฐ๋ก ์์ฒญ ๋ฐ ์๋ต ๊ณ์ ์ ๋ฌ
- TOKEN ์์ผ๋ฉด ๊ถํ ๋ถ์ฌ
- TOKEN์ด ์์ผ๋ฉด ๊ถํ ๋ถ์ฌํ์ง ์๊ธฐ
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);- ํ ํฐ์ด ์์ผ๋ฉด ์๋ํ์ง ์์!
![]() |
๊ทผ๋ฐ ์๋ฌด TOKEN์ ๋ฃ์ด๋ ์๋ํ๋ ๋ฌธ์ ! |
|---|
- TOKEN์ ์ ํจ์๊ฐ์ด ์ง๋ฌ๋์ง ํ์ธํ๊ธฐ
public boolean validateToken(String token) {
//Token ๋ง๋ฃ ์๊ฐ ๋๋ null ๋ฐํ
Date expiration = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.getExpiration();
boolean isExpired = expiration.before(new Date());
return !isExpired;
}TOKEN๋ง๋ฃ๋ก ์ธํExpiredJwtException๋ฐ์
public String getUserId(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}-
TOKEN์์userName(ID)์Claim์ถ์ถํ๋ ๋ฉ์๋JwtUtil.getUsername()์์ฑ -
๊ทธ๋ฆฌ๊ณ ์ถ์ถํ
๋ก๊ทธ์ธID๋ฅผUsernamePasswordAuthenticationToken์ ๋ฃ์ด์ฃผ๋ฉดController์์๋ก๊ทธ์ธID๋ฅผ ์ฌ์ฉํ ์ ์๋ค
import org.springframework.security.core.Authentication;
...
@PostMapping
public ResponseEntity<String> writeReview(Authentication authentication) {
return ResponseEntity.ok().body(authentication.getName());
}๋๋
@PostMapping
public ResponseEntity<Void> registerPost(@RequestBody RegisterPostRequestDto requestDto, @AuthenticationPrincipal User user) {
postService.registerPost(requestDto, user);
return ResponseEntity.ok().build();
}Docker client: ๋์ปค ์ค์นํ์ ๋ ๊ทธ๊ฒ ๋ฐ๋ก client์ด๊ณ , build, pull, run ๋ฑ์ ๋์ปค ๋ช ๋ น์ด ์ํDOCKER_HOST: ๋์ปค๊ฐ ๋์ด์ ธ์๋ ์๋ฒ ์๋ฏธ,DOCKER_HOST์์ ์ปจํ ์ด๋์ ์ด๋ฏธ์ง ๊ด๋ฆฌDocker daemon: ๋์ปค ์์งRegistry: ์ธ๋ถ(remote) ์ด๋ฏธ์ง ์ ์ฅ์๋ก ๋ค๋ฅธ ์ฌ๋๋ค์ด ๊ณต์ ํ ์ด๋ฏธ์ง๋ฅผ ๋ด๋ถ(local) ๋์ปค ํธ์คํธ์ pullํ ์ ์๋ค- ์ด๋ ๊ฒ ๊ฐ์ ธ์จ ์ด๋ฏธ์ง๋ฅผ runํ๋ฉด ์ปจํ ์ด๋๊ฐ ๋จ
- public ์ ์ฅ์ : Docker Hub, QUAY
- private ์ ์ฅ์ : AWS ๋๋ Docker Registry ์ง์ ๋์์ ๋น๊ณต๊ฐ๋ก ์ฌ์ฉ
- ๋์ปค ์์ง์์ ์ฌ์ฉํ๋ ๊ธฐ๋ณธ๋จ์, ๋์ปค ์์ง์ ํต์ฌ
- ๋์ปค ์ด๋ฏธ์ง์ ์ปจํ
์ด๋๋
1:N๊ด๊ณ - ๋์ปค ์ด๋ฏธ์ง์ ์ปจํ ์ด๋์ ๊ด๊ณ๋ ์ด์์ฒด์ ์์์ ํ๋ก๊ทธ๋จ-ํ๋ก์ธ์ค, ๊ฐ์ฒด์งํฅ ํ๋ก๊ทธ๋๋ฐ์์์ ํด๋์ค-์ธ์คํด์ค ๊ด๊ณ
-
Docker File โ Docker Imagedocker build๋ช ๋ น์ด๋ก Docker File์ ํตํด Docker Image ์์ฑ
-
Docker Image โ Docker Container- Docker Image๋ฅผ
docker run์ผ๋ก ์คํ์์ผ Docker Container ์์ฑ
- Docker Image๋ฅผ
-
Docker Image
- ์ปจํ ์ด๋๋ฅผ ์์ฑํ ๋ ํ์ํ ์์
[์ ์ฅ์ ์ด๋ฆ]/[์ด๋ฏธ์ง ์ด๋ฆ]:[ํ๊ทธ]
์ ์ฅ์ ์ด๋ฆ: ์ด๋ฏธ์ง๊ฐ ์ ์ฅ๋ ์ฅ์, ์ ์ฅ์ ์ด๋ฆ์ด ๋ช ์๋์ง ์์ ์ด๋ฏธ์ง๋ ๋์ปค ํ๋ธ์ ๊ณต์ ์ด๋ฏธ์ง๋ฅผ ๋ฃํ๋ค์ด๋ฏธ์ง ์ด๋ฆ: ํด๋น ์ด๋ฏธ์ง๊ฐ ์ด๋ค ์ญํ ์ ํ๋์ง ๋ํ๋ด๊ณ ํ์๋ก ์ค์ ํด์ผ ํ๋ค- ex.
ubuntu:latest: ์ฐ๋ถํฌ ์ปจํ ์ด๋๋ฅผ ์์ฑํ๊ธฐ ์ํ ์ด๋ฏธ์ง
- ex.
ํ๊ทธ: ์ด๋ฏธ์ง์ ๋ฒ์ ์ ๋ํ๋ด๊ณ , ์๋ต ์ ๋์ปค ์์ง์latest๋ก ์ธ์
-
Docker Container
- ๋์ปค ์ด๋ฏธ์ง๋ก ์์ฑํ ์ ์๋ค
- ์ปจํ ์ด๋๋ฅผ ์์ฑํ๋ฉด ํด๋น ์ด๋ฏธ์ง์ ๋ชฉ์ ์ ๋ง๋ ํ์ผ์ด ๋ค์ด ์๋, ํธ์คํธ์ ๋ค๋ฅธ ์ปจํ ์ด๋๋ก๋ถํฐ ๊ฒฉ๋ฆฌ๋ ์์คํ ์์ ๋ฐ ๋คํธ์ํฌ๋ฅผ ์ฌ์ฉํ ์ ์๋ ๋ ๋ฆฝ๋ ๊ณต๊ฐ(ํ๋ก์ธ์ค)์ด ์์ฑ๋๋ค
- ๋๋ถ๋ถ์ ๋์ปค ์ปจํ ์ด๋๋ ์์ฑ๋ ๋ ์ฌ์ฉ๋ ๋์ปค ์ด๋ฏธ์ง์ ์ข ๋ฅ์ ๋ฐ๋ผ ์๋ง์ ์ค์ ๊ณผ ํ์ผ์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ ๋์ปค ์ด๋ฏธ์ง์ ๋ชฉ์ ์ ๋ง๋๋ก ์ฌ์ฉ๋๋ ๊ฒ์ด ์ผ๋ฐ์
- ์ปจํ ์ด๋๋ ์ด๋ฏธ์ง๋ฅผ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์ฌ์ฉํ๊ณ , ์ด๋ฏธ์ง์์ ๋ณ๊ฒฝ๋ ์ฌํญ๋ง ์ปจํ ์ด๋ ๊ณ์ธต์ ์ ์ฅํ๋ฏ๋ก ์ปจํ ์ด๋์์ ๋ฌด์์ ํ๋ ์ง ์๋ ์ด๋ฏธ์ง๋ ์ํฅ์ ๋ฐ์ง ์๋๋ค
- ์์ฑ๋ ๊ฐ ์ปจํ
์ด๋๋ ๊ฐ๊ธฐ ๋
๋ฆฝ๋ ํ์ผ์์คํ
์ ์ ๊ณต๋ฐ๊ณ ํธ์คํธ์ ๋ถ๋ฆฌ๋์ด ์์ด, ํน์ ์ปจํ
์ด๋์์ ์ด๋ค ์ดํ๋ฆฌ์ผ์ด์
์ ์ค์นํ๊ฑฐ๋ ์ญ์ ํด๋ ๋ค๋ฅธ ์ปจํ
์ด๋์ ํธ์คํธ๋ ๋ณํ๊ฐ ์๋ค
- ex. ๊ฐ์ ๋์ปค ์ด๋ฏธ์ง๋ก A, B ๋ ๊ฐ์ ์ปจํ ์ด๋๋ฅผ ์์ฑํ ๋ค์ A ์ปจํ ์ด๋๋ฅผ ์์ ํด๋ B ์ปจํ ์ด๋์๋ ์ํฅ์ ์ฃผ์ง ์๋๋ค
-
๋์ปค๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ ๋ฆฝ์ ์ธ ํ๊ฒฝ์์ ์คํ๋๊ธฐ ๋๋ฌธ์ ์ปจํ ์ด๋ ๋ฐ์์ ์ ๊ทผํ ์ ์๋ค
-
์ปจํ ์ด๋์ ํต์ ํ๊ธฐ ์ํด์๋ ์ปจํ ์ด๋๋ฅผ ๊ฐ๋์ํค๋ฉด์
-p์ต์ ์ ์ฌ์ฉํด ํธ์คํธ์ ํฌํธ์ ์ปจํ ์ด๋์ ํฌํธ๋ฅผ ์ค์ ํด์ผ ํ๋ค
-p ${host_port}:${container_port}- ์ด ์ค์ ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํธ์คํธ(์๋ฒ ๋๋ PC)์์ ์ฌ์ฉ ์ค์ธ ํฌํธ์ ๋ฒํธ๊ฐ ๊ฒน์น์ง ์๋์ง ํ์ธ์ด ํ์ํ๋ค
docker run --name test1 -d httpd
docker run --name test1 -d -p 8080:80 httpd--name test1: test1์ด๋ผ๋ ์ด๋ฆ์ผ๋ก ์ปจํ ์ด๋ ์์ฑ-d: ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ๋์-p 8080:80: ํธ์คํธ์ ํฌํธ๋ 8080, ์ปจํ ์ด๋์ ํฌํธ๋ 80์ผ๋ก ์ธํ ํด ๋คํธ์ํฌ ์ค์
docker ps -a
docker container ls -a- ๋์ผํ ๋ ๊ฐ์ ๋ช ๋ น์ด
-a์ต์ : ์์ผ๋ฉด ์คํ ์ค์ธ ์ปจํ ์ด๋๋ง ๋ณด์ฌ์ค- ๋ถ์ฌ์ฃผ๋ฉด ๋ค์ํ ์ํ์ ์ปจํ ์ด๋ ํ์ธ ๊ฐ๋ฅ
- ์์ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํด ์ปจํ ์ด๋์ ์ํ๋ฅผ ํ์ธํ ์ ์๋ค
docker stop test1
docker rm test1- ์ปจํ ์ด๋ ์คํ ์ค์ง ๋ฐ ์ญ์ ๋ช ๋ น์ด
- ๋์ปค ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๊ธฐ ์ํ ์คํฌ๋ฆฝํธ ํ์ผ
- ์ฌ๋ฌ ํค์๋๋ฅผ ์ฌ์ฉํด dockerfile์ ์์ฑํด ๋น๋๋ฅผ ๋ณด๋ค ์ฝ๊ฒ ์ํํ ์ ์๋ค
FROM: base๊ฐ ๋๋ image ์ง์ , ์ฃผ๋ก OS ์ด๋ฏธ์ง๋ ๋ฐํ์ ์ด๋ฏธ์ง๋ฅผ ์ง์ RUN: ์ด๋ฏธ์ง๋ฅผ ๋น๋ํ ๋ ์ฌ์ฉํ๋ ์ปค๋งจ๋๋ฅผ ์ค์ ํ ๋ ์ฌ์ฉADD: ์ด๋ฏธ์ง์ ํธ์คํธ์ ํ์ผ์ด๋ ํด๋๋ฅผ ์ถ๊ฐํ๊ธฐ ์ํด ์ฌ์ฉ- ๋ง์ฝ ์ด๋ฏธ์ง์ ๋ณต์ฌํ๋ ค๋ ๋๋ ํ ๋ฆฌ๊ฐ ์กด์ฌํ์ง ์์ผ๋ฉด docker๊ฐ ์๋์ผ๋ก ์์ฑ
COPY: ํธ์คํธ ํ๊ฒฝ์ ํ์ผ์ด๋ ํด๋๋ฅผ ์ด๋ฏธ์ง ์์ผ๋ก ๋ณต์ฌํ๊ธฐ ์ํด ์ฌ์ฉADD์ ๋์ผํ๊ฒ ๋์ํ์ง๋ง ๊ฐ์ฅ ํ์คํ ์ฐจ์ด์ ์ URL์ ์ง์ ํ๊ฑฐ๋ ์์ถํ์ผ์ ์๋์ผ๋ก ํ์ง ์์
EXPOSE: ์ด๋ฏธ์ง๊ฐ ํต์ ์ ์ฌ์ฉํ ํฌํธ๋ฅผ ์ง์ ํ ๋ ์ฌ์ฉENV: ํ๊ฒฝ ๋ณ์ ์ง์ ์ ์ฌ์ฉ$name,${name}์ ํํ๋ก ์ฌ์ฉ ๊ฐ๋ฅ${name:-else}: name์ด ์ ์๋์ด ์์ง ์๋ค๋ฉด else๊ฐ ์ฌ์ฉ๋จ
CMD: ๋์ปค ์ปจํ ์ด๋๊ฐ ์คํ๋ ๋ ์คํํ ์ปค๋งจ๋ ์ง์ RUN๊ณผ ๋น์ทํ์ง๋ง ๋์ปค ์ด๋ฏธ์ง๋ฅผ ๋น๋ํ ๋ ์คํ๋๋ ๊ฒ์ด ์๋๋ผ ์ปจํ ์ด๋๋ฅผ ์์ํ ๋ ์คํ๋๋ค๋ ๊ฒ์ด ๋ค๋ฅด๋ค
ENTRYPOINT: ๋์ปค ์ด๋ฏธ์ง๊ฐ ์คํ๋ ๋ ์ฌ์ฉ๋๋ ๊ธฐ๋ณธ ์ปค๋งจ๋ ์ง์ (๊ฐ์ )WORKDIR: RUN, CMD, ENTRYPOINT ๋ฑ์ ์ฌ์ฉํ ์ปค๋งจ๋๋ฅผ ์คํํ๋ ๋๋ ํ ๋ฆฌ ์ง์ -W์ต์ ์ผ๋ก ์ค๋ฒ๋ผ์ด๋ฉ ๊ฐ๋ฅ
VOLUME: ํผ์์คํด์ค ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ ๋ ์ฌ์ฉ- ํธ์คํธ์ ๋๋ ํ ๋ฆฌ๋ฅผ ๋์ปค ์ปจํ ์ด๋์ ์ฐ๊ฒฐ
- ์ฃผ๋ก ํ๋ฐ์ฑ์ผ๋ก ์ฌ์ฉ๋๋ฉด ์๋๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๋ ์ฌ์ฉ
docker build ${option} ${dockerfile directory}
docker build -t test1 . - dockerfile์ ์คํํ๊ธฐ ์ํ docker build ์ปค๋งจ๋
- ์ด๋ฏธ์ง์ ์ด๋ฆ test
- .์ผ๋ก ๋์ปค ํ์ผ์ ์์น
docker run --name test_app -p 80:80 test1- ์์ฑ๋ ์ด๋ฏธ์ง๋ฅผ ์ปจํ ์ด๋๋ก ์ฌ์ฉํ๊ธฐ ์ํจ
FROM openjdk:17-jdk-slim
#์ด Docker ์ด๋ฏธ์ง๋ OpenJDK 17๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํจ, Java 17์ ์ค์นํ๊ณ ์คํํ ์ ์๋ ํ๊ฒฝ ์ ๊ณต
ARG JAR_FILE=/build/libs/*.jar
#Docker ๋น๋ ์์ ์ ๋ฌ๋๋ ์ธ์(Argument)๋ก, ์ดํ๋ฆฌ์ผ์ด์
JAR ํ์ผ์ ๊ฒฝ๋ก๋ฅผ ์ง์
COPY ${JAR_FILE} app.jar
# ์์ ์ ์ํ JAR_FILE ๋ณ์๋ฅผ ์ด์ฉํด ๋น๋๋ JAR ํ์ผ์ Docker ์ด๋ฏธ์ง ๋ด๋ถ๋ก ๋ณต์ฌ
# ์ด๋, app.jar๋ก ํ์ผ์ ๋ณต์ฌํ๊ฒ ๋๋ค
ENTRYPOINT ["java","-jar", "/app.jar"]
#์ปจํ
์ด๋๊ฐ ์์๋ ๋ ์คํ๋๋ ๋ช
๋ น์ด ์ค์
#์ด ๊ฒฝ์ฐ, Java๋ก JAR ํ์ผ์ ์คํํ๋ ๋ช
๋ น์ด ์ง์ - jdbc ์์กด์ฑ ์ถ๊ฐ โ ์๋
implementation 'org.springframework.boot:spring-boot-starter-jdbc'-
mysql ๋น๋ฐ๋ฒํธ ๊ฐํ : ๋์๋ฌธ์, ์ซ์, ํน์๋ฌธ์ ์กฐํฉ โ ์๋
-
application.yml์์ spring datasource url ์ค์ ๋ณ๊ฒฝ โ ํด๊ฒฐ
application.yml์์host.docker.internal:3306์ผ๋ก ์ฐ๊ฒฐ
- ๋์ปค ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋น์ค, ๋คํธ์ํฌ, ๋ณผ๋ฅจ ๋ฑ์ ์ค์ ์ yml ํ์์ผ๋ก ์ ์ฅํ๋ ํ์ผ
- ์ฌ๋ฌ ์ปจํ ์ด๋๋ฅผ ์ ์ํ๋ ๋ฐ ์ฌ์ฉ๋๋ค
services:
frontend:
image: awesome/webapp
backend:
image: awesome/database- 'frontend'์ 'backend'๋ ๊ฐ
container๋ฅผ ์ ์ํ๊ณ , ๊ฐcontainer์ ์ด๋ฆ์ด ๋๋ค awesome/database๋ผ๋ ๋์ปคimage๋ฅผ ๊ฐ์ง๊ณcontainer๋ฅผ ๊ฐ๋ํ๊ฒ ๋๋ฉดcontainer์ ์ด๋ฆ์ด 'backend'๊ฐ ๋๋ค๋ ์๋ฏธ
image: ์ปจํ ์ด๋์ ์ด๋ฏธ์ง ์ ์build: ์ด๋ฏธ์ง๋ฅผ ํ์ฉํ๋ ๋ฐฉ์์ด ์๋ dockerfile์ ๊ฒฝ๋ก๋ฅผ ์ง์ ํด ๋น๋ํ์ฌ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ- ์ด๋ฏธ์ง๋ฅผ ์ด๋์ ๊ฐ์ ธ์ค๋ ๊ฒ ์๋๋ผ,
์ดbuild๋ฅผ ํตํด dockerfile์ ๊ฒฝ๋ก๋ฅผ ์ค์ ํด ์ง์ ๋น๋ํด์ ์ปจํ ์ด๋๋ฅผ ๋์ธ ๋ ์ฌ์ฉ๋๋ ๋ฐฉ๋ฒ
- ์ด๋ฏธ์ง๋ฅผ ์ด๋์ ๊ฐ์ ธ์ค๋ ๊ฒ ์๋๋ผ,
dockerfile: ๋น๋ํ dockerfile์ ์ด๋ฆ์ดDockerfile์ด ์๋ ๊ฒฝ์ฐ ์ด๋ฆ์ ์ง์ ํ๊ธฐ ์ํด ์ฌ์ฉports: ํธ์คํธ์ ์ปจํ ์ด๋์ ํฌํธ ๋ฐ์ธ๋ฉ ์ค์ ์ ์ฌ์ฉ๋จvolumes: ํธ์คํธ์ ์ง์ ๋ ๊ฒฝ๋ก๋ก ์ปจํ ์ด๋์ ๋ณผ๋ฅจ์ ๋ง์ดํธ ํ๋๋ก ์ค์ container_name: ์ปจํ ์ด๋ ์ด๋ฆ ์ค์ command: ์ปจํ ์ด๋๊ฐ ์คํ๋ ํ ์ปจํ ์ด๋ ์์์ ์คํ์ํฌ ์ ๋ช ๋ น์ดenvironment: ํ๊ฒฝ ๋ณ์ ์ค์ env_file:environment์ ๋์ผํ ๊ธฐ๋ฅ์ ์ํํ์ง๋ง, ์ด ํค์๋๋ฅผ ์ฌ์ฉํ๋ฉดenvํ์ผ์ ์ด์ฉํด ์ ์ฉํ ์ ์๋คdepends_on: ๋ค๋ฅธ ์ปจํ ์ด๋์ ์์กด๊ด๊ณ ์ค์ restart: ์ปจํ ์ด๋์ ์ฌ์์๊ณผ ๊ด๋ จํ ์ค์ - ์ด๋ค ์ค๋ฅ๋ก ์ธํด ์ด๋ฏธ์ง๊ฐ ์คํ์ด ์ ๋์ ๋ ๋ฉ์ถ ๊ฑด์ง ๋ค์ ์คํํ ๊ฑด์ง
docker-compose up- ํด๋น ๋ช
๋ น์ด๋ฅผ ์คํํ๋ ๊ฒฝ๋ก์์
docker-compose.ymlํ์ผ์ ์ฐพ์์ ์คํ
docker-compose -f docker-compose-custom.yml up-f์ต์ :docker-compose๋ ๊ธฐ๋ณธ์ ์ผ๋กdocker-compose.yml์ ์ด๋ฆ์ ์ฌ์ฉํ๋๋ฐ,
๋ง์ฝ ๋ค๋ฅธ ์ด๋ฆ์ผ๋ก ํ์ผ์ ๊ด๋ฆฌํ๊ณ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํด๋น ์ต์ ์ ์ด์ฉํ ์ ์๋ค
docker-compose up -d-d์ต์ : ๋ฐฑ๊ทธ๋ผ์ด๋์์docker-compose๋ฅผ ์คํํ๊ธฐ ์ํด ์ฌ์ฉ-d์ต์ ์์ด up ํ๋ฉด, ํ ์คํธ ๋๋ ๋๊น์ง ํด๋น ํฐ๋ฏธ๋์ ๋ ์ด์ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ๋ ์ต์
- Redis ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฑ์ ์ธ๋ถ ํ๊ฒฝ์ด ํ์ํ ๊ฒฝ์ฐ, ์ฆ, ์ธํ๋ผ ๊ตฌ์ถ ์
๋ก์ปฌ์ ์ค์นํ๊ธฐ ์ซ์ ๋ ๋์ปค ์ด๋ฏธ์ง๋ฅผ ์ด์ฉํด ์ปจํ ์ด๋๋ก ์ฐ๊ณ ๋ด๋ฆฌ๋ ์์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅ
version: "3"
services:
db:
container_name: dangn_db # ์ปจํ
์ด๋ ์ด๋ฆ ์ค์
image: mysql:8.0 # MySQL 8.0 ๋ฒ์ ์ด๋ฏธ์ง ์ฌ์ฉ
environment: # MySQL์ ์ ๋ฌํ๋ ํ๊ฒฝ ๋ณ์
MYSQL_ROOT_PASSWORD: mysql # ๋ฃจํธ ์ฌ์ฉ์ ๋น๋ฐ๋ฒํธ์
MYSQL_DATABASE: ceos_dangn # ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๋ฆ
volumes: # ํธ์คํธ ์์คํ
๊ณผ ์ปจํ
์ด๋ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๊ธฐ ์ํ ๋ณผ๋ฅจ ์ค์
- dbdata:/var/lib/mysql # MySQL ๋ฐ์ดํฐ ๋๋ ํ ๋ฆฌ๋ฅผ ํธ์คํธ ์์คํ
์ dbdata ๋ณผ๋ฅจ๊ณผ ์ฐ๊ฒฐ
ports: # ํธ์คํธ ์์คํ
๊ณผ ์ปจํ
์ด๋ ๊ฐ์ ํฌํธ ๋งคํ์ ์ค์
- 3307:3306 # ํธ์คํธ์ 3307 ํฌํธ๋ฅผ ์ปจํ
์ด๋ ๋ด์ 3306 ํฌํธ๋ก ๋งคํ
restart: always # ์ปจํ
์ด๋๊ฐ ์ข
๋ฃ๋ ๋ ํญ์ ๋ค์ ์์ํ๋๋ก ์ค์
web:
container_name: dangn_web # ์ปจํ
์ด๋ ์ด๋ฆ ์ค์
build: . # ํ์ฌ ๋๋ ํ ๋ฆฌ์์ Dockerfile์ ์ฌ์ฉํด ์ด๋ฏธ์ง ๋น๋
ports: # ํธ์คํธ ์์คํ
๊ณผ ์ปจํ
์ด๋ ๊ฐ์ ํฌํธ ๋งคํ ์ค์
- "8080:8080" # ์น ์ดํ๋ฆฌ์ผ์ด์
์ 8080 ํฌํธ๋ฅผ ํธ์คํธ์ 8080 ํฌํธ์ ์ฐ๊ฒฐ
depends_on: # ์์กดํ๋ ์๋น์ค ์ค์
- db # web ์๋น์ค๊ฐ ์์๋๊ธฐ ์ ์ db ์๋น์ค๊ฐ ๋จผ์ ์์๋๋๋ก ์ค์
environment: # ์ดํ๋ฆฌ์ผ์ด์
์์ ์ฌ์ฉํ ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์
mysql_host: db # MySQL ํธ์คํธ๋ฅผ db๋ก ์ค์
restart: always # ์ปจํ
์ด๋๊ฐ ์ข
๋ฃ๋ ๋ ํญ์ ๋ค์ ์์ํ๋๋ก ์ค์
volumes: # ํธ์คํธ ์์คํ
๊ณผ ์ปจํ
์ด๋ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๊ธฐ ์ํ ๋ณผ๋ฅจ ์ค์
- .:/app # ํ์ฌ ๋๋ ํ ๋ฆฌ๋ฅผ ํธ์คํธ์ /app ๋๋ ํ ๋ฆฌ์ ์ฐ๊ฒฐ
volumes:
app: # ํธ์คํธ ์์คํ
๊ณผ web ์ปจํ
์ด๋ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๊ธฐ ์ํ ๋ณผ๋ฅจ
dbdata: # ํธ์คํธ ์์คํ
๊ณผ db ์ปจํ
์ด๋ ๊ฐ์ MySQL ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๊ธฐ ์ํ ๋ณผ๋ฅจ| Containers | Images | Volumes |
|---|---|---|
GET:/api/v1/users/profile-getUserInfo()
- ์๋ฌ ์ฒ๋ฆฌ์ ํ์ฉ url ์์
POST:/api/v1/review/create-createReview()
- VPC๋ ๊ธฐ๋ณธ default ์ด์ฉํจ
-
SSH,HTTP,HTTPS,MYSQL์ ๋ํด IPv4์ IPv6 ๋ชจ๋ ์ค์ ํด์ค -
์ค์ ๋ ๋ณด์ ๊ทธ๋ฃน ์์ฑ ํด๋ฆญ
- ๋ค์๊ณผ ๊ฐ์ด ์ ํค ํ์ด ์์ฑํด์ค
- ์์ฑํด์ค ํค ํ์ด๋
C:\Users\yoonsseo\.ssh\ceos_dangn.pem๊ฒฝ๋ก์ ์ ์ฅํด ์ค
- ์์์ ๋ง๋ค์ด๋จ๋ ๋ณด์ ๊ทธ๋ฃน ์ฐ๊ฒฐ
- ์คํ ๋ฆฌ์ง ํฌ๊ธฐ๋ 30GB (ํ๋ฆฌํฐ์ด ๊ฐ๋ฅ ์ต๋ ์ฉ๋)๋ก ์ค์ ํด์ค
- EC2 ์์ฑ ํ์ธ
- ๋ง์คํฐ ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ์ํธ๋ ๋์ค์ DB ์ฐ๊ฒฐ ์ ์ฌ์ฉ
- ์ ํ ํ๋ฆฟ์์ ํ๋ฆฌํฐ์ด ์ ํํ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฅํ ์ต์ ์๋ฌด๊ฑฐ๋ ์ ํ
- ์คํ ๋ฆฌ์ง ์ฉ๋์ 20GB, ์คํ ๋ฆฌ์ง ์๋ ์กฐ์ ์ ๋นํ์ฑํ (์๋์น ์์ ๊ณผ๊ธ ๋ฐฉ์ง)
- ๋ฐ๋ก ์ค์ ํ์ง ์์
-
Workflow
- ์๋ํ๋ ์ ์ฒด ํ๋ก์ธ์ค๋ก, ํ๋ ์ด์์ Job์ผ๋ก ๊ตฌ์ฑ๋๊ณ , Event์ ์ํด ์์ฝ๋๊ฑฐ๋ ํธ๋ฆฌ๊ฑฐ๋ ์ ์๋ ์๋ํ๋ ์ ์ฐจ๋ฅผ ๋งํ๋ค
- Workflow ํ์ผ์ YAML์ผ๋ก ์์ฑ๋๊ณ , Github Repository์ .github/workflows ํด๋ ์๋์ ์ ์ฅ๋๋ค
- Github์๊ฒ YAML ํ์ผ๋ก ์ ์ํ ์๋ํ ๋์์ ์ ๋ฌํ๋ฉด, Github Actions๋ ํด๋น ํ์ผ์ ๊ธฐ๋ฐ์ผ๋ก ๊ทธ๋๋ก ์คํ์ํจ๋ค
-
Event
- Workflow๋ฅผ ํธ๋ฆฌ๊ฑฐ(์คํ)ํ๋ ํน์ ํ๋์ด๋ ๊ท์น
- ์๋ฅผ ๋ค์ด, ๋๊ตฐ๊ฐ๊ฐ ์ปค๋ฐ์ ๋ฆฌํฌ์งํ ๋ฆฌ์ ํธ์ํ๊ฑฐ๋ ํ ์์ฒญ์ด ์์ฑ ๋ ๋ GitHub์์ ํ๋์ด ์์๋ ์ ์๋ค
-
Job
- Job์ ์ฌ๋ฌ Step์ผ๋ก ๊ตฌ์ฑ๋๊ณ , ๋จ์ผ ๊ฐ์ ํ๊ฒฝ์์ ์คํ๋๋ค
- ๋ค๋ฅธ Job์ ์์กด ๊ด๊ณ๋ฅผ ๊ฐ์ง ์๋ ์๊ณ , ๋ ๋ฆฝ์ ์ผ๋ก ๋ณ๋ ฌ๋ก ์คํ๋ ์๋ ์๋ค
-
Step
- Job ์์์ ์์ฐจ์ ์ผ๋ก ์คํ๋๋ ํ๋ก์ธ์ค ๋จ์
- Step์์ ๋ช ๋ น์ ๋ด๋ฆฌ๊ฑฐ๋, Action์ ์คํํ ์ ์๋ค.
-
Action
- Job์ ๊ตฌ์ฑํ๊ธฐ ์ํ Step๋ค์ ์กฐํฉ์ผ๋ก ๊ตฌ์ฑ๋ ๋ ๋ฆฝ์ ์ธ ๋ช ๋ น
- Workflow์ ๊ฐ์ฅ ์์ ๋น๋ ๋จ์
- Workflow์์ Action์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ Action์ด Step์ ํฌํจํด์ผ ํ๋ค
- Action์ ๊ตฌ์ฑํ๊ธฐ ์ํด์ ๋ ํฌ์งํ ๋ฆฌ์ ์ํธ์์ฉํ๋ ์ปค์คํ ์ฝ๋๋ฅผ ๋ง๋ค ์๋ ์๋ค
- ์ฌ์ฉ์๊ฐ ์ง์ ์ปค์คํฐ๋ง์ด์งํ๊ฑฐ๋, ๋ง์ผํ๋ ์ด์ค์ ์๋ Action์ ๊ฐ์ ธ๋ค ์ฌ์ฉํ ์๋ ์๋ค
-
Runner
- Gitbub Action Runner ์ดํ๋ฆฌ์ผ์ด์ ์ด ์ค์น๋ ๋จธ์ ์ผ๋ก, Workflow๊ฐ ์คํ๋ ์ธ์คํด์ค
- ๊นํ ๋ ํฌ์งํ ๋ฆฌ์ ์ก์ ํญ์ ๋ ธ์ถ๋๋ Workflow์ ์ด๋ฆ์ผ๋ก ์ต์ ๋ํ ๊ฐ
name: Deploy Development Server- ์ด๋ค ์กฐ๊ฑด์ Workflow๋ฅผ ์๋์ผ๋ก Trigger ์ํฌ์ง Event ๋ช ์
- push(Branch or Tag), pull_request, schedule์ ์ฌ์ฉํ ์ ์๋ค
push์ด๋ฒคํธ๋ฅผ ๋ช ์ํ๋ฉด, ๋๊ตฐ๊ฐ๊ฐ ๊น ๋ ํฌ์งํ ๋ฆฌ์ ๋ณ๊ฒฝ์ฌํญ์ push ํ๋ ์์ ๋ง๋ค job์ด ์คํ๋๋ค
- ๋จ์ผ Event๋ฅผ ์ฌ์ฉํ ์๋ ์๊ณ , array๋ก ์์ฑํ ์๋ ์๋ค
on: push
# ๋๋
on: [pull_request, issues]## develop ๋ธ๋์น์ push๊ฐ ๋๋ฉด ์คํ๋ฉ๋๋ค
on:
push:
branches: [ "develop" ]- ํน์ ํ ๋ธ๋์น๋, tag, ๋๋ path์์๋ง ์คํ๋๋๋ก ํ ์๋ ์๊ณ ,
์๋ ์์์ ๊ฐ์ดpaths๋ก ํน์ ํจํด์ ์ค์ ํ์ฌ ํด๋น ํจํด์ ์ผ์นํ๋ ํ์ผ์ด ๋ณ๊ฒฝ๋์์ ๋ Workflow๊ฐ ์คํ๋๋๋ก ํ๊ณ ,
!paths๋paths-ignore๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌด์ํ ํจํด์ ์ค์ ํ ์๋ ์๋ค
on:
push:
branches: [ master, dev ]
pull_request:
branches: [ master ]
paths:
- "**.js"
paths-ignore:
- "doc/**"- ์ํฌ ํ๋ก์ฐ๊ฐ ๊น ๋ ํฌ์ ๋ํ ๊ถํ์ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํ๊ฒ ์ค์ ํ๋ค
permissions:
contents: readjobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
## ์ฌ๋ฌ๋ถ์ด ์ฌ์ฉํ๋ ๋ฒ์ ์ ์ฌ์ฉํ์ธ์
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
## gradle build
- name: Build with Gradle
run: ./gradlew bootJarbuild๋ผ๋job์ ์์ฑํ๊ณ , ๊ทธ ์๋์ 3๊ฐ์step์ด ์กด์ฌํ๋ ๊ตฌ์กฐruns-on: ์ด๋ ์ด์์ฒด์ ์์job์ ์คํํ ์ง ์ง์ uses: ์ด๋ค ์ก์ ์ ์ฌ์ฉํ ์ง ์ง์ - ์ด๋ฏธ ๋ง๋ค์ด์ง action(์ 3์๊ฐ ๋ง๋ action)์ ์ฌ์ฉํ ๋ ์ง์
actions/checkout@v3: ์ฐ๋ฆฌ์ branch๋ฅผ ํ์ฌ ๋น์ด์๋ ubuntu์ ๋ด๋ ค๋ฐ๋๋ก ํจactions/setup-java@v3: java ๋ค์ด๋ฐ๊ธฐ
run: bash์์ ์คํํ ๋ช ๋ น์ด๋ฅผ ์ ์chmod +x gradlew: gradlew ์คํํ ๊ถํ ๋ถ์ฌ./gradlew build: ํด๋น java ์ฝ๋ ๋น๋
## ์น ์ด๋ฏธ์ง ๋น๋ ๋ฐ ๋์ปคํ๋ธ์ push
- name: web docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t my-repo/my-web-image .
docker push my-repo/my-web-image
docker build -f dockerfile-nginx -t my-repo/my-nginx-image .
docker push my-repo/my-nginx-image
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
## ์ฌ๋ฌ๋ถ์ด ์ํ๋ ๊ฒฝ๋ก๋ก ์ด๋ํฉ๋๋ค.
cd /home/ubuntu/
## .env ํ์ผ์ ์์ฑํฉ๋๋ค.
sudo touch .env
echo "${{ secrets.ENV_VARS }}" | sudo tee .env > /dev/null
## docker-compose.yaml ํ์ผ์ ์์ฑํฉ๋๋ค.
sudo touch docker-compose.yaml
echo "${{ vars.DOCKER_COMPOSE }}" | sudo tee docker-compose.yaml > /dev/null
## docker-compose๋ฅผ ์คํํฉ๋๋ค.
sudo chmod 666 /var/run/docker.sock
sudo docker rm -f $(docker ps -qa)
sudo docker pull my-repo/my-web-image
sudo docker pull my-repo/my-nginx-image
docker-compose -f docker-compose.yaml --env-file ./.env up -d
docker image prune -f- ๋์ปค ๊ด๋ จ ์คํฌ๋ฆฝํธ
-
DOCKER_USERNAME: ๋์ปค ๊ณ์ ์ ์ ๋ค์ -
DOCKER_PASSWORD: ๋์ปค ๊ณ์ ๋น๋ฐ๋ฒํธ -
KEY: EC2๋ฅผ ์์ฑํ๋ฉฐ ๊ฐ์ด ์์ฑํ๋ .pem ํ์ผ์ ๋ด์ฉ
- ์ด ๋,
-----BEGIN๋ถํฐEND ... KEY-----๊น์ง ์ ๋ ฅํด์ฃผ์ด์ผ ํ๋ค-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAidvIJTS/UYMxf3G5fWC3tPkHiD35xttdsez++y2EO5vWKtpE wHcNCeHzwKiadand2VLDNnKi8/r+e3oPRrDCKQI8he5siDs6qyZuHOm2qd+jiQ+S ZeD ... 7Kzfn3eqHh+sMt4t9iX8 gdO2R6Z0TI3dfFpNKJU2WehZ7TZEA3qDJNqTg7008IJaUcuAEeWULtDwiwx/hkZ7 9kt5/TEA8jEoJw4gPakNlfEPEsQ2Sv7zpPPquZEGTqIjWXVMvPE0 -----END RSA PRIVATE KEY-----
ENV_VARS: ํ๊ฒฝ ๋ณ์๋ฅผ key-value๋ก ๋ด์๋๋ค
=์ ๊ธฐ์ค์ผ๋ก ์ข์ธก์ด key, ์ฐ์ธก์ด value
DB_URL=jdbc:mysql://ceos-dangn-rds.cp0xntend9ra.ap-northeast-2.rds.amazonaws.com:3306/ceos-dangn-rds
DB_USERNAME=root
DB_PASSWORD=blahblah- ์ ์ฅํด๋ ํ๊ฒฝ๋ณ์ ์ฌ์ฉํ๊ธฐ : application.yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 10DOCKER_COMPOSE: docker-compose.yaml ๋ฅผ ์์ฑํ ๋ ์ฐธ๊ณ ํ๋ ๋ณ์- ์์ secrets๊ณผ๋ ๋ค๋ฅด๊ฒ ๋ณ์๋ก ๋ฑ๋ก
- docker-compose ํ์ผ ์์ฑ ํ ๋ ํฌ์งํ ๋ฆฌ ๋ณ์๋ก ๋ฑ๋ก
FROM openjdk:17
ARG JAR_FILE=/build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar", "/app.jar"]FROM nginx
# ๊ธฐ๋ณธ Nginx ์ด๋ฏธ์ง ์ฌ์ฉ
RUN rm -rf /etc/nginx/conf.d/default.conf \
# ๊ธฐ๋ณธ Nginx ์ค์ ํ์ผ์ ์ญ์
COPY ./nginx/conf.d/nginx.conf /etc/nginx/conf.d
# ํธ์คํธ ๋จธ์ ์ ./nginx/conf.d/nginx.conf ํ์ผ์ ์ปจํ
์ด๋ ๋ด๋ถ์ /etc/nginx/conf.d ๊ฒฝ๋ก์ ๋ณต์ฌ
CMD ["nginx", "-g", "daemon off;"]
# ์ปจํ
์ด๋๊ฐ ์์๋ ๋ ์คํ๋ ๋ช
๋ น ์ ์- Nginx๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ Docker ์ด๋ฏธ์ง ์ ์ํ๋ ์คํฌ๋ฆฝํธ
deamon off: Nginx๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํ๋๋๋ก ์ค๊ณ๋์ด์๋๋ฐ,
Nginx๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋์ํ์ง ์๊ณ ํ๋ก์ธ์ค๋ฅผ foreground์์ ์คํํ๋๋ก ์ง์
version: '3'
services:
web:
container_name: dangn_web
image: my-repo/my-web-image
env_file:
- .env
expose:
- 8080
ports:
- 8080:8080
tty: true
environment:
- TZ=Asia/Seoul
nginx:
container_name: dangn_nginx
image: my-repo/my-nignx-image
ports:
- 80:80
depends_on:
- webserver {
listen 80;
# ์ด ์๋ฒ ๋ธ๋ก์ 80๋ฒ ํฌํธ์์ ๋ค์ด์ค๋ ์์ฒญ์ ์ฒ๋ฆฌ
server_name *.compute.amazonaws.com;
# ์ด ์๋ฒ ๋ธ๋ก์ *.compute.amazonaws.com ๋๋ฉ์ธ์ ๋ํ ์์ฒญ์ ์ฒ๋ฆฌ
access_log /var/log/nginx/access.log;
# ๊ฐ๊ฐ ์ ๊ทผ ๋ก๊ทธ์ ์ค๋ฅ ๋ก๊ทธ๋ฅผ ๊ธฐ๋กํ ํ์ผ ๊ฒฝ๋ก๋ฅผ ์ค์
error_log /var/log/nginx/error.log;
# ์ด ๋ธ๋ก์ ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํ ์์ฒญ์ ์ฒ๋ฆฌ
#
location / {
proxy_pass http://web:8080;
# proxy_pass ์ง์๋ฌธ์ ์ฌ์ฉํ์ฌ ์ด ์๋ฒ๊ฐ ๋ฐ์ ์์ฒญ์ http://web:8080 ์ฃผ์๋ก ์ ๋ฌ
# ์ฌ๊ธฐ์ web์ Docker ๋คํธ์ํฌ ์์์ ํด๋น ์๋น์ค์ ํ ๋น๋ ์ด๋ฆ
# ์๋น์ค๊ฐ 8080 ํฌํธ์์ ์คํ ์ค์ด๋ผ๊ณ ๊ฐ์
proxy_set_header Host $host:$server_port;
# proxy_set_header : ํ๋ก์ ์๋ฒ๋ก ์ ๋ฌ๋ ๋ ์ถ๊ฐ์ ์ธ HTTP ํค๋ ์ค์
# ํ๋ก์ ์๋ฒ๋ก ์ ๋ฌ๋๋ ์์ฒญ์ Host ํค๋ ์ค์
# ํ๋ก์ ์๋ฒ๋ ํด๋ผ์ด์ธํธ ์์ฒญ์ ๋ฐฑ์๋ ์๋ฒ๋ก ์ ๋ฌํ ๋ ์๋ ํธ์คํธ ์ ๋ณด๋ฅผ ์ ์งํ ์ ์๋ค
proxy_set_header X-Forwarded-Host $server_name;
# ํ๋ก์ ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฐ์ ์๋ ํธ์คํธ ์ฃผ์๋ฅผ ์ ๋ฌํ๋ ๋ฐ ์ฌ์ฉ๋๋ค
proxy_set_header X-Real-IP $remote_addr;
# ํด๋ผ์ด์ธํธ์ ์ค์ IP ์ฃผ์๋ฅผ ํฌํจํ๋ฉฐ, ํ๋ก์ ์๋ฒ๊ฐ ์ด ์ ๋ณด๋ฅผ ๋ฐฑ์๋ ์๋ฒ๋ก ์ ๋ฌํ ์ ์๋๋ก ํจ
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# ํด๋ผ์ด์ธํธ์์ ํ๋ก์๊น์ง์ ์ด์ ์์ฒญ์ IP ์ฃผ์๋ฅผ ํฌํจ
# ์ด๋ฅผ ํตํด ๋ฐฑ์๋ ์๋ฒ๋ ํด๋ผ์ด์ธํธ์ ์๋ IP ์ฃผ์๋ฅผ ์ ์ ์๋ค
}
}- reverse proxy ์ญํ ์ ํ๋ ๊ตฌ์ฑ
- ๊นํ๋ธ ์ก์
์์๋ ๋น๋ ์ฑ๊ณต์ผ๋ก ์ด๋ก๋ถ์ด ๋จ๋๋ฐ
docker psํ๋ฉด ์๋ฌด๊ฒ๋ ์ ๋ฌ๋ค
2. docker images๋ก ๋์ปค ์ด๋ฏธ์ง ํ์ธ
docker run -d -p 8080:8080 --name my_ceos_container yoonsseo/ceos18dangn-d์ต์ ์ด๋-p์ต์ ์ ์ด์ฉํด ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ์คํ ํ๊ณ 8080์ผ๋ก ๋งคํ
4. ์ด์
docker ps ํ๋ฉด ์ปจํ
์ด๋ ๋ชฉ๋ก ํ์ธํ ์ ์๋ค
5. ํฌ์คํธ๋งจ์ด๋ MySql์์ ํ์ธ
gradle.yml์ํฌํ๋ก์ฐ์ ์์์ ์๋์ผ๋ก ์ ๋ ฅํด์ฃผ์๋ ๋ค์ ๋ช ๋ น์ด ์ถ๊ฐ
docker run -d -p 8080:8080 --name ceos_container yoonsseo/ceos18dangn- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_PUBLIC_DNS }}
username: ubuntu
key: ${{ secrets.PEM_KEY }}
script: |
cd /home/ubuntu/
sudo touch docker-compose.yml
echo "${{ vars.DOCKER_COMPOSE }}" | sudo tee docker-compose.yml > /dev/null
sudo chmod 666 /var/run/docker.sock
sudo docker rm -f $(sudo docker ps -qa)
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/ceos18dangn
docker-compose -f docker-compose.yml up -d
docker run -d -p 8080:8080 --name ceos_container yoonsseo/ceos18dangn
docker image prune -f- ๊ฒฐ๊ณผ
- ๊ทผ๋ฐ ์ ์ถ๊ฐํ์ง ์์ผ๋ฉด ์ ๋๋ ๊ฑด์ง๋ ์ ์ ์์๋ค..๐ฅน๐คฏ๐ฑ๐ซ ๐ฅฒ๐ข๐ฅบ๐ซฃ













