Skip to content

Comments

[Authentication] 로그인 필터 및 핸들러 개발#49

Merged
codingbaraGo merged 10 commits intomainfrom
feat/authentication/#22
Jan 12, 2026
Merged

[Authentication] 로그인 필터 및 핸들러 개발#49
codingbaraGo merged 10 commits intomainfrom
feat/authentication/#22

Conversation

@codingbaraGo
Copy link
Owner

💻 작업 내용

  • User 엔티티 및 데이터베이스 구조 변경
  • 세션 개발
  • Authentication 정보를 담는 AuthenticateInfo 인터페이스 및 구현체 개발
  • 로그인 필터 개발
  • 로그인 핸들러 및 화면 개발

✨ 리뷰 포인트

로그인 포인트를 필터 체인단에 놓을까, 핸들러 계층에 놓을까 고민하다 핸들러 계층에 놓았습니다.
구조가 적절한지 피드백주세요.

🎯 관련 이슈

closed #22

- User의 컬럼 목록 변경
- Database의 User 저장 로직 변경(sequential key 추가)
- Redirect Renderer/WebResponse 추가
- 세션 엔티티 정의
- 세션 스토리지 개발
- AuthenticationInfo 인터페이스 정의
- 비로그인 유저용 구현체 UnanimousAuthentication 개발
- 로그인 유저용 구현체 UnanimousAuthentication 개발
- HttpRequest 클래스의 멤버에 AuthenticationInfo 추가
- 세션 정보 로딩 필터 개발
- 권한별 접근 제어 필터 개발
- 로그인 핸들러 개발
- 로그인 뷰 버튼 수정
@codingbaraGo codingbaraGo self-assigned this Jan 12, 2026
@codingbaraGo codingbaraGo added the feature New feature or request label Jan 12, 2026
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

종합 리뷰

PR 요청사항 답변: 구조 설계

로그인 포인트를 핸들러 계층에 배치한 것은 적절한 선택입니다.

로그인은 순수 비즈니스 로직(사용자 검증, 세션 생성)이기 때문에 handlers에서 처리하는 것이 관심사의 분리 원칙에 부합합니다. 반면 필터는 요청/응답의 횡단 관심사(authentication 정보 로드, authorization 확인)에만 집중하므로 현재 구조가 명확하고 유지보수하기 좋습니다.


핵심 이슈 (우선순위순)

1. 보안: 평문 비밀번호 저장 및 비교 (심각)

  • LoginWithPost.java의 직접 비교가 위험합니다. 데이터베이스 유출 시 모든 사용자 비밀번호 노출
  • 필수: bcrypt, PBKDF2 등으로 해싱 구현 필요

2. 시간 계산 오류 (높음)

  • VariableConfig의 30*60*100은 18초인데, 의도는 30분으로 보입니다
  • 1000을 곱해야 올바른 밀리초 단위가 됩니다

3. 세션 관리 메모리 누수 (중간)

  • 타임아웃된 세션만 삭제되고, 만료되지 않은 과거 세션은 영구 보관
  • Scheduled task로 정기적인 정리 필요

4. Null Pointer Exception 위험 (중간)

  • MemberAuthorizationFilter와 UnanimousAuthorizationFilter에서 authenticationInfo 체크 필수

5. 로그인 성능 이슈 (낮음)

  • Database.findUserByEmail()의 stream 기반 순회는 O(n) 시간복잡도
  • 사용자 수 증가 시 로그인 성능 저하

긍정적인 측면

✅ 필터-핸들러 분리가 명확하고 구조가 이해하기 쉬움
✅ ConcurrentHashMap 사용으로 thread-safe한 세션 관리
✅ Optional 사용으로 null 안전성 고려
✅ 인증/인가 필터 분리가 적절함

"./src/main/resources/static");

public static final long IDLE_MS = 30*60*100;
public static final long ABSOLUTE_MS = 180*60*100;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단위 오류: 30*60*100은 18,000ms(18초)이며, 180*60*100은 1,080,000ms(18분)입니다. 일반적으로 세션 타임아웃은 분 단위로 설정하는데, 계산식에서 1000을 곱해야 합니다.\n\n의도:\n- IDLE_MS: 30분 = 30*60*1000\n- ABSOLUTE_MS: 180분(3시간) = 180*60*1000"

Comment on lines +25 to +26
public static Optional<User> findUserByEmail(String email){
return users.values().stream().filter(u -> u.getEmail().equals(email)).findAny();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

성능 경고: stream().filter().findAny()는 매번 전체 컬렉션을 순회합니다. 사용자 수가 증가하면 로그인 성능이 저하됩니다.\n\n개선안: 사용자를 이메일로 검색하려면 별도의 Map을 유지하거나 데이터베이스 인덱싱을 사용하세요.\njava\nprivate static Map<String, User> usersByEmail = new ConcurrentHashMap<>();\n\npublic static Optional<User> findUserByEmail(String email) {\n return Optional.ofNullable(usersByEmail.get(email));\n}\n"

Comment on lines +35 to +37

if (!user.getPassword().equals(password)) {
throw new ServiceException(ErrorCode.LOGIN_FAILED);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

보안 위험: 평문 비밀번호를 데이터베이스에 저장하고 직접 비교하면 안 됩니다. 해킹으로 데이터베이스가 유출되면 모든 사용자의 비밀번호가 노출됩니다.\n\n필수 개선: 비밀번호를 해싱(예: bcrypt, PBKDF2)으로 저장하고, 로그인 시에는 입력 비밀번호를 동일한 해시 알고리즘으로 검증하세요."

Comment on lines +10 to +14
public User(String password, String nickname, String email, String userRole) {
this.password = password;
this.name = name;
this.nickname = nickname;
this.email = email;
this.userRole = userRole;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

설계 문제: User 엔티티의 userId가 null로 초기화되고 나중에 setUserId()로 설정됩니다. 생성 후 즉시 사용하면 런타임 오류가 발생할 수 있습니다.\n\n개선안: 생성자에서 userId를 받거나, 필수 필드를 필드 선언 단계에서 초기화하세요."

Comment on lines +12 to +18
if(request.getAuthenticationInfo().getRole().equals(UserRole.MEMBER)){
chain.doFilter();
} else {
response.setStatus(HttpStatus.FOUND);
response.setHeader("Location", "/login");
response.setHeader("Content-Length", "0");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null Pointer 위험: authenticationInfo가 null인 경우 NullPointerException이 발생합니다.\n\n개선안: null 체크를 추가하고, 권장되는 HTTP 상태 코드는 redirect(302)보다는 unauthorized(401) 또는 forbidden(403)이 더 적절합니다."

Comment on lines 11 to 17
public void runFilter(HttpRequest request, HttpResponse response, FilterChainContainer.FilterChainEngine chain) {
if(request.getAuthenticationInfo().getRole().equals(USER_ROLE)) {
chain.doFilter();
} else {
response.setStatus(HttpStatus.FOUND);
response.setHeader("Location", "/");
response.setHeader("Content-Length", "0");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null Pointer 위험: authenticationInfo가 null인 경우 NullPointerException이 발생합니다.\n\n개선안: null 체크를 추가하세요."

Comment on lines +28 to +32
if (now - s.getCreatedAt() > absoluteMs || now - s.getLastAccessAt() > idleMs) {
store.remove(sid);
return null;
}
s.touch(now);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메모리 누수 위험: 타임아웃된 세션을 명시적으로 삭제하지만, 만료되지 않은 세션이 요청되지 않으면 영구적으로 메모리에 남아있습니다(현재 remove() 호출이 getValid() 내부에만 있음).\n\n개선안: 별도의 background thread나 scheduled task를 통해 주기적으로 만료된 세션을 정리하세요."

Comment on lines +51 to 62
case PUBLIC -> List.of(
appConfig.authenticationFilter());
case AUTHENTICATED -> List.of(
appConfig.authenticationFilter(),
appConfig.memberAuthorizationFilter());
case RESTRICT -> List.of(
appConfig.restrictedFilter());
case LOG_IN -> List.of(
appConfig.authenticationFilter(),
appConfig.unanimousAuthorizationFilter());
};
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구조 설계 피드백 (PR 요청사항): 로그인 로직을 핸들러 계층에 배치한 것은 좋은 선택입니다. 이유:\n\n✅ 핸들러 계층 배치의 장점:\n- 로그인은 비즈니스 로직 (사용자 검증, 세션 생성)이므로 handlers에 속함\n- 필터는 요청/응답의 관심사 분리(authentication/authorization 처리)에만 집중\n- 필터-핸들러 분리가 명확하고 유지보수 용이\n\n✅ 구현 모델이 명확함: \n- AuthenticationFilter: 세션 정보 로드 및 사용자 정보 설정\n- MemberAuthorizationFilter/UnanimousAuthorizationFilter: 권한 확인\n- LoginWithPost: 실제 로그인 비즈니스 로직 처리\n\n다만 위의 보안 및 안정성 이슈(비밀번호 해싱, null 체크, 성능 등)를 개선하면 더 견고한 구조가 될 것입니다."

@codingbaraGo codingbaraGo merged commit a966b52 into main Jan 12, 2026
1 check passed
@codingbaraGo codingbaraGo deleted the feat/authentication/#22 branch January 12, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Authentication] - 인증 기능 개발

1 participant