Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ out/

### VS Code ###
.vscode/

### Secrets ###
application*.properties
schema.sql
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "spring-basic-roomescape-playground-submodule"]
path = spring-basic-roomescape-playground-submodule
url = https://github.com/zhy2on/spring-basic-roomescape-playground-submodule.git
10 changes: 9 additions & 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 All @@ -32,3 +32,11 @@ dependencies {
test {
useJUnitPlatform()
}

processResources.dependsOn('copySecret')

tasks.register('copySecret', Copy) {
from './spring-basic-roomescape-playground-submodule'
include 'application*.properties'
into './src/main/resources'
}
32 changes: 32 additions & 0 deletions src/main/java/roomescape/DataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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;
import roomescape.theme.Theme;
import roomescape.theme.ThemeRepository;
import roomescape.time.Time;
import roomescape.time.TimeRepository;

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

@Component
@Profile("!test")
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.saveAll(Arrays.asList(admin, brown));
}
}
63 changes: 63 additions & 0 deletions src/main/java/roomescape/TestDataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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;
import roomescape.reservation.Reservation;
import roomescape.reservation.ReservationRepository;
import roomescape.theme.Theme;
import roomescape.theme.ThemeRepository;
import roomescape.time.Time;
import roomescape.time.TimeRepository;

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

@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("어드민", "2024-03-01", times.get(0), theme1, admin);
Reservation reservation2 = new Reservation("어드민", "2024-03-01", times.get(1), theme2, admin);
Reservation reservation3 = new Reservation("어드민", "2024-03-01", times.get(2), theme3, admin);
Reservation reservation4 = new Reservation("브라운", "2024-03-01", times.get(0), theme2);

reservationRepository.saveAll(Arrays.asList(reservation1, reservation2, reservation3, reservation4));
}
}
47 changes: 47 additions & 0 deletions src/main/java/roomescape/auth/JwtUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package roomescape.auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import roomescape.member.Member;

import java.security.Key;

public class JwtUtils {

private final Key key;

public JwtUtils(String secretKey) {
this.key = Keys.hmacShaKeyFor(secretKey.getBytes());
}

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

public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}

public String getRoleFromToken(String token) {
Claims claims = parseToken(token);
return claims.get("role", String.class);
}

public Member getMemberFromToken(String token) {
Claims claims = parseToken(token);
Long id = Long.parseLong(claims.getSubject());
String name = claims.get("name", String.class);
String role = claims.get("role", String.class);
return new Member(id, name, role);
}
}
50 changes: 50 additions & 0 deletions src/main/java/roomescape/config/AdminInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package roomescape.config;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import roomescape.auth.JwtUtils;
import roomescape.exception.ForbiddenException;
import roomescape.exception.UnauthorizedException;
import roomescape.member.Member;
import roomescape.util.CookieUtil;

public class AdminInterceptor implements HandlerInterceptor {

private final JwtUtils jwtUtils;

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

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

if (token == null) {
throw new UnauthorizedException("No token found in the request");
}

Member member = jwtUtils.getMemberFromToken(token);

if (member == null) {
throw new UnauthorizedException("Invalid token");
}

if (!"ADMIN".equals(member.getRole())) {
throw new ForbiddenException("User does not have admin privileges");
}

return true;
} catch (UnauthorizedException | ForbiddenException e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(e.getMessage());
return false;
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("An unexpected error occurred");
return false;
}
}
}
18 changes: 18 additions & 0 deletions src/main/java/roomescape/config/JwtConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package roomescape.config;

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

@Configuration
public class JwtConfig {

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

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

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
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 roomescape.auth.JwtUtils;
import roomescape.member.LoginMember;
import roomescape.util.CookieUtil;
import io.jsonwebtoken.Claims;

public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
private final JwtUtils jwtUtils;

public LoginMemberArgumentResolver(JwtUtils jwtUtils) {
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) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
String token = CookieUtil.extractTokenFromCookie(request.getCookies());
if (token != null && !token.isEmpty()) {
try {
Claims claims = jwtUtils.parseToken(token);
Long id = Long.parseLong(claims.getSubject());
String name = claims.get("name", String.class);
String email = claims.get("email", String.class);
String role = claims.get("role", String.class);
return new LoginMember(id, name, email, role);
} catch (Exception e) {
return null;
}
}
return null;
}
}
30 changes: 30 additions & 0 deletions src/main/java/roomescape/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package roomescape.config;

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 roomescape.auth.JwtUtils;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

private final JwtUtils jwtUtils;

public WebConfig(JwtUtils jwtUtils) {
this.jwtUtils = jwtUtils;
}

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

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

public class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.exception;

public class MemberNotFoundException extends RuntimeException {
public MemberNotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.exception;

public class ThemeNotFoundException extends RuntimeException {
public ThemeNotFoundException(String message) {
super(message);
}
}
7 changes: 7 additions & 0 deletions src/main/java/roomescape/exception/TimeNotFoundException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.exception;

public class TimeNotFoundException extends RuntimeException {
public TimeNotFoundException(String message) {
super(message);
}
}
7 changes: 7 additions & 0 deletions src/main/java/roomescape/exception/UnauthorizedException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.exception;

public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}
8 changes: 8 additions & 0 deletions src/main/java/roomescape/member/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package roomescape.member;

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

public record LoginRequest(
String email,
String password
) { }
Loading