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] 정다영 미션 제출합니다. #71

Open
wants to merge 16 commits into
base: day024
Choose a base branch
from
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Spring Core (배포)

<h3>7단계 - @Configuration</h3>

- [x] JWT 관련 로직을 roomescape와 같은 계층의 auth 패키지의 클래스로 분리
- [x] 불필요한 DB 접근 최소화
- JWT 토큰에는 사용자 식별 정보와 권한 정보가 들어갑니다.
만약 이 두 정보만 필요하다면 DB 접근이 필요하지 않습니다.


<h3>8단계 - Profile과 Resource</h3>

schema.sql 대신 데이터베이스를 초기화 클래스 생성
- [x] Production용도 DataLoader 생성
사용자 정보만 초기화
- [x] Test용도 TestDataLoader생성
테스트에 필요한 사전 값 초기화
- [x] Environemt 분리 (토큰 비밀키)
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ 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 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' //jpa변경

implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
Expand All @@ -29,6 +28,7 @@ dependencies {
runtimeOnly 'com.h2database:h2'
}


test {
useJUnitPlatform()
}
}
12 changes: 12 additions & 0 deletions src/main/java/auth/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package auth;

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

@Configuration
public class AuthConfig {
@Bean
public JwtTokenProvider jwtTokenProvider() {
return new JwtTokenProvider();
}
}
41 changes: 41 additions & 0 deletions src/main/java/auth/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import roomescape.member.MemberResponse;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;

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

@Bean
public SecretKey secretKey() {
return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
}

public String createToken(MemberResponse member) {
String accessToken = Jwts.builder()
.setSubject(member.getId().toString())
.claim("name", member.getName())
.claim("email", member.getEmail())
.signWith(secretKey())
.compact();

return accessToken;
}

public Long extractMemberIdFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey())
.build()
.parseClaimsJws(token)
.getBody();
return Long.valueOf(claims.getSubject());
}
}
23 changes: 23 additions & 0 deletions src/main/java/roomescape/DataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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("Production")
public class DataLoader implements CommandLineRunner {
private final MemberRepository memberRepository;

public DataLoader(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void run(final String... args) throws Exception {
final Member member1 = memberRepository.save(new Member("어드민", "[email protected]", "password", "ADMIN"));
final Member member2 = memberRepository.save(new Member("브라운", "[email protected]", "password", "USER"));
}
}
2 changes: 1 addition & 1 deletion src/main/java/roomescape/PageController.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ public String login() {
public String signup() {
return "signup";
}
}
}
5 changes: 4 additions & 1 deletion src/main/java/roomescape/RoomescapeApplication.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package roomescape;

import auth.AuthConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

@Import(AuthConfig.class)
@SpringBootApplication
public class RoomescapeApplication {
public static void main(String[] args) {
SpringApplication.run(RoomescapeApplication.class, args);
}
}
}
56 changes: 56 additions & 0 deletions src/main/java/roomescape/TestDataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package roomescape;

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

@Profile("test")
@Component
public class TestDataLoader implements CommandLineRunner {
private final MemberRepository memberRepository;
private final ThemeRepository themeRepository;
private final TimeRepository timeRepository;
private final ReservationRepository reservationRepository;

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

@Override
public void run(final String... args) throws Exception {
final Member member1 = memberRepository.save(new Member("어드민", "[email protected]", "password", "ADMIN"));
final Member member2 = memberRepository.save(new Member("브라운", "[email protected]", "password", "USER"));

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

final Time time1 = timeRepository.save(new Time("10:00"));
final Time time2 = timeRepository.save(new Time("12:00"));
final Time time3 = timeRepository.save(new Time("14:00"));
final Time time4 = timeRepository.save(new Time("16:00"));
final Time time5 = timeRepository.save(new Time("18:00"));
final Time time6 = timeRepository.save(new Time("20:00"));

reservationRepository.save(new Reservation("어드민", "2024-03-01", time1, theme1, member1));
reservationRepository.save(new Reservation("어드민", "2024-03-01", time2, theme2, member1));
reservationRepository.save(new Reservation("어드민", "2024-03-01", time3, theme3, member1));
reservationRepository.save(new Reservation("브라운", "2024-03-01", time4, theme1, member2));
}
}
26 changes: 26 additions & 0 deletions src/main/java/roomescape/member/AdminInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package roomescape.member;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class AdminInterceptor implements HandlerInterceptor {

@Autowired
private MemberService memberService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = memberService.extractTokenFromCookie(request.getCookies());
Member member = memberService.extractMemberFromToken(token);

if (member == null || !member.getRole().equals("ADMIN")) {
response.setStatus(401);
return false;
}
return true;
}
}
61 changes: 61 additions & 0 deletions src/main/java/roomescape/member/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package roomescape.member;

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

@Entity
public class LoginMember {

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

private String name;
private String email;
private String role;

public LoginMember() {

}

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

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getRole() {
return role;
}

public void setRole(String role) {
this.role = role;
}
}
35 changes: 35 additions & 0 deletions src/main/java/roomescape/member/LoginMemberArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package roomescape.member;

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;

@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

private final MemberService memberService;

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

@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 {
String token = webRequest.getHeader("Cookie").split("=")[1];

Member member = memberService.extractMemberFromToken(token);

if (member == null) {
throw new RuntimeException("인증되지 않은 사용자입니다.");
}
return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole());
}
}
19 changes: 16 additions & 3 deletions src/main/java/roomescape/member/Member.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
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) {
public Member() {
}

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

public Member(String name, String email, String password, String role) {
this.name = name;
this.email = email;
Expand All @@ -40,4 +52,5 @@ public String getPassword() {
public String getRole() {
return role;
}
}

}
Loading