Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
d0eac68
test: 1단계,2단계 추가
chaehyunL Jun 28, 2026
53bba1c
feat:LoginController 생성
chaehyunL Jun 28, 2026
24ee8ee
feat:LoginRequest 생성
chaehyunL Jun 28, 2026
54aee6d
feat: findById 메서드 추가
chaehyunL Jun 28, 2026
0365054
feat: LoginService 생성
chaehyunL Jun 28, 2026
143eb7d
feat: JwtProvider 생성
chaehyunL Jun 29, 2026
ecc8025
feat: LoginMember 생성
chaehyunL Jun 29, 2026
80053ba
feat: LoginMember 핸들러 생성
chaehyunL Jun 29, 2026
ab18aef
feat: LoginMember create 메서드에 추가
chaehyunL Jun 29, 2026
66728ba
feat: String name 이용
chaehyunL Jun 29, 2026
74f36bd
feat: loginMember 생성
chaehyunL Jun 29, 2026
f0ce4c0
style: ctrl + alt+ l 개행정리
chaehyunL Jun 29, 2026
44ad820
feat: WebConfig 생성
chaehyunL Jun 29, 2026
c5ae254
docs:README 파일 생성
chaehyunL Jun 29, 2026
05a06d3
feat: createToken 메서드 생성
chaehyunL Jun 30, 2026
7e0c71b
feat: token에 관한 책임 분리
chaehyunL Jun 30, 2026
95f915d
feat: resolveArgument 메서드 책임 분리
chaehyunL Jun 30, 2026
165b552
refector: checklogin 메서드 변경
chaehyunL Jun 30, 2026
40aff17
refector: http 관련 기능을 controller 로 이동
chaehyunL Jun 30, 2026
578d9a3
style: 개행정리
chaehyunL Jun 30, 2026
af5ff58
refector: 메서드 입력 객체 loginMember로 변경
chaehyunL Jul 1, 2026
b0692c5
test: 3단계 테스트 생성
chaehyunL Jul 3, 2026
d87a302
feat:resorce.properties 생성
chaehyunL Jul 4, 2026
4836e92
refector: secretKey 숨김
chaehyunL Jul 4, 2026
d808ed6
refector: extractToken 제거
chaehyunL Jul 4, 2026
4c1ffe7
feat:Login 핸들러 인터셉터 등록
chaehyunL Jul 4, 2026
f77ce23
feat:Login 핸들러 인터셉터 등록
chaehyunL Jul 4, 2026
0de02ba
refector: preHandle 내용 수정
chaehyunL Jul 4, 2026
2e26756
refector: 뭔래 있던 properties 중복되어서 삭제함
chaehyunL Jul 4, 2026
4730128
refector: jwt.secret 주석 처리 해제
chaehyunL Jul 4, 2026
0eae6c3
refector: jwt.secret->roomescape.auth.jwt.secret
chaehyunL Jul 4, 2026
653e960
refector:admin/** path 추가
chaehyunL Jul 4, 2026
8bb5385
style: 개행 정리
chaehyunL Jul 4, 2026
488f861
refector: Component 등록
chaehyunL Jul 4, 2026
aa2b5fa
feat: expiretime 구현
chaehyunL Jul 4, 2026
8895666
feat: TokenNotFoundException 생성
chaehyunL Jul 5, 2026
4184b79
refector: Exception 패키지 내로 이동
chaehyunL Jul 5, 2026
3bd4251
feat: Token 검증 로직 추가
chaehyunL Jul 5, 2026
ec8004e
feat: token 추출 방식 변경
chaehyunL Jul 6, 2026
8957601
feat: accessToken과 refreshToken으로 나눔
chaehyunL Jul 6, 2026
20a948c
feat: accessToken과 refreshToken으로 나눔
chaehyunL Jul 6, 2026
f948434
feat: accessToken이 정보를 받아오도록 변경
chaehyunL Jul 6, 2026
93465ad
feat: findById 메서드 구현
chaehyunL Jul 6, 2026
2fba555
feat: token->accessToken/refreshToken으로 구분
chaehyunL Jul 6, 2026
49b6d7f
docs:ReadMe 파일 작성
chaehyunL Jul 6, 2026
13b8925
refector: token 관련 수정
chaehyunL Jul 10, 2026
715960c
feat: cookieManager 생성
chaehyunL Jul 10, 2026
a272863
refector: cookieManager 이용해서 수정
chaehyunL Jul 10, 2026
cee6ff6
refector: 오류검증 로직 추가
chaehyunL Jul 10, 2026
9fda054
Merge branch 'chaehyunl' into mvc3
chaehyunL Jul 10, 2026
767742f
refector: gradle 의존성 대체
chaehyunL Jul 8, 2026
a3eebbe
feat:JPA 관련 설정 추가
chaehyunL Jul 8, 2026
262a651
feat: Entity 생성
chaehyunL Jul 8, 2026
c5cc7a3
feat: Entity 생성
chaehyunL Jul 8, 2026
6fff1f3
refector:JPA 방식으로 변경
chaehyunL Jul 8, 2026
4518389
refector: JPA 변경에 따른 수정
chaehyunL Jul 8, 2026
b9cc04b
refector: JPA 변경에 따른 수정
chaehyunL Jul 8, 2026
31d685e
refector: JPA 변경에 따른 수정
chaehyunL Jul 8, 2026
9c9ae75
refector: value->time
chaehyunL Jul 8, 2026
e3f65ef
refector: value->time
chaehyunL Jul 8, 2026
35ebc38
test: Jpa test와 기존 테스트 분리
chaehyunL Jul 8, 2026
0f3d928
findByEmail 메서드 추가
chaehyunL Jul 8, 2026
77abb17
refector:data.spl로 변경
chaehyunL Jul 8, 2026
cae9a52
refector:value->time_value
chaehyunL Jul 8, 2026
dd40b5a
refector: schema 삭제
chaehyunL Jul 8, 2026
07c4f55
refector: protected 추가
chaehyunL Jul 8, 2026
1ff8645
refector: time_value 수정
chaehyunL Jul 8, 2026
1373c71
refector: entityManager
chaehyunL Jul 15, 2026
3db99b7
style:ctrl_alt_l
chaehyunL Jul 15, 2026
80797fa
style:ctrl_alt_l
chaehyunL Jul 15, 2026
cd1a819
feat: entity매니저로 변경
chaehyunL Jul 15, 2026
74c0595
Merge branch 'chaehyunl' into JPA2
chaehyunL Jul 15, 2026
313e5ee
feat: entity매니저로 변경
chaehyunL Jul 15, 2026
fd3e903
docs:README 파일 생성
chaehyunL Jul 15, 2026
12e9014
feat: 필요 메서드 생성
chaehyunL Jul 15, 2026
7bb3fe5
feat: 필요 메서드 생성
chaehyunL Jul 15, 2026
72b8311
feat: 필요 메서드 생성
chaehyunL Jul 15, 2026
63b2eb1
refector:네이밍 변경
chaehyunL Jul 17, 2026
13f3d39
refector:네이밍 변경
chaehyunL Jul 17, 2026
5405303
refector:time_value->timeValue 이름 변경
chaehyunL Jul 17, 2026
6c6a2e9
refector:cookieManager 사용
chaehyunL Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0'

Expand Down
24 changes: 8 additions & 16 deletions src/main/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,18 @@


---
## Spring MVC 미션 3단계 - 관리자 기능
## Spring JPA 4-5단계

entity매니저를 사용해서 repository 수정
jpa 전환 및 내 예약 구성코드를 생성함.

어드민 페이지 진입은 admin 권한이 있는 사람만 할 수 있도록 제한한다.
만약 진입 권한이 없다면 401 코드를 응답하도록 한다.

---
### 고민한 내용 🤔

- HandlerInterceptor에서 기본적으로 preHandle/postHandle/afterCompletion으로 나뉘어져있던데,
이번의 경우에는 admin페이지 접속만을 확인하면 되는거라서 preHandle만을 사용했습니다.
이경우에는 postHandle과 afterCompletion을 어떻게 구성하면 좋을지..? 알고 싶네요.


- WebConfig 같은 경우에는, admin 페이지를 접속할때의 role만을 확인해주면 되므로 /admin/***을 사용해주었습니다.

- token 만료시간도 있어야할 것 같아서 expire_time을 이용해서,만료시간을 구현해보았습니다. 만료시간을 구현할 더 좋은
방법이 있을지?? 궁금합니다.

- token을 accessToken과 refreshToken으로 나눠서 구현해주었는데, 구현하다보니 너무 어려워서 제대로 구현된건지 잘
모르겠습니다.

- jpa를 구성하는 과정에서 @entity를 사용하는 과정에서 많은 부분을
entity 객체가 대신해주니까 그 부분이 이해가 살짝 안되고 어려웠습니다.
- 그리고 entity 매니저를 사용하게 된다면, jpa와 달리 어떠한 부분이 더 좋다고
느낀지 그 부분이 궁금해요!


4 changes: 2 additions & 2 deletions src/main/java/roomescape/CookieManager.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package roomescape;

import jakarta.servlet.http.Cookie;
import org.springframework.stereotype.Component;
import roomescape.exception.TokenNotFoundException;

import java.util.Arrays;

@Component
public class CookieManager {


public String extractToken(Cookie[] cookies, String cookieName) {
return Arrays.stream(cookies)
.filter(cookie -> cookieName.equals(cookie.getName()))
Expand Down
25 changes: 17 additions & 8 deletions src/main/java/roomescape/login/LoginInterceptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,41 @@
import roomescape.CookieManager;
import roomescape.JwtProvider;
import roomescape.member.Member;
import roomescape.member.MemberDao;
import roomescape.member.MemberRepository;

@Component
public class LoginInterceptor implements HandlerInterceptor {

private final JwtProvider jwtProvider;
private final MemberDao memberDao;
private final MemberRepository memberRepository;
private final CookieManager cookieManager;

public LoginInterceptor(JwtProvider jwtProvider, MemberDao memberDao, CookieManager cookieManager) {
public LoginInterceptor(JwtProvider jwtProvider, MemberRepository memberRepository, CookieManager cookieManager) {
this.jwtProvider = jwtProvider;
this.memberDao = memberDao;
this.memberRepository = memberRepository;
this.cookieManager = cookieManager;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Cookie[] cookies = request.getCookies();
Cookie[] cookies = getCookies(request);

String token = cookieManager.extractToken(cookies, "accessToken");
String token = cookieManager.extractToken(cookies,"accessToken");
Long memberId = jwtProvider.getMemberId(token);
Member member = memberDao.findById(memberId);
if (member == null || !member.getRole().equals("ADMIN")) {
Member member = memberRepository.findById(memberId)
.orElseThrow();
if (!member.getRole().equals("ADMIN")) {
response.setStatus(401);
return false;
}
return true;
}

private Cookie[] getCookies(HttpServletRequest httpServletRequest) {
Cookie[] cookies = httpServletRequest.getCookies();
if (cookies == null) {
throw new IllegalStateException("cookie is not exist");
}
return cookies;
}
}
12 changes: 6 additions & 6 deletions src/main/java/roomescape/login/LoginMemberArgumentResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
import roomescape.CookieManager;
import roomescape.JwtProvider;
import roomescape.member.Member;
import roomescape.member.MemberDao;
import roomescape.member.MemberRepository;

@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
private final MemberDao memberDao;
private final MemberRepository memberRepository;
private final JwtProvider jwtProvider;
private final CookieManager cookieManager;
public LoginMemberArgumentResolver(JwtProvider jwtProvider, MemberDao memberDao,CookieManager cookieManager) {
this.memberDao = memberDao;
public LoginMemberArgumentResolver(JwtProvider jwtProvider, MemberRepository memberRepository,CookieManager cookieManager) {
this.memberRepository = memberRepository;
this.jwtProvider = jwtProvider;
this.cookieManager=cookieManager;
}
Expand All @@ -38,7 +38,8 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m

Long memberId = jwtProvider.getMemberId(token);

Member member = memberDao.findById(memberId);
Member member = memberRepository.findById(memberId)
.orElseThrow();

return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole());
}
Expand All @@ -58,5 +59,4 @@ private Cookie[] getCookies(HttpServletRequest httpServletRequest) {
}
return cookies;
}

}
24 changes: 11 additions & 13 deletions src/main/java/roomescape/login/LoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,34 @@

import org.springframework.stereotype.Service;
import roomescape.member.Member;
import roomescape.member.MemberDao;
import roomescape.member.MemberRepository;
import roomescape.member.MemberResponse;

@Service
public class LoginService {
private final MemberDao memberDao;
private final MemberRepository memberRepository;

public LoginService(MemberDao memberDao) {
this.memberDao = memberDao;
public LoginService(MemberRepository memberDao) {
this.memberRepository = memberDao;
}

public Member login(String email, String password) {
Member member = memberDao.findByEmailAndPassword(email, password);
if (member == null) {
throw new RuntimeException("Invalid email or password");
}
Member member = memberRepository.findByEmailAndPassword(email, password)
.orElseThrow();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

orElseThrow()에 예외 메시지가 없네요

  1. 없으면 생기는 문제점은 뭐가 있을까요?

수정하게 된다면 예외 메세지가 없는 모든 코드에서 수정이 이루어지길 바래요~!

@chaehyunL chaehyunL Jul 17, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

제가 코드를 짤때는 member를 optional로 받도록 되어있습니다. 즉 null값이 존재할 수 있습니다. 그런데 이걸 orElseThrow가 없게된다면, null값일때 대처할 수 없게됩니다.
만약 제가 optional로 받지 않았더라면 orElseThrow를 없애는게 맞다고 생각합니다.
하지만, 제가 생각했을때, member를 전달하는 과정에서 오류가 생기면 그저 Member member를 썼을때는 대처할 수 없게된다고 생각되어서 optional를 사용해서 오류검증 orElseThrow가 있는 방식이 조금더 적합된다고 생각됩니다

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional로 처리해주신 부분 좋은 설계라고 생각해요!
findBy~라는 네이밍 자체가 "반환값이 있을 수도, 없을 수도 있다"는 걸 말해주니까요~

제가 말씀 드리고 싶은 부부능ㄴ orElseThrow() 괄호 안이 비어 있다는 것이었어요!
아래 코드와 같이 커스텀 예외나 메시지를 넣어줄 수도 있지 않을까 싶더라구요

.orElseThrow(() -> new MemberNotFoundException("적절한 멤버를 찾지 못했습니다"))

지금처럼 인자 없이 orElseThrow()만 쓰면 후자가 주는 이점들을 놓치게 되지 않을까 싶은데
채현님은 어떻게 생각하시나요?
그리고 그 생각을 답변에 최대한 상세하게 작성해주세요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

단순히 optional을 사용했을때, .orElseThrow()를 쓰면 오류가 나지 않는다는 면에서 orElseThrow를 사용하였습니다.

만약, orElseThrow를 이용해서 새로운 커스텀 에러 메세지를 넣는다면, 오류가 나는 지점을 메세지를 받음으로써 더 정확하고 쉽게 파악할 수 있다고 생각합니다!

커스텀 예외를 넣는 것이 더 좋다고 생각되네요!!


return member;
}

public MemberResponse checkLogin(Long memberId) {

Member member = memberDao.findById(memberId);
if (member == null) {
throw new RuntimeException("Invalid email or password");
}
Member member = memberRepository.findById(memberId)
.orElseThrow();
return new MemberResponse(member.getId(), member.getName(), member.getEmail());
}

public Member findById(Long id) {
Member member = memberDao.findById(id);
Member member = memberRepository.findById(id)
.orElseThrow();

return member;
}
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/roomescape/member/Member.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package roomescape.member;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String password;
private String role;

public Member(Long id, String name, String email, String role) {
this.id = id;
this.name = name;
this.email = email;
this.role = role;
}
protected Member(){

}
public Member(String name, String email, String password, String role) {
this.name = name;
this.email = email;
Expand Down
68 changes: 0 additions & 68 deletions src/main/java/roomescape/member/MemberDao.java

This file was deleted.

44 changes: 44 additions & 0 deletions src/main/java/roomescape/member/MemberRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package roomescape.member;

import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public class MemberRepository {
private final EntityManager entityManager;

public MemberRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}

public Optional<Member> findById(Long id) {
return Optional.ofNullable(entityManager.find(Member.class, id));
}

public void save(Member member) {
entityManager.persist(member);
}

public List<Member> findAll() {
return entityManager.createQuery(
"SELECT m FROM Member m",
Member.class
).getResultList();
}

public Optional<Member> findByEmailAndPassword(String email, String password) {
List<Member> members = entityManager.createQuery(
"SELECT m FROM Member m WHERE m.email= :email AND m.password =:password",
Member.class
).setParameter("email", email)
.setParameter("password", password)
.getResultList();

return members.stream().findFirst();
}


}
15 changes: 11 additions & 4 deletions src/main/java/roomescape/member/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@

@Service
public class MemberService {
private MemberDao memberDao;
private final MemberRepository memberRepository;

public MemberService(MemberDao memberDao) {
this.memberDao = memberDao;
public MemberService(MemberRepository memberDao) {
this.memberRepository = memberDao;
}

public MemberResponse createMember(MemberRequest memberRequest) {
Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER"));
Member member =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

POST /members를 호출해 보셨나요? 회원가입이 이루어지지 않습니다. 이유는 어떤 것일까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Dao를 제가 여전히 호출하고 있어서 그런 것 같습니다..!
Repository와 연결되어 있어야하는데, 이러한 부분에서 연결이 끊겨서 생기는 문제인 것 같습니다

. 다음 리뷰할때 그부분 참고해서 변경하도록 하겠습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

한 번 호출해서 예외를 확인해보시면 좋을 것 같아요~!

  1. 어떤 예외 메시지가 발생하나요?
  2. Dao 호출 때문에 회원가입이 이루어지지 않는다고 판단하신 근거가 궁금해요!
  3. 수정 후 해결 방법도 함께 작성해주세요~~

이 부분은 다음 리뷰어분께 확인을 요청드리겠습니다!
cc.@c0mpuTurtle

new Member(
memberRequest.getName(),
memberRequest.getEmail(),
memberRequest.getPassword(),
"USER");

memberRepository.save(member);
return new MemberResponse(member.getId(), member.getName(), member.getEmail());
}
}
13 changes: 13 additions & 0 deletions src/main/java/roomescape/reservation/Reservation.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
package roomescape.reservation;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import roomescape.theme.Theme;
import roomescape.time.Time;

@Entity
public class Reservation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String date;
@ManyToOne
@JoinColumn(name ="time_id")
private Time time;
@ManyToOne
@JoinColumn(name ="theme_id")
private Theme theme;

public Reservation(Long id, String name, String date, Time time, Theme theme) {
Expand Down
Loading