html CSS 는 현재 피그마에맞게

아키텍처 원칙

로그인

User

DTO 분리 (목적별)

원칙: 왠만하면 매 요청/응답마다 DTO 분리. 공통 DTO 지양.

DTO 역할 사용 위치
SessionUserDTO 세션 저장용 사용자 정보 (id, username, nickname, email, roles) CustomUserAccount 내부, 세션
로그인 요청 DTO username, password (현재는 @RequestParam으로 처리) Security가 자동 처리
회원가입 요청 DTO username, password, nickname, email UserController (현재는 @RequestParam)

※ Security 적용 전에는 LoginUserDTO였으나, 목적이 “로그인”이 아니라 “세션 저장”이므로
SessionUserDTO로 명확히 분리. 이것이 “매 요청/응답마다 DTO 분리” 원칙.

Spring Security — 세션 방식 적용

개발자가 만드는 것 (2가지 + 설정)

| 파일 | 역할 | |——|——| | SecurityConfig | URL 접근 권한, 로그인/로그아웃 URL, PasswordEncoder Bean 등록 | | CustomUserAccount | UserDetails 구현 — SessionUserDTO 재사용 (composition) + password 추가 | | CustomUserDetailsService | UserDetailsService 구현 — username으로 DB 조회 후 UserDetails 반환 |

Entity → DTO 변환 원칙 준수:
CustomUserAccount는 필드를 직접 선언하지 않고 SessionUserDTO를 composition으로 갖는다.
CustomUserAccount.from(entity) → 내부에서 SessionUserDTO.from(entity) 재사용 + password 추가.
Controller에서 Entity 사용 금지 원칙 준수.

로그인 흐름 (Security가 자동 처리)

사용자가 username/password 입력 → /loginProc POST
  ↓ Security가 요청 가로챔 (UsernamePasswordAuthenticationFilter)
  ↓ CustomUserDetailsService.loadUserByUsername(username) 자동 호출
  ↓ DB에서 사용자 조회 → CustomUserAccount로 감싸서 반환
  ↓ Security가 BCrypt로 password 비교
  ↓ 성공 → Authentication 생성 → 세션에 저장 / 실패 → /login?error 리다이렉트

SecurityConfig URL 접근 규칙

permitAll()           → "/", "/login", "/signup"  (비로그인 허용)
hasAuthority("ADMIN") → "/admin"                  (ADMIN만)
hasAnyAuthority()     → "/mypage"                  (ADMIN 또는 USER)
anyRequest()          → permitAll()                (나머지 허용)

Controller에서 로그인 정보 사용

// @AuthenticationPrincipal로 CustomUserAccount 주입 → model에 담아서 HTML에서 사용
@GetMapping("/mypage")
public String mypage(@AuthenticationPrincipal CustomUserAccount userDetails, Model model) {
    model.addAttribute("user", userDetails);  // id, username, nickname, email, role 사용 가능
}

Thymeleaf에서 사용

<!-- 로그인/비로그인 분기 — sec 태그 사용 -->
<div sec:authorize="isAuthenticated()">로그인 상태</div>
<div sec:authorize="isAnonymous()">비로그인 상태</div>
<div sec:authorize="hasAuthority('ADMIN')">관리자 전용</div>

<!-- 사용자 정보 표시 — model에 담은 CustomUserAccount(user)를 일반 EL로 사용 -->
<span th:text="${user.nickname}">닉네임</span>     <!-- getNickname() -->
<span th:text="${user.username}">아이디</span>      <!-- getUsername() -->
<span th:text="${user.email}">이메일</span>         <!-- getEmail() -->
<span th:text="${user.roles}">USER</span>            <!-- getRoles() -->

sec:authentication="principal.nickname" 방식도 가능하지만,
model에 담아서 ${user.nickname}으로 쓰는 것이 기존 Thymeleaf와 같아서 직관적.
이 프로젝트에서는 model 방식을 사용한다.

UserEntity 변경사항

초기 데이터 (DataInitializer)

🗄️ JPA 설계

연관관계 원칙

CHAT (STOMP + SockJS 실시간 채팅)

기능 요약

의존성

implementation 'org.springframework.boot:spring-boot-starter-websocket'

패키지 구조

chat/
├── ChatRoomEntity.java          // 채팅방 Entity (DB 저장)
├── ChatRoomRepository.java      // 채팅방 Repository
├── ChatService.java             // 채팅방 비즈니스 로직
├── ChatController.java          // SSR 컨트롤러 (방 목록, 생성, 입장 페이지)
├── ChatMessageController.java   // STOMP 메시지 컨트롤러 (@MessageMapping)
└── dto/
    ├── ChatRoomCreateRequest.java   // 방 생성 요청 DTO (roomName)
    ├── ChatRoomResponse.java        // 방 응답 DTO (id, roomName, creatorNickname, createdAt)
    └── ChatMessageDTO.java          // STOMP 메시지 DTO (type, roomId, sender, message) — DB 저장 X

설정 클래스

| 파일 | 역할 | |——|——| | WebSocketConfig | STOMP 메시지 브로커 설정, SockJS 엔드포인트 등록 | | SecurityConfig | /chat/** authenticated, /ws-stomp/** permitAll 추가 |

WebSocketConfig 설정

엔드포인트: /ws-stomp (SockJS fallback)
구독 prefix: /sub     (서버 → 클라이언트, SimpleBroker)
발행 prefix: /pub     (클라이언트 → 서버, @MessageMapping)

ChatRoomEntity (DB 저장)

chat_rooms 테이블
├── id (Long, PK, AUTO_INCREMENT)
├── room_name (String, 채팅방 이름)
├── creator_nickname (String, 생성자 닉네임)
└── created_at (LocalDateTime, @PrePersist로 자동 세팅)

DTO 설계 (프로젝트 원칙 준수)

| DTO | 역할 | 변환 메서드 | |—–|——|————-| | ChatRoomCreateRequest | 방 생성 요청 (roomName) | toEntity(creatorNickname) | | ChatRoomResponse | 방 목록/상세 응답 | from(ChatRoomEntity) | | ChatMessageDTO | STOMP 실시간 메시지 (DB 저장 X) | 변환 불필요 (JSON 직렬화/역직렬화) |

ChatMessageDTO.MessageType:

Controller 분리

| Controller | 역할 | 매핑 | |————|——|——| | ChatController | SSR 페이지 렌더링 (Thymeleaf) | @RequestMapping("/chat") | | ChatMessageController | STOMP 메시지 수신/브로드캐스트 | @MessageMapping("/chat/message") |

ChatController (SSR):

GET  /chat/rooms          → 채팅방 목록 페이지 (rooms.html)
POST /chat/rooms          → 채팅방 생성 → redirect:/chat/rooms
GET  /chat/rooms/{roomId} → 채팅방 입장 페이지 (room.html)

ChatMessageController (STOMP):

/pub/chat/message 수신 → 타입별 메시지 가공 → /sub/chat/room/{roomId}로 브로드캐스트

보안 설정 (SecurityConfig)

.requestMatchers("/ws-stomp/**").permitAll()    // SockJS 핸드셰이크 허용
.requestMatchers("/chat/**").authenticated()     // 채팅 페이지는 로그인 필수

클라이언트 (room.html)

템플릿 구조

templates/
└── chat/
    ├── rooms.html    // 채팅방 목록 + 생성 폼
    └── room.html     // 채팅방 (SockJS/STOMP 연결, 메시지 송수신)