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] 한명수 미션 제출합니다. #77

Open
wants to merge 27 commits into
base: mangsuyo
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b4d561
feat: [1단계] 테스트 적용하기
mangsuyo Jun 23, 2024
b832370
feat: [1단계] 로그인 요청시 토큰 발급
mangsuyo Jun 23, 2024
916b61e
feat: [1단계] 토큰 검증하기
mangsuyo Jun 23, 2024
814f977
feat: [2단계] 테스트 적용
mangsuyo Jun 23, 2024
728c6dd
feat: [2단계] 토큰으로 로그인하기
mangsuyo Jun 23, 2024
62dc582
feat: [2단계] 토큰으로 예약 생성하기
mangsuyo Jun 23, 2024
0169273
feat: [3단계] 테스트 적용하기
mangsuyo Jun 23, 2024
5db49d6
feat: [3단계] admin 페이지 인터셉터
mangsuyo Jun 23, 2024
c978b3c
refactor: [3단계] 구조 및 함수명 리팩터링
mangsuyo Jun 23, 2024
c9a3e42
refactor: 시크릿 키 감추기
mangsuyo Jun 30, 2024
36c3bcb
chore: [1단계] JPA 의존성 추가
mangsuyo Jun 30, 2024
a8ac74f
chore: [4단계] JPA 의존성 추가
mangsuyo Jun 30, 2024
5d62e8a
Merge branch 'mangsuyo-jpa' of https://github.com/mangsuyo/spring-bas…
mangsuyo Jun 30, 2024
d261be0
chore: [4단계] JPA 관련 설정
mangsuyo Jun 30, 2024
61e7358
feat: [4단계] 테스트 적용하기
mangsuyo Jun 30, 2024
4716b9d
feat: [4단계] 엔티티 매핑하기
mangsuyo Jun 30, 2024
047c001
feat: [4단계] 테스트 요구사항 해결하기
mangsuyo Jun 30, 2024
3d5715f
feat: [5단계] 테스트 적용하기
mangsuyo Jul 1, 2024
09128e0
feat: [5단계] jdbc -> jpa로 변경하기
mangsuyo Jul 1, 2024
931795a
feat: [5단계] 테스트 요구사항 해결하기
mangsuyo Jul 1, 2024
202c9ae
feat: [6단계] 테스트 적용하기
mangsuyo Jul 1, 2024
62ea442
feat: [6단계] 테스트 요구사항 해결하기
mangsuyo Jul 1, 2024
3641ab6
feat: 예약 대기 순서 전달하기
mangsuyo Jul 5, 2024
750a450
refactor: 예외 처리 반복 줄이기
mangsuyo Jul 5, 2024
181d65b
fix: 나의 예약에서 예약목록 반환하기
mangsuyo Jul 5, 2024
a71be85
feat: [7단계] jwtUtils 분리하기
mangsuyo Jul 9, 2024
fc94c7e
feat: [7단계] 프로필마다 데이터 주입하기
mangsuyo Jul 9, 2024
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
28 changes: 28 additions & 0 deletions src/main/java/roomescape/DataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package roomescape;

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import roomescape.member.Member;
import roomescape.member.MemberRepository;

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

private final MemberRepository memberRepository;

public DataLoader(MemberRepository memberRepository) {
this.memberRepository = memberRepository;

}

@Override
public void run(String... args) {
Member admin = new Member("어드민", "[email protected]", "password", "ADMIN");
Member brown = new Member("브라운", "[email protected]", "password", "USER");
memberRepository.save(admin);
memberRepository.save(brown);
}
}
66 changes: 66 additions & 0 deletions src/main/java/roomescape/TestDataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package roomescape;

import java.util.Arrays;
import java.util.List;

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import roomescape.member.Member;
import roomescape.member.MemberRepository;
import roomescape.reservation.Reservation;
import roomescape.reservation.ReservationRepository;
import roomescape.theme.Theme;
import roomescape.theme.ThemeRepository;
import roomescape.time.Time;
import roomescape.time.TimeRepository;

@Component
@Profile("test")
public class TestDataLoader implements CommandLineRunner {

private final MemberRepository memberRepository;
private final ThemeRepository themeRepository;
private final TimeRepository timeRepository;
private final ReservationRepository reservationRepository;

public TestDataLoader(MemberRepository memberRepository, ThemeRepository themeRepository,
TimeRepository timeRepository, ReservationRepository reservationRepository) {
this.memberRepository = memberRepository;
this.themeRepository = themeRepository;
this.timeRepository = timeRepository;
this.reservationRepository = reservationRepository;
}

@Override
public void run(String... args) {
Member admin = new Member("어드민", "[email protected]", "password", "ADMIN");
Member brown = new Member("브라운", "[email protected]", "password", "USER");
memberRepository.saveAll(Arrays.asList(admin, brown));

Theme theme1 = new Theme("테마1", "테마1입니다.");
Theme theme2 = new Theme("테마2", "테마2입니다.");
Theme theme3 = new Theme("테마3", "테마3입니다.");
themeRepository.saveAll(Arrays.asList(theme1, theme2, theme3));

List<Time> times = Arrays.asList(
new Time("10:00"),
new Time("12:00"),
new Time("14:00"),
new Time("16:00"),
new Time("18:00"),
new Time("20:00")
);
timeRepository.saveAll(times);

Reservation reservation1 = new Reservation("admin", "2024-03-01", times.get(0), theme1);
Reservation reservation2 = new Reservation("admin", "2024-03-01", times.get(1), theme2);
Reservation reservation3 = new Reservation("admin", "2024-03-01", times.get(2), theme3);
Reservation reservation4 = new Reservation("brown", "2024-03-01", times.get(0), theme2);

reservationRepository.saveAll(Arrays.asList(reservation1, reservation2, reservation3, reservation4));
}
}


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

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import roomescape.member.Member;
import roomescape.member.MemberService;

@Component
public class AdminInterceptor implements HandlerInterceptor {

private MemberService memberService;
private final JwtUtils jwtUtils;

public AdminInterceptor(JwtUtils jwtUtils, MemberService memberService) {
this.jwtUtils = jwtUtils;
this.memberService = memberService;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
String token = jwtUtils.extractTokenFromCookie(request.getCookies());
Member member = memberService.getMemberFromToken(token);
if (member == null || !member.getRole().equals("ADMIN")) {
response.setStatus(401);
return false;
}
return true;
}
}
34 changes: 34 additions & 0 deletions src/main/java/roomescape/auth/AuthWebConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package roomescape.auth;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
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;

@Configuration
public class AuthWebConfiguration implements WebMvcConfigurer {

private final LoginMemberArgumentResolver loginMemberArgumentResolver;
private final AdminInterceptor adminInterceptor;

@Autowired
public AuthWebConfiguration(LoginMemberArgumentResolver loginMemberArgumentResolver,
AdminInterceptor adminInterceptor) {
this.loginMemberArgumentResolver = loginMemberArgumentResolver;
this.adminInterceptor = adminInterceptor;
}

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

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

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JwtConfiguration {

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

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

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Value;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import roomescape.member.Member;

public class JwtUtils {

private String secretKey;

@Value("${roomescape.auth.jwt.secret}")
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}

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

public String extractTokenFromCookie(Cookie[] cookies) {
if (cookies == null) {
throw new IllegalArgumentException("Cookies array is null");
}

return Arrays.stream(cookies)
.filter(cookie -> "token".equals(cookie.getName()))
.map(Cookie::getValue)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Token cookie not found"));
}

public Long getIdFromToken(String token) {
return Long.valueOf(Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes()))
.build()
.parseClaimsJws(token)
.getBody().getSubject());
}
}
8 changes: 8 additions & 0 deletions src/main/java/roomescape/auth/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package roomescape.auth;

public record LoginMember(
Long id,
String name,
String email,
String password) {
}
43 changes: 43 additions & 0 deletions src/main/java/roomescape/auth/LoginMemberArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package roomescape.auth;

import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import roomescape.member.Member;
import roomescape.member.MemberService;

@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

private final MemberService memberService;
private final JwtUtils jwtUtils;

public LoginMemberArgumentResolver(MemberService memberService, JwtUtils jwtUtils) {
this.memberService = memberService;
this.jwtUtils = jwtUtils;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(LoginMember.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Cookie[] cookies = request.getCookies();
String token = jwtUtils.extractTokenFromCookie(cookies);
if (token == null) {
return null;
}
Member member = memberService.getMemberFromToken(token);
return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole());
}
}
99 changes: 60 additions & 39 deletions src/main/java/roomescape/member/Member.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,64 @@
package roomescape.member;

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

@Entity
public class Member {
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;
}

public Member(String name, String email, String password, String role) {
this.name = name;
this.email = email;
this.password = password;
this.role = role;
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}

public String getRole() {
return role;
}

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String name;

@Column(nullable = false, unique = true)
private String email;

@Column(nullable = false)
private String password;

@Column(nullable = false)
private String role;

public Member() {
}

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

public Member(String name, String email, String password, String role) {
this.name = name;
this.email = email;
this.password = password;
this.role = role;
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}

public String getRole() {
return role;
}
}
Loading