Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Spring Core] (배포) 정상희 미션 제출합니다. #116

Open
wants to merge 40 commits into
base: sangheejeong
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
eac6692
[FEAT] 리드미 작성
SANGHEEJEONG Dec 24, 2024
e1b4ce3
[FEAT] 로그인, 인증 정보 조회 (1단계)
SANGHEEJEONG Dec 25, 2024
0186fcf
[FEAT] 리드미 작성 (2단계)
SANGHEEJEONG Dec 26, 2024
769b6bf
[FEAT] ArgumentResolver 구현 + 예약 리팩터링 (2단계)
SANGHEEJEONG Dec 26, 2024
9b180fb
[FEAT] 리드미 작성 (3단계)
SANGHEEJEONG Dec 26, 2024
2ebef37
[FEAT] 어드민 페이지 권한 (3단계)
SANGHEEJEONG Dec 26, 2024
f9c1a7d
[REFACTOR] 클래스명 변경 및 코드 정리
SANGHEEJEONG Dec 26, 2024
c83b84b
[FEAT] 리드미 작성 (4단계)
SANGHEEJEONG Jan 7, 2025
36b7dd3
[FEAT] Dao -> Repository (4단계)
SANGHEEJEONG Jan 8, 2025
9cfe2f7
[FEAT] 리드미 작성 (5단계)
SANGHEEJEONG Jan 8, 2025
112420c
[FEAT] 내 예약 목록 조회 (5단계)
SANGHEEJEONG Jan 9, 2025
2b497bf
[FEAT] 중복 예약 방지 및 예약 대기 기능 (6단계)
SANGHEEJEONG Jan 9, 2025
2e0884e
[REFACTOR] AuthClaims 와 LoginResponse 분리
SANGHEEJEONG Jan 13, 2025
0cfc7eb
[REFACTOR] 토큰 추출 메서드 중복 제거
SANGHEEJEONG Jan 13, 2025
ff3ad6a
[REFACTOR] Role enum 타입으로 변경
SANGHEEJEONG Jan 13, 2025
75132bc
[REFACTOR] 예외처리 핸들러 작성
SANGHEEJEONG Jan 13, 2025
fde3d1d
[REFACTOR] 커스텀 어노테이션 적용
SANGHEEJEONG Jan 13, 2025
1f2e385
[REBASE] mvc 리베이스 후 충돌 해결
SANGHEEJEONG Jan 15, 2025
1d62e9a
[REBASE] mvc 리베이스 충돌 해결 2
SANGHEEJEONG Jan 15, 2025
d5e2ad0
[FEAT] 리드미 작성 (5단계)
SANGHEEJEONG Jan 8, 2025
ae765ec
[REBASE] mvc 리베이스 충돌 해결 3
SANGHEEJEONG Jan 15, 2025
71cd274
[REBASE] mvc 리베이스 충돌 해결 4
SANGHEEJEONG Jan 15, 2025
9bcfcbf
[REFACTOR] service 웹 기술 제거
SANGHEEJEONG Jan 15, 2025
ec05e2c
[REFACTOR] DTO 변환 로직 분리
SANGHEEJEONG Jan 15, 2025
4b0e950
[REFACTOR] jpa를 위한 생성자 오용 방지
SANGHEEJEONG Jan 15, 2025
d47ce84
[REFACTOR] name -> id 조회로 변경 및 권한 추가
SANGHEEJEONG Jan 15, 2025
43f3c71
[REFACTOR] joinColumn 추가
SANGHEEJEONG Jan 15, 2025
b8b073f
[REFACTOR] joinColumn 추가 (Waiting 엔티티)
SANGHEEJEONG Jan 15, 2025
ebfb5d9
[REFACTOR] WaitingService 조회 수정
SANGHEEJEONG Jan 15, 2025
a89b961
[REFACTOR] 코드 정렬
SANGHEEJEONG Jan 15, 2025
0b1819c
Merge branch 'sanghee-jpa' of https://github.com/SANGHEEJEONG/spring-…
SANGHEEJEONG Jan 15, 2025
64d65be
[FEAT] 리드미 작성
SANGHEEJEONG Jan 15, 2025
4841c67
[FEAT&FIX] auth 패키지 외부 이동 및 오류 수정
SANGHEEJEONG Jan 15, 2025
43cb26c
[FIX] id 추출 및 에러 코드 오류 수정
SANGHEEJEONG Jan 15, 2025
b6fadf3
[FIX] 예약 & 대기 생성 로직 수정
SANGHEEJEONG Jan 15, 2025
9e3b738
[FIX] @AuthCustomAnnotation 어노테이션 추가
SANGHEEJEONG Jan 15, 2025
14a1bc0
[FEAT] 데이터 로더 클래스 생성
SANGHEEJEONG Jan 15, 2025
3b804b4
[REFACTOR] 안 쓰는 코드 제거
SANGHEEJEONG Jan 26, 2025
001cd99
[REFACTOR] jwt 관련 로직 분리
SANGHEEJEONG Jan 26, 2025
3f3af8a
[FIX] 이단계 테스트 업데이트
SANGHEEJEONG Jan 26, 2025
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
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 🌀 Spring MVC (인증)

# 1단계
___
## 로그인 페이지
+ 이메일, 비밀번호 입력
## 로그인 요청
+ 이메일, 비밀번호 -> 멤버 조회
+ 조회한 멤버로 토큰 발급
+ Cookie를 만들어 응답
## 인증 정보 조회
+ Cookie -> 토큰 정보 추출
+ 멤버를 찾아서 응답
## API
+ GET/login : 로그인 페이지 호출
+ POST/login : 로그인 요청
+ GET/login/check : 인증 정보 조회

# 2단계
___
## 로그인 리팩터링
#### HandlerMethodArgumentResolver
+ 컨트롤러 메서드 파라미터로 자동 주입

## 예약 생성 기능 변경
+ 예약 : ReservationRequest(요청 DTO)
-> name이 있으면 name으로 Member 찾기
-> name이 없으면 Cookie에 담긴 정보 활용

# 3단계
___
## 관리자 기능
+ admin 페이지 진입 (HandlerInterceptor 이용)
-> 관리자 : 진입 가능
-> 관리자 X : 401 코드 응답

# 🌀 Spring JPA (인증)

# 4단계
___
## JPA 전환
+ 엔티티 & 연관 관계 매핑
+ DAO -> JpaRepository를 상속받는 Repository로 대체

# 5단계
___
## 내 예약 목록 조회
+ 응답 DTO -> 예약 아이디, 테마, 날짜, 시간, 상태를 포함
## 예약 테이블 수정
+ 관리자 예약 (어드민 화면) : name을 string으로 전달
+ 사용자 예약 (예약 화면) : 로그인 정보를 이용해 Member ID 저장
## API
+ GET/reservation-mine : reservation-mine 페이지 응답
+ GET/reservations-mine : 내 예약 목록 조회

# 6단계
___
## 예약 대기
+ 요청
+ 취소
+ 조회 : N번 째 예약 대기인지 표시
## API
+ POST/waitings : 예약 대기 생성
+ DELETE/waitings/{id} : 예약 삭제

# 7단계
___
## @Configuration
+ JWT 관련 로직 auth 패키지(roomescape 외부 패키지)로 분리

# 8단계
___
## Profile과 Resource
### 1. schema.sql 대신 데이터베이스 초기화 클래스 만들기
#### < DataLoader >
+ DataLoader : 사용자 정보 초기화
+ TestDataLoader : 테스트에 필요한 사전 값 초기화

### 2. secret key 값을 properties 파일로 이동
8 changes: 7 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ 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-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

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

implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-gson:0.11.2'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/auth/AuthClaims.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package auth;
Copy link

Choose a reason for hiding this comment

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

Auth 라고 이름 붙은 클래스를 모두 auth 패키지로 옮겨주신 것 같아요.

이건 아마 7단계 중 아래의 요구사항 때문이겠죠?!

JWT 관련 로직을 roomescape와 같은 계층의 auth 패키지의 클래스로 분리하세요.

여기서 두 가지 정도의 리뷰 포인트가 존재할 것 같아요.

1. JWT 관련 로직은 어디까지를 관련된 로직으로 봐야 하는가?

상희님도 이에 대해 고민을 해보셨을 것 같아요. 그리고 이에 대한 결론으로, JWTUtils 와 Auth 가 붙은 모든 클래스를 그 대상으로 보신 것 같습니다.

다만 저는 생각이 조금 달라요. 아래는 요구사항 해결 방향성에서 가져온 문장인데요!

JWT 관련 로직으로는 토큰 생성과 토큰 값 조회 기능이 있습니다.

Auth 가 붙은 클래스들에는, 토큰 생성과 토큰 값 조회에 대한 기능을 포함하고 있지 않는 것 같아요. 이는 모두 JWTUtils 라는 클래스의 책임으로 보여요.

즉, JWT 관련 로직이라는 범위에는 Auth~ 클래스가 포함되지 않을 것 같다는 생각이에요.

예를 들어, 상희님의 코드에서는 AuthClaimsArgumentResolverJWTUtils를 의존하고 있는데, 이는 JWTUtils의 기능을 이용하여 AuthClaims 객체 조립을 도울 뿐, JWTUtils에 관련된 로직이라고 보긴 어렵지 않을까 하는 생각입니다.

2. 왜 auth 패키지로 분리해야 하는가? (= 미션의 의도가 무엇인가?)

이번 리뷰가 좀 어려웠는데, 이 포인트 때문이었던 것 같아요. 이 이야기는 상희님이 PR 본문에 남겨주신 아래의 질문들에 도움이 될 것 같네요.

굳이 왜 @component를 사용하지 않고 @configuration을 사용하는 게 요구사항인지 궁금했습니다. 약간 후자를 사용하면 싱글톤이 보장된다고 하는데 인증 과정을 왜 싱글톤으로 하는지에 대한 의문이 좀 들었습니다.

auth 패키지를 roomescape와 동등한 패키지로 만들라는 것이 @configuration 을 통해 빈을 등록하는 연습을 위한 건지 아니면 auth라는 인증 패키지는 원래 대부분 패키지를 기본 패키지와 동등한 계층으로 만들어 사용하는지 궁금합니다. 만약 후자라면 이유도 궁금합니다.

아래는 7단계 요구사항 테스트 코드인데요,

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public class MissionStepTest {
    @Test
    void 칠단계() {
        Component componentAnnotation = JwtUtils.class.getAnnotation(Component.class);
        assertThat(componentAnnotation).isNull();
    }
}

상희님이 만드신 JWTUtils 클래스에 @Component 어노테이션이 없어야 한다는 의도를 담은 테스트 코드예요.

이 테스트의 의도와, 7단계 미션의 맥락을 봤을 때, JWTUtils 및 auth 패키지를 스프링으로부터 완벽히 격리되어 있는 하나의 모듈로 바라보는 걸 원하지 않았을까? 라는 생각이 듭니다.

이렇게 생각해 봅시다. 상희님은 당장 JWT 관련 로직이 필요한데, 직접 구현하기에는 난이도가 너무 높다고 가정해 볼게요.

그렇다면 대안은, 이미 잘 만들어져있는 JWT 관련 라이브러리를 외부에서 끌어오는 방법이 있을 것 같아요. 이때 이 라이브러리의 패키지 명은 auth 이고, 클래스는 JWTUtils 라는 이름을 가진 거죠.

문제는, JWT 라이브러리를 만든 개발자가 스프링 기술을 전혀 고려하지 않고, 범용적으로 사용할 수 있는 POJO 형태의 코드를 작성했다는 점입니다.

즉, JWTUtils 클래스에는 @Component 와 같은 코드가 전혀 작성이 되어있지 않을 거예요. 스프링 기술을 전혀 고려하지 않았으니까요. 이 클래스가 스프링 빈으로 사용될 건지, 순수 자바 객체로 사용될 건지는, 이 라이브러리를 가져다 쓰는 개발자의 몫으로 남겨둔 거죠.

그러면 이제 다시 우리의 상황으로 돌아와서 생각해 봅시다!

우리는 스프링 기술을 사용하고 있고, 당장 JWTUtils 클래스를 스프링 빈으로 등록해서 사용해야 하는 상황입니다.

문제는, JWTUtils 클래스가 외부 라이브러리 코드이기 때문에, 우리가 수정할 수가 없다는 점이에요. 즉, @Component 어노테이션을 붙이고 싶어도 방법이 없어요.

이럴 때 어떻게 해볼 수 있을까요?

외부 모듈 코드라서, 해당 코드의 제어권이 우리에게 없을 때, @Configuration 을 통해서 수동으로 빈 등록을 할 수 있어요. 이렇게 하면 외부 모듈 코드더라도, 우리가 사용하고 있는 스프링 세계로 끌어들일 수 있겠죠.

또 다른 관점에서 볼 수도 있는데, 만약 상희님이 도메인 설계를 하고 있는데, 스프링 이라는 특정 기술에 종속적인 코드를 작성하고 싶지 않다고 가정해볼게요. 즉, 그 도메인 클래스의 import문에는 스프링 기술이 존재하면 안되는 상황입니다.

그렇게 도메인 코드를 POJO 형식으로 잘 작성했는데, 개발을 하다보니 그 도메인 객체를 스프링 빈으로 등록해서 조금 더 간편하게 사용하고 싶을 때가 있을 거예요.

이럴 때 사용해볼 수 있는게 @Configuration 이기도 합니다. 상희님이 만든 도메인 객체를 Configuration 클래스에서 스프링 빈으로 수동 등록하는 거죠. 이렇게 되면, 도메인 코드에서는 스프링 기술 종속성을 제거할 수 있으면서도, 동시에 스프링 빈 객체로서 사용할 수 있습니다.

그래서 이 미션을 진행하실 때 상희님이 가져야 할 관점을 정리해보자면,

auth 패키지 및 JWTUtils 클래스를 스프링 기술과 별개로 존재하는 외부의 모듈(라이브러리)로서 바라보는 것 일 거 같아요.

상희님이 JWTUtils 라는 코드를 외부에서 끌고 왔다고 가정하고, 코드를 다시 작성해보시면, 왜 @Configuration 을 사용해야 하는지 감이 오실 것 같아요.

Copy link

Choose a reason for hiding this comment

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

어떻게 이런 리뷰를 달 수 있죠.. 코코닥의 리뷰로 하나 배우고 갑니다 🏃🏃‍♂️

Copy link
Author

@SANGHEEJEONG SANGHEEJEONG Jan 26, 2025

Choose a reason for hiding this comment

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

JWT 관련 로직으로는 토큰 생성과 토큰 값 조회 기능이 있습니다

JWT 관련 로직이 JWT에 직접적인 연관성을 가지는 클래스만 포함하는 것이 이 요구사항의 의도였군요!
저는 JWT를 활용하여 인증하는 흐름까지 포함하는 건 줄 알고 다 같이 옮겨버렸던 것 같습니다..!
리뷰어님 말씀대로 토큰 생성과 조회에 대한 책임은 JWTUtils가 가진다고 생각합니다.


리뷰어님의 리뷰를 보고 미션의 의도를 정리해봤는데요,

  • JWTUtils를 스프링에 의존하지 않는 POJO 형태의 유틸리티 클래스로 바라보는 것을 요구
  • 우리가 직접 수정할 수 없는 외부 라이브러리처럼 취급하여, @configuration을 통해 빈으로 수동 등록하는 방식을 연습하려는 의도

이렇게 정리할 수 있을 것 같습니다.

도메인 코드에서는 스프링 기술 종속성을 제거할 수 있으면서도, 동시에 스프링 빈 객체로서 사용할 수 있습니다.

  • 도메인 클레스에 스프링 프레임워크의 어노테이션이나 인터페이스 등을 포함하지 않는 POJO
  • 코드는 스프링과 무관하지만 애플리케이션에서는 스프링 컨테이너가 객체를 관리하도록 설정

미션의 의도대로 했을 때 얻는 이점

  • 도메인 코드가 프레임 워크 변경의 영향을 덜 받음으로써 코드의 적용이 스프링에만 국한되지 않음
  • 인증 방식이 변경되더라도 코드 수정 없이 모듈 자체만 교체하면 됨

리뷰어님 덕분에 미션에서 왜 이런 요구사항을 제시했는지 얼추 이해가 된 것 같아요~ 감사합니다.
혹시 조금이라도 틀린 내용이 있거나 잘못 이해한 부분이 있다면 알려주세요!


public record AuthClaims(
Long id,
String name,
String role
) {
}
32 changes: 32 additions & 0 deletions src/main/java/auth/AuthClaimsArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package auth;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

// @Component
@RequiredArgsConstructor
public class AuthClaimsArgumentResolver implements HandlerMethodArgumentResolver {

private final JWTUtils jwtUtils;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(AuthCustomAnnotation.class) &&
parameter.getParameterType().equals(AuthClaims.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = ((ServletWebRequest) webRequest).getRequest();
String token = jwtUtils.extractToken(request).token();

return jwtUtils.getClaimsFromToken(token);
}
}

23 changes: 23 additions & 0 deletions src/main/java/auth/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package auth;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AuthConfig {

@Bean
public JWTUtils jwtUtils() {
return new JWTUtils();
}

@Bean
public AuthClaimsArgumentResolver authClaimsArgumentResolver(JWTUtils jwtUtils) {
return new AuthClaimsArgumentResolver(jwtUtils);
}

@Bean
public AuthRoleInterceptor authRoleInterceptor(JWTUtils jwtUtils) {
return new AuthRoleInterceptor(jwtUtils);
}
}
13 changes: 13 additions & 0 deletions src/main/java/auth/AuthCustomAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package auth;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthCustomAnnotation {
}
25 changes: 25 additions & 0 deletions src/main/java/auth/AuthRoleInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package auth;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.servlet.HandlerInterceptor;

// @Component
@RequiredArgsConstructor
public class AuthRoleInterceptor implements HandlerInterceptor {

private final JWTUtils jwtUtils;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = jwtUtils.extractToken(request).token();

if (!jwtUtils.getClaimsFromToken(token).role().equals("ADMIN")) {
response.setStatus(401);
return false;
}

return true;
}
}
6 changes: 6 additions & 0 deletions src/main/java/auth/AuthToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package auth;

public record AuthToken(
String token
) {
}
27 changes: 27 additions & 0 deletions src/main/java/auth/AuthWebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package auth;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
@RequiredArgsConstructor
public class AuthWebConfig implements WebMvcConfigurer {

private final AuthClaimsArgumentResolver authClaimsArgumentResolver;
private final AuthRoleInterceptor authRoleInterceptor;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authClaimsArgumentResolver);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authRoleInterceptor).addPathPatterns("/admin/**");
}
}
69 changes: 69 additions & 0 deletions src/main/java/auth/JWTUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import roomescape.exception.InvalidTokenException;
import roomescape.exception.MissingTokenException;
import roomescape.member.Member;

import java.util.Arrays;

// @Component
Copy link

Choose a reason for hiding this comment

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

사용하지 않는 코드는 주석 처리를 하기 보단, 삭제를 하는 것이 좋을 것 같아요!

public class JWTUtils {

@Value("${roomescape.auth.jwt.secret}")
private String secretKey;

public AuthToken createToken(Member member) {
String accessToken = Jwts.builder()
.setSubject(member.getId().toString())
.claim("name", member.getName())
.claim("role", member.getRole())
.signWith(Keys.hmacShaKeyFor(secretKey.getBytes()))
.compact();

return new AuthToken(accessToken);
}

public AuthToken extractToken(HttpServletRequest request){
String token = Arrays.stream(request.getCookies())
.filter(cookie -> "token".equals(cookie.getName()))
.findFirst()
.map(Cookie::getValue)
.orElseThrow(() -> new MissingTokenException("토큰을 찾을 수 없습니다."));

return new AuthToken(token);
}

public AuthClaims getClaimsFromToken(String token) {
if (token == null || token.isBlank()) {
throw new MissingTokenException("토큰이 비어있습니다.");
}

try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes()))
.build()
.parseClaimsJws(token)
.getBody();

Long id = Long.parseLong(claims.getSubject());
String name = claims.get("name", String.class);
String role = claims.get("role", String.class);

if (id == null || name == null || role == null) {
throw new InvalidTokenException("필수 클레임이 누락되었습니다.");
}

return new AuthClaims(id, name, role);

} catch (JwtException e) {
throw new InvalidTokenException("유효하지 않은 토큰입니다.");
}
}
}
22 changes: 22 additions & 0 deletions src/main/java/roomescape/DataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package roomescape;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import roomescape.member.Member;
import roomescape.member.MemberRepository;

@Profile("prod")
@Component
@RequiredArgsConstructor
public class DataLoader implements CommandLineRunner {

private final MemberRepository memberRepository;

@Override
public void run(String... args) throws Exception {
memberRepository.save(new Member("어드민", "[email protected]", "password", "ADMIN"));
memberRepository.save(new Member("브라운", "[email protected]", "password", "USER"));
}
}
14 changes: 0 additions & 14 deletions src/main/java/roomescape/ExceptionController.java

This file was deleted.

2 changes: 2 additions & 0 deletions src/main/java/roomescape/RoomescapeApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {"auth","roomescape"})
public class RoomescapeApplication {
public static void main(String[] args) {
SpringApplication.run(RoomescapeApplication.class, args);
Expand Down
Loading