15 KiB
| phase | agent | agent_version | generated_at | concerns | concerns_checked | source_confidence | workers_spawned | ||
|---|---|---|---|---|---|---|---|---|---|
| research | research-advisor | 2 | 2026-06-18T03:18:28Z |
|
true | high | 3 |
조사 결과
주제
bibimbap(Java 21 / Spring Boot / JSP / MyBatis, WAR) 에 테스트용 더미 데이터를 seed 하기 위한 사전 조사. "로그인·리뷰 작성이 실제로 동작하는" 형태로 (1) 게임 (2) 로그인 가능 계정 (3) 더미 리뷰+댓글 을 만들기 위한 정확한 컬럼값·메커니즘 도출. 코드 수정 없음.
신뢰도 표기:
확인됨= 코드/실행으로 1차 확인.추정= 통용·유추.미확인= 검증 불가. 본 조사의 사실 항목은 전부 프로젝트 코드 직접 확인 기반 →source_confidence: high. 다만 schema.sql 자체가 "비권위 복원본"이라는 메타 한계는 concerns 에 보존(컬럼명은 매퍼로 교차확인됨, 제약·타입은 추론).
포인트별 발견
포인트 A: 로그인/인증 메커니즘 (실제 로그인 가능한 계정 seed)
- 경로:
src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java,data/UserAuthIdentityData.java,mapper/UserAuthIdentitiesMapper.java,mapper/UsersMapper.java,db/schema.sql
요약:
-
Spring Security 미사용.
PasswordEncoder|SecurityFilterChain|@EnableWebSecurity|spring-boot-starter-securitysrc/ + pom.xml 0 hit. 인증은 UserController 자체 구현 password 로그인. OAuth 없음(google/kakao/naver/oauth 0 hit). [확인됨] -
로그인 엔드포인트:
POST /login, form 파라미터email,password,remember(선택) — UserController.java:122, 125-128. [확인됨] 진입부에서CsrfTokens.isValid(request)실패 시 403 — UserController.java:131-133. [확인됨] -
provider 컬럼 값은
"email"단 하나.PROVIDER_EMAIL="email"— UserController.java:42. identity 생성 시 set — UserController.java:314. 조회 필터도 동일 값 — UserController.java:140-143. [확인됨]provider_user_id에는 정규화 이메일(trim+lowercase) 저장 — UserController.java:104-106, 527-533. [확인됨] -
password_hash 알고리즘 = PBKDF2 (BCrypt 아님). PasswordEncoder 빈 없음. 형식:
pbkdf2_sha256$<iterations>$<base64(salt)>$<base64(hash)>($4필드) — UserController.java:554-556. [확인됨]- iterations=210000 (UserController.java:46), keyLength=256bit (:47), salt=16byte SecureRandom (:48,551-552),
알고리즘
PBKDF2WithHmacSHA256via JCE SecretKeyFactory/PBEKeySpec (:576-584). [확인됨] - 검증:
verifyPassword()가$split → parts[0]=="pbkdf2_sha256" && length==4 확인 후 동일 iterations/salt 재계산,MessageDigest.isEqual상수시간 비교 — UserController.java:144, 559-574. [확인됨] - PBKDF2 표준 동작(iterations·salt 가 해시 문자열에 동봉되어 검증 시 재현 가능) — [추정/일반 통용 지식]
- iterations=210000 (UserController.java:46), keyLength=256bit (:47), salt=16byte SecureRandom (:48,551-552),
알고리즘
-
로그인 성공 시 세션 저장 (saveLoginSession — UserController.java:162, 502-525). 세션 직전
changeSessionId()세션고정방어 — :159-160. [확인됨] 개별 attribute:id(Long),userId(Long — 인증가드 sessionUserId() 가 읽는 키, :337),displayName,email(canonicalEmail),avatarUrl,role,status,authProvider("email"),authIdentityId,lastLoginAt— UserController.java:503-512. 추가로account키에 위 값들의 LinkedHashMap 통째 저장 — :514-524. (UserData 객체 자체가 아니라 개별 스칼라+Map) [확인됨] -
로그인 검증 컬럼 흐름 [확인됨]:
- user_auth_identities
WHERE provider='email' AND provider_user_id=<정규화이메일> AND is_delete IS NOT TRUE— UserAuthIdentitiesMapper.java:47-49 - password_hash != null && PBKDF2 통과 — UserController.java:144
- users
WHERE id=<identity.user_id> AND is_delete IS NOT TRUE— UsersMapper.java:25-26 - user.status == "ACTIVE" 필수 (STATUS_ACTIVE=:44) — UserController.java:148-149
- user_auth_identities
-
seed 시 필수 채움 컬럼 (schema.sql 비권위 — 컬럼명만 신뢰):
- users:
role(default 'USER'),status='ACTIVE'(ACTIVE 아니면 로그인 거부),is_delete=false. display_name/canonical_email 권장. — schema.sql:30-42 [확인됨, 제약값은 추정] - user_auth_identities:
user_id(FK),provider='email',provider_user_id=정규화이메일(소문자),password_hash=PBKDF2형식(null이면 즉시거부),is_delete=false. — schema.sql:49-63 [확인됨, 제약값은 추정] - 주의: active-unique idx
ux_user_auth_identities_provider_user_id_active=(provider, provider_user_id) WHERE is_delete IS NOT TRUE— schema.sql:66-68. 같은 이메일 활성 identity 중복 INSERT 실패. [확인됨, idx는 비권위 schema.sql]
- users:
-
신뢰도: 확인됨 (코드 직접 확인). schema.sql 유래 제약값만 추정.
포인트 B: 리뷰/댓글 작성 시 유저 귀속 + INSERT 컬럼 + CSRF
- 경로:
controller/api/GameReviewController.java,data/GameReviewData.java,mapper/GameReviewsMapper.java,controller/api/GameCommentController.java,data/GameCommentData.java,mapper/GameCommentsMapper.java,security/CsrfTokens.java,db/schema.sql,docs/game-reviews-ddl.sql
요약:
- 리뷰 API: GET
/game/{id}/reviews(목록, :41-42), GET/game/{id}/reviews/{reviewId}(:58-62), POST/game/{id}/reviews(작성, :74), PUT(수정, :124), DELETE(소프트삭제, :170) — GameReviewController.java. [확인됨] 작성 바디(@RequestParam, form):rating(String→1~5 파싱, 범위밖 400),body(최대 1000자) — :78-101, BODY_MAX=:30. [확인됨] - user_id 출처 = 세션 attribute
"userId"(요청 바디 아님).sessionUserId(session)→session.getAttribute("userId")— GameReviewController.java:86, 241-257(:245). 없으면 401.review.setUserId(userId)— :109. [확인됨] - game_reviews INSERT 컬럼 = 정확히 4개:
(game_id, user_id, rating, body)#{}바인딩 — GameReviewsMapper.java:72-86. [확인됨]- game_id ← PathVariable(:108), user_id ← 세션(:109), rating ← 1~5 정수(:110; DB CHECK BETWEEN 1 AND 5 — schema.sql:129, docs/game-reviews-ddl.sql:49-53), body ← trimToEmpty(빈입력시 ""; DB text nullable) — :98,275-278. [확인됨]
- 미포함(전부 DB DEFAULT): id(seq), created_at/updated_at(now()), is_delete(false), deleted_at(null) — schema.sql:124-127. [확인됨, 비권위 schema]
- 참고: updated_at > created_at 이면 조회 시 edited=true 표시 — GameReviewsMapper.java:24, GameReviewController.java:210. [확인됨]
- ux_game_reviews_game_user_active = 부분 유니크 인덱스
(game_id, user_id) WHERE is_delete IS NOT TRUE— schema.sql:132-133, docs/game-reviews-ddl.sql:58-61. [확인됨] 의미: 활성 리뷰는 (game,user) 조합당 1개. soft-delete 행은 제외 → 재작성 허용. 코드도 INSERT 전 중복 선검사 후 409 — GameReviewController.java:103-105, GameReviewsMapper.java:56-70. [확인됨] seed 주의: 한 게임에 더미 리뷰 여러 개 원하면 각 행 user_id 를 다르게. 한 user 는 게임당 활성 1개. [확인됨] - 댓글 API: GET
/game/{id}/comments(:39), POST(작성, :56), PUT(:102), DELETE(소프트삭제, :143). 작성 바디content만(최대 200자, CONTENT_MAX=:28) — GameCommentController.java:60,75-78. [확인됨]- user_id ← 세션
"userId"(:67,198). [확인됨] - nickname 비정규화 저장 확정: 작성 시점 세션
"displayName"을 nickname 컬럼에 복사 —comment.setNickname(authorName):84, sessionDisplayName=session.getAttribute("displayName") :212-218(:216). 조회 시 users JOIN 없이 nickname 그대로 노출(nickname AS authorName) — GameCommentsMapper.java:36, GameCommentController.java:179. [확인됨] - game_comments INSERT 컬럼 = 정확히 4개:
(game_id, user_id, nickname, content)— GameCommentsMapper.java:46-60. user_id nullable FK(schema.sql:112), nickname varchar(100) nullable(:102). [확인됨, 비권위 schema] - 유니크 제약 없음 → 같은 (game,user) 댓글 여러 개 가능. 화면 작성자명 보이려면 nickname 직접 채워야(조회가 users JOIN 안 함). 목록 정렬 created_at ASC, id ASC — GameCommentsMapper.java:42. [확인됨]
- user_id ← 세션
- CSRF: Spring Security 미사용 → 표준 CSRF 필터 없음. 대신 자체
CsrfTokens유틸을 컨트롤러 mutation 진입부에서 수동검증. [확인됨]- 리뷰 POST/PUT/DELETE: GameReviewController.java:83-85,134-136,178-180. 댓글: GameCommentController.java:64-66,111-113,151-153. GET 은 검증 없음. [확인됨]
- 토큰: 세션 key
"csrfToken", 헤더X-CSRF-Token(우선) 또는 폼 파라미터_csrf(폴백), 32byte SecureRandom Base64url — CsrfTokens.java:12-13,20-33,35-49. [확인됨] - 영향: HTTP API 경유 수동 작성은 로그인세션+csrfToken+헤더/파라미터 필요. DB 직접 INSERT seed 는 CSRF·세션·중복선검사 모두 우회(단 DB 유니크idx·CHECK·FK 는 그대로 적용). [확인됨]
- 신뢰도: 확인됨. schema.sql/ddl 유래 제약만 추정.
포인트 C: seed 적용 경로 + 게임 표시 조건
- 경로:
.env,docker-compose.yml,src/main/resources/dev/db.properties,GameController.java,GamesMapper.java,data/GameData.java,db/schema.sql,WEB-INF/views/game-detail.jsp
요약:
- DB 접속 정보 확정 (.env + docker-compose.yml + dev/db.properties 일관) [확인됨, 실행 확인]:
- DB명
bibimbap, 유저bibimbap, 패스워드change_me_local_dev, 호스트포트 5433 → 컨테이너 5432. - .env: POSTGRES_DB/USER=bibimbap, POSTGRES_PASSWORD=change_me_local_dev, APP_SCHEMA=dev, DB_PORT=5433
- docker-compose.yml:18
"${DB_PORT:-5432}:5432", :40jdbc:...:5432/${POSTGRES_DB}?currentSchema=${APP_SCHEMA:-dev} - dev/db.properties:2-4
jdbc:postgresql://localhost:5433/bibimbap?currentSchema=dev - 컨테이너
bibimbap-db postgres:16 Up (healthy) 0.0.0.0:5433->5432/tcp(docker compose ps 실행 확인) - 앱 스키마 =
dev. psql 접속:PGPASSWORD=change_me_local_dev psql -h localhost -p 5433 -U bibimbap -d bibimbap후SET search_path TO dev;(또는dev.games한정). - 현재 dev.games / dev.users 모두 0행 (read-only 조회 확인). live 스키마는 테이블 없음 → 앱 데이터는 전적으로 dev. [확인됨]
- DB명
- 게임 표시 조건 (목록과 상세가 다름; games-users INNER JOIN
JOIN users u ON u.id=g.user_id→ 유효 미삭제 user 필수) — GamesMapper.java:32-33. [확인됨]- 상세(getGame, GamesMapper.java:34-36):
g.id=#{id} AND g.is_delete IS NOT TRUE AND u.is_delete IS NOT TRUE. is_visible 조건 없음 → 비공개 게임도 id 직접 접근 시 상세 뜸. [확인됨] - 목록(getVisibleGames, GamesMapper.java:57-59):
g.is_visible IS NOT FALSE AND g.is_delete IS NOT TRUE AND u.is_delete IS NOT TRUE. [확인됨] - 목록+상세 모두 보이려면: games.is_visible=true, games.is_delete=false, 연결 users.is_delete=false. [확인됨]
- 컨트롤러: gameDetail 이 getGame(id)!=null 이면 DB 게임으로 game-detail 렌더 — GameController.java:113-121. [확인됨]
- 상세(getGame, GamesMapper.java:34-36):
- games INSERT 필수(NOT NULL, no-default) 컬럼 =
user_id(FK→users),name둘뿐 — schema.sql:75-87. [확인됨, 비권위 schema]- default 보유(생략가능): like_count(0), is_visible(true), sort_order(0), created_at/updated_at(now()), is_delete(false). nullable: creator_note, git_url, webgl_path, thumbnail_url. id=seq.
- 최소 INSERT
(user_id, name)만으로 가능 + 목록노출 조건 자동충족. 단 user_id FK 로 users 행 선행 필수.
- asset 컬럼 NULL 이어도 상세/리뷰 동작 — 깨지지 않음 [확인됨]:
- game-detail.jsp:12-21 webglFrameSrc null/blank/"null" 시 빈 문자열 폴백 → iframe src="" 빈화면(예외 없음). 컨트롤러가 trimToEmpty 정규화 후 모델 주입(GameController.java:273,281-283)이라 NPE 없음.
- thumbnail_url 은 상세 JSP 미사용. 리뷰/댓글은 별도 비동기 API(
/game/{id}/reviews,/comments)로 로드 → game asset 컬럼과 무관. - 결론: webgl_path/thumbnail_url 을 NULL 로 둬도 상세 + 리뷰/댓글 테스트 가능.
- 신뢰도: 확인됨 (코드 + 실행). schema.sql 유래 제약만 추정.
종합 판단 (더미 seed 실행 청사진)
세 포인트가 한 흐름으로 맞물린다. 현실적 seed 경로 = dev 스키마에 psql 직접 INSERT (앱 UI 경유는 CSRF+세션+WebGL 업로드 강제로 고비용; DB 직삽은 그 전부 우회하되 FK·CHECK·유니크idx 만 적용).
INSERT 순서(FK 의존성):
- users 1행: status='ACTIVE'(필수 — 아니면 로그인 거부), role='USER', is_delete=false, display_name 권장(목록 creator + 댓글 nickname 소스).
- user_auth_identities 1행: user_id=위 user, provider='email', provider_user_id=<정규화 소문자 이메일>, password_hash=PBKDF2 형식
pbkdf2_sha256$210000$<b64salt>$<b64hash>, is_delete=false. (활성 유니크 idx 주의) - games 1행: user_id=위 user, name=값. is_visible/is_delete 는 default 로 목록노출 충족. webgl_path/thumbnail_url NULL 가능.
- game_reviews N행: (game_id, user_id, rating 1~5, body). 한 게임당 user 1활성리뷰 → 여러 리뷰면 user 여러 개 seed.
- game_comments N행: (game_id, user_id, nickname[직접 채움], content). 유니크 없음 → 자유 다수.
로그인 가능 핵심(질문의 최우선 관심사): password_hash 는 BCrypt 가 아니라 PBKDF2WithHmacSHA256.
평문→해시는 외부 BCrypt 도구로 만들 수 없고, iterations=210000 / 256bit / 16byte salt / base64 스펙으로
pbkdf2_sha256$210000$<base64salt>$<base64hash> 문자열을 생성해 넣어야 검증 통과한다.
(해시 생성 스크립트 작성은 design/구현 단계 몫 — research 범위 밖.)
권위 격상 전 검증 필요 항목: db/schema.sql 이 비권위 복원본이므로, 위 컬럼 제약/타입/default 는 seed SQL 확정 직전
실 DB \d dev.users / \d dev.user_auth_identities / \d dev.games / \d dev.game_reviews / \d dev.game_comments 로 대조 권장.
(컬럼명·매퍼 일치·인덱스 존재는 코드/실행으로 확인됨 — 불일치 위험은 타입/제약값에 한정.)
미해결
- PBKDF2 seed 해시를 실제로 생성하는 도구/스크립트는 미작성(설계·구현 영역). 본 조사는 "어떤 형식이어야 검증 통과하는가"까지만.
- db/schema.sql 비권위로 인한 컬럼 제약/타입/default 의 운영 DB 대조는 미수행(읽기조회로 행수·테이블존재만 확인). seed SQL 확정 전
\d대조 필요. - game_likes 서버측 like_count 증감 경로(좋아요는 localStorage 클라이언트 토글로 보임) — 더미 리뷰/계정/게임 범위 밖이라 미조사.