NewsFeed๋ ์ฌ์ฉ์๊ฐ ์์ ์ผ์์ ๊ณต์ ํ ์ ์๋ ์์ ๋ฏธ๋์ด ํ๋ซํผ์ ๋๋ค. ํ์ ๊ฐ์ ํ ๊ฒ์๋ฌผ์ ์์ฑํ๊ณ , ํ๋ก์ฐ, ์ข์์, ๋๊ธ ๋ฐ ๋ฉ์ ์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ฌ์ง๊ณผ ์์น ์ ๋ณด๋ฅผ ํจ๊ป ๋ฑ๋กํ ์ ์์ต๋๋ค.
- Backend: Java, Spring Boot, JPA, QueryDSL, Socket.I/O
- Database: MySQL, Redis
- Map Service : KakaoMap API
- Authentication: JWT, Session, Kakao Login
- MVP 1: 2024/12/16 ~ 2024/12/31
- MVP 2: 2025/02/16 ~ 2024/02/31
- ํ์ ๊ฐ์ : PasswordEncoder๋ฅผ ํ์ฉํ ๋น๋ฐ๋ฒํธ ์ํธํ ๋ฐ JWT ๋ฐ๊ธ
- ํ์ ์ ๋ณด ์์ : ์์ ์ ๋น๋ฐ๋ฒํธ ํ์ธ ํ์
- ์นด์นด์ค ๋ก๊ทธ์ธ: OAuth๋ฅผ ์ด์ฉํ ํ์ ๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ ์๋ ์ฒ๋ฆฌ
- ํ์ ์ญ์ : Soft Delete ์ ์ฉ, ์ญ์ ์ ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ ํ์
- ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ: ๊ธฐ์กด ๋น๋ฐ๋ฒํธ์ ์ ๊ท ๋น๋ฐ๋ฒํธ๊ฐ ๋์ผํ์ง ์๋๋ก ์ ํ
- ํผ๋ ์์ฑ: ํ์ PK ๊ฐ ๊ฒ์ฆ ํ ํผ๋ ์์ฑ
- ์์น ๊ธฐ๋ฐ ํผ๋ ์์ฑ: ์์น ์ ๋ณด ๋ฑ๋ก ๊ฐ๋ฅ
- ํผ๋ ์กฐํ: ํ์ ์์ด๋ ๊ธฐ๋ฐ ํผ๋ ์กฐํ, ์ธ๊ธฐ ๊ฒ์๋ฌผ(์ข์์ ์) ์ ๋ ฌ
- ํผ๋ ์์ : ์์น, ์ฃผ์, ์ด๋ฏธ์ง, ์ ๋ชฉ, ๋ด์ฉ ์์ ๊ฐ๋ฅ
- ํผ๋ ์ญ์ : Soft Delete ์ ์ฉ
- ์น๊ตฌ ์ถ๊ฐ: ์น๊ตฌ ์์ฒญ ๋ฐ์ ์ ๊ฒ์ฆ(Session ์ฒดํฌ), ์ค๋ณต ์์ฒญ ๋ฐฉ์ง
- ์น๊ตฌ ์์ฒญ ์๋ฝ: ์์ฒญ ์๋ฝ ์ ACCEPTED ์ํ ๋ณ๊ฒฝ
- ๋ณด๋ธ ์์ฒญ ์กฐํ: ์์ฒญ ๋ณด๋ธ ์ฌ๋ ์ ๋ณด ํ์ธ
- ๋ฐ์ ์์ฒญ ์กฐํ: ์๋ฝ ์ ๋ฐ์ ์์ฒญ ํ์ธ ๊ฐ๋ฅ
- ์น๊ตฌ ๋ชฉ๋ก ์กฐํ
- ์น๊ตฌ ์ญ์
- ์ข์์ ์ถ๊ฐ: ํน์ ํผ๋์ ์ข์์ ๋๋ฅด๊ธฐ
- ์ข์์ ์ทจ์: ๊ธฐ์กด์ ๋๋ฅธ ์ข์์๋ง ์ทจ์ ๊ฐ๋ฅ
- ์ข์์ ์ ์กฐํ: ํน์ ํผ๋์ ์ข์์ ๊ฐ์ ํ์ธ
- ์ผ๋ฐ ๋ก๊ทธ์ธ: Email & Password ์ธ์ฆ ํ JWT ๋ฐ๊ธ
- ์นด์นด์ค ๋ก๊ทธ์ธ: OAuth2 ๋ก๊ทธ์ธ ๋ฐ JWT ์ฌ๋ฐ๊ธ
- JWT + Session ํผํฉ ์ฌ์ฉ: ๋ณด์ ๊ฐํ๋ฅผ ์ํด ์ธ์ ๊ณผ JWT๋ฅผ ์กฐํฉํ์ฌ ์ธ์ฆ ์ฒ๋ฆฌ
- Interceptor ์ ์ฉ: ์ธ์ฆ ๋ฐ ์ธ๊ฐ๋ฅผ ์ํ ์ธํฐ์ ํฐ ํ์ฉ
- ๋ฉ์์ง ์ ์ก ๋ฐ ์ ์ฅ: Redis๋ฅผ ํ์ฉํ ๋ฉ์์ง ์ ์ฅ ๋ฐ ์กฐํ
- WebSocket ๊ธฐ๋ฐ ์ค์๊ฐ ์ฑํ ๊ตฌํ
- Interceptor + JWT + Session ์กฐํฉ: MVP 1์์๋ Interceptor์ Session์ ์ฌ์ฉํ๊ณ , MVP 2์์ Spring Security๋ฅผ ๋์
- Refresh Token ๊ด๋ฆฌ: Redis๋ฅผ ํ์ฉํ ๋ธ๋๋ฆฌ์คํธ ์ ์ฉ
- ๋ก๊ทธ์์ ์ Token ์ญ์ : ํ์ ๋ก๊ทธ์์ ์ Redis์์ Refresh Token ์ ๊ฑฐ
- ๋ฌธ์ ์ : ์นด์นด์ค ๋ก๊ทธ์ธ ์ดํ ๋ฆฌ๋ค์ด๋ ํธ ์ ์ด๋ฏธ ์ฌ์ฉ๋ Authorization Code๋ก ์ธํด ์ธ์ฆ ์คํจ ๋ฐ์
- ํด๊ฒฐ ๋ฐฉ๋ฒ: code ์์ฒญ์ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋ฐ๋๊ฒ์ด ์๋, ์ง์ ์นด์นด์คํก ์๋ฒ์ ์์ฒญํ๋๋ก ๋ณ๊ฒฝ
- ๋๋๊ธ ๊ณ์ธต ๊ตฌ์กฐ ์ ์ง: ๋ถ๋ชจ-์์ ๊ด๊ณ๋ฅผ ์ ์งํ์ฌ JSON ์๋ต ๊ตฌ์กฐ ๊ฐ์
- Lazy Loading ๋ฌธ์ ํด๊ฒฐ: Fetch Join์ ํ์ฉํ์ฌ ๋๋๊ธ์ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์ค๋๋ก ์์
@Query("SELECT c FROM Comment c LEFT JOIN FETCH c.children WHERE c.feed.id = :feedId")
List<Comment> findByFeedId(@Param("feedId") Long feedId);
- Session ๊ฒ์ฆ ๋ก์ง ์ถ๊ฐ: ์น๊ตฌ ์์ฒญ ๋ฐ์ ์๊ฐ ๋ณธ์ธ์ด ๋ง๋์ง ํ์ธ
- ์ค๋ณต ์์ฒญ ๋ฐฉ์ง: ์ด๋ฏธ ์น๊ตฌ ๊ด๊ณ์ธ ๊ฒฝ์ฐ ์์ฒญ ๋ถ๊ฐ
if (!authenticatedMemberId.equals(friendRequestDto.getReceiverId())) {
throw new NoAuthorizedException(ErrorCode.NO_AUTHOR);
}
NewsFeed MVP 2์์๋ Spring Security๋ฅผ ๋์ ํ์ฌ ๊ธฐ์กด์ Interceptor ๋ฐ Session ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์ ๊ฐ์ ํ๊ณ , ๋ณด์์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๊ฐํํ๋ ๊ฒ์ ๋ชฉํ๋ก ํฉ๋๋ค.
- securityFilterChain: ์ธ์ฆ์ด ํ์ํ์ง ์์ ๋ถ๋ถ์ ์ ์ธํ๊ณ , ๋ก๊ทธ์ธ ํ ์ธ์ฆ์ด ํ์ํ ๊ธฐ๋ฅ์ ๋ช ํํ๊ฒ ๊ตฌ๋ถ
- roleHierarchy ์ค์ : ์ญํ ๊ณ์ธต์ ์ ์ํ์ฌ ๊ถํ ๊ด๋ฆฌ ์ฒด๊ณ ๊ฐํ
- Session ์ ๊ฑฐ ๋ฐ JWT ๊ธฐ๋ฐ ์ธ์ฆ ์ ์ฉ: ๊ธฐ์กด Session ๊ธฐ๋ฐ ์ธ์ฆ์ ์ ๊ฑฐํ๊ณ ,
AuthenticatedMemberUtil์ ํ์ฉํ์ฌ ์ธ์ฆ ์ ๋ณด ์ ๊ณต
- ๊ธฐ์กด ๋ฐฉ์:
Long memberId = AuthenticatedMemberUtil.getAuthenticatedMemberId();๋ฅผ ๊ฐ ์ปจํธ๋กค๋ฌ์์ ๋ฐ๋ณต์ ์ผ๋ก ํธ์ถ - ๊ฐ์ ๋ฐฉํฅ:
@Authenticationํ์ฉ: ์ธ์ฆ์ด ํ์ํ ์๋น์ค์ ์ปจํธ๋กค๋ฌ์์UserDetails๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์ง์ ๋ฐ์, ์ธ์ฆ๋Member์ ๋ณด๋ฅผ ์ถ์ถํ ์ ์๋๋ก ๋ณ๊ฒฝ.- ์ด๋ฅผ ํตํด, ๊ธฐ์กด์
AuthenticatedMemberUtil.getAuthenticatedMemberId();๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ํธ์ถํ๋ ๋ฐฉ์์ ๊ฐ์ ํ๊ณ , ์ธ์ฆ ๊ด๋ จ ๋ก์ง์Util๋ก ๋ถ๋ฆฌํ์ฌ ์ปจํธ๋กค๋ฌ์ ์ญํ ์ ๋จ์ํํจ.
โ
๋ณด์ ๊ฐํ: CORS, CSRF ๋ณดํธ ๋ฐ ์ธ์ฆ ์ธ๊ฐ ์ฒด๊ณ ๊ฐ์
โ
์ ์ง๋ณด์ ์ฉ์ด์ฑ: ๊ฐ๋ณ Filter ํน์ Interceptor๋ฅผ ๊ณ์ ์ถ๊ฐํ ๊ฒฝ์ฐ ๊ตฌ์กฐ๊ฐ ๋ณต์กํด์ง ๊ฐ๋ฅ์ฑ์ด ์์ โ Security๋ฅผ ๋์
ํ์ฌ ์ธ์ฆ ์ธ๊ฐ ๋ก์ง์ ์ค์์์ ๊ด๋ฆฌ
โ
๊ตฌํ ์ฉ์ด์ฑ: ๋ณด์ ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํํ๋ ๊ฒ๋ณด๋ค Security ํ๋ ์์ํฌ๋ฅผ ํ์ฉํ์ฌ ๋น ๋ฅด๊ณ ์์ ์ ์ธ ์ธ์ฆ ์ธ๊ฐ ์์คํ
๊ตฌ์ถ ๊ฐ๋ฅ
- WebSocket๋ง ์ฌ์ฉ์ Scale Out์ ํ์ฅ์ด ์ด๋ ค์
- Redis๋ฅผ ์ฌ์ฉํด Scale Out์ ์ด์ ํ๋ณด
๋ฌธ์ ์ : Security๋ฅผ ์ ํํ ํ์ฉํ์ง ๋ชปํ ์ํ์์ Session ๊ฐ์ ๊ฑท์ด๋ธ ๊ฒฐ๊ณผ, SecurityContextHolder๊ฐ ์๋ RequestAttribute๋ฅผ ํตํด ๊ฒ์ฆํ๋ ค๋ค ์ธ์ฆ ์คํจ ๋ฐ์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
@AuthenticationPrincipal์ ๊ฐ ์ปจํธ๋กค๋ฌ๋ง๋ค ์ ์ฉํ๋ ๋ฐฉ๋ฒ ๊ณ ๋ ค- ํ์ง๋ง ์ค๋ณต ์ฝ๋๊ฐ ๋ง์ Util ํด๋์ค๋ก ๋ณ๊ฒฝํ์ฌ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ๊ณต
๋ฌธ์ ์ : ๋ก๊ทธ์ธํ ์ฌ์ฉ์๊ฐ ์๋ ๊ฒฝ์ฐ์๋ ์น๊ตฌ ์์ฒญ ๋ฐ ์๋ฝ์ ํ ์ ์์์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
FriendService์์ ์์ฒญ์ ๊ฒ์ฆํ๋validate๋ก์ง ์ถ๊ฐ
if (!authenticatedMemberId.equals(friendRequestDto.getReceiverId())) {
throw new NoAuthorizedException(ErrorCode.NO_AUTHOR);
}
๋ฌธ์ ์ : ๊ธฐ์กด์๋ userId๋ฅผ ๊ธฐ์ค์ผ๋ก ์ธ์ฆ ๊ฒ์ฌ๋ฅผ ์งํํ์ง๋ง, Security์์๋ username์ ๊ธฐ๋ณธ ์๋ณ์๋ก ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ธ์ฆ ์คํจ ๋ฐ์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
UserDetailsService์์username์ ๋ฐํํ๋๋ก ์์ - ํ์ ์
userId๋ฅผ ์ปค์คํ ํ๋๋ก ์ถ๊ฐํ์ฌ ๊ด๋ฆฌ
๊ณ ๋ฏผ ๋ด์ฉ:
- Email ๊ธฐ๋ฐ ์ธ์ฆ: ์ฌ์ฉ์ ์นํ์ ์ด์ง๋ง ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ์ด ๋์ ์๋ณ์๋ก ์ฌ์ฉํ๊ธฐ ์ ๋งคํจ
- PK ๊ธฐ๋ฐ ์ธ์ฆ: ๋จ์ ์ซ์๋ก ๋ ธ์ถ ์ํ์ด ์์ง๋ง ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ์ด ๋ฎ์ ์ธ์ฆ ๋ฐ ์ฐ์ฐ ์ ์ฑ๋ฅ ๋ฉด์์ ์ ๋ฆฌํจ
์ ํํ ๋ฐฉ๋ฒ:
- AuthService์์๋ Email์ ์ฌ์ฉํ์ฌ ์ธ์ฆ ์งํ
- JWT ๋ด์์ PK ๊ฐ์ ํฌํจํ์ฌ ๋ด๋ถ์ ์ผ๋ก PK๋ฅผ ํ์ฉ (์ ์ผ์ฑ๊ณผ ์ฑ๋ฅ์ ๊ณ ๋ คํ ์ค๊ณ)
