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] 신예린 미션 제출합니다. #68

Open
wants to merge 20 commits into
base: nyeroni
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ out/

### VS Code ###
.vscode/

src/main/resources/application-oauth.properties
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
53 changes: 53 additions & 0 deletions src/main/java/roomescape/TestDataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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;

@Profile("test")
@Component
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) throws Exception {
final Member member1 = memberRepository.save(new Member("어드민", "admin@email.com", "password", "ADMIN"));
final Member member2 = memberRepository.save(new Member("브라운", "brown@email.com", "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));
}
}
53 changes: 53 additions & 0 deletions src/main/java/roomescape/auth/AdminAccessInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package roomescape.auth;

import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import roomescape.member.MemberService;
import roomescape.member.ViewMemberResponse;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Optional;

@Component
public class AdminAccessInterceptor implements HandlerInterceptor {

private final JwtProvider jwtProvider;
private final MemberService memberService;

public AdminAccessInterceptor(JwtProvider jwtProvider, MemberService memberService) {
this.jwtProvider = jwtProvider;
this.memberService = memberService;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

Optional<String> token = Arrays.stream(request.getCookies())
.filter(cookie -> cookie.getName().equals("token"))
.findFirst()
.map(Cookie::getValue);

if(token.isEmpty()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

String tokenValue = token.get();
Claims claims = jwtProvider.getClaimsFromToken(tokenValue);
String role = claims.get("role", String.class);
ViewMemberResponse member = memberService.findMemberById(Long.parseLong(claims.getSubject()));

if(member == null || !member.getRole().equalsIgnoreCase("admin")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

return true;
}
}
15 changes: 15 additions & 0 deletions src/main/java/roomescape/auth/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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 AuthConfig {

@Bean
public JwtUtils jwtUtils(@Value("${roomescape.auth.jwt.secret}") String secretKey,
@Value("${roomescape.auth.jwt.expiration}") String expiration) {
return new JwtUtils(secretKey, expiration);
}
}
34 changes: 34 additions & 0 deletions src/main/java/roomescape/auth/AuthMemberArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package roomescape.auth;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
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 java.util.Arrays;

@Component
public class AuthMemberArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
boolean isUserClass = LoginMember.class.equals(parameter.getParameterType());
return isLoginUserAnnotation && isUserClass;
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
Arrays.stream(request.getCookies())
.filter(cookie -> cookie.getName().equals("token"))
.findFirst()
.map(Cookie::getValue)
.orElseThrow(()-> new IllegalArgumentException("로그인이 필요합니다."));
LoginMember loginUser = (LoginMember) request.getAttribute("loginUser");
return loginUser;
}
}
25 changes: 25 additions & 0 deletions src/main/java/roomescape/auth/DataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package roomescape.auth;

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("default")
@Component
public class DataLoader implements CommandLineRunner {

private final MemberRepository memberRepository;

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

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

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

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;

@Component
public class JwtProvider {
@Value("${roomescape.auth.jwt.secret}")
private String secretKey;
@Value("${roomescape.auth.jwt.expiration}")
private Long expiration;

public Claims getClaimsFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(secretKey.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
}
}
28 changes: 28 additions & 0 deletions src/main/java/roomescape/auth/JwtUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package roomescape.auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;

public class JwtUtils {
private String secretKey;
private String expiration;

public JwtUtils(String secretKey, String expiration) {
this.secretKey = secretKey;
this.expiration = expiration;
}

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

import java.nio.file.attribute.UserPrincipal;

public class LoginMember implements UserPrincipal {

private Long id;
private String name;
private String email;
private String role;

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;
}
}
70 changes: 70 additions & 0 deletions src/main/java/roomescape/auth/LoginMemberArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package roomescape.auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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.member.MemberService;
import roomescape.member.ViewMemberResponse;

@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

private final MemberService memberService;
private final JwtProvider jwtProvider;
public LoginMemberArgumentResolver(MemberService memberService, JwtProvider jwtProvider) {
this.memberService = memberService;
this.jwtProvider = jwtProvider;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
boolean isUserClass = LoginMember.class.equals(parameter.getParameterType());
return isLoginUserAnnotation && isUserClass;
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
Cookie[] cookies = request.getCookies();

if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("token")) {
String token = cookie.getValue();
System.out.println("==========");
System.out.println("token == " + token);
System.out.println("==========");

Claims claims = jwtProvider.getClaimsFromToken(token);
if (claims != null && claims.getSubject() != null) {
System.out.println("claims == " + claims.getSubject());
Long memberId = Long.parseLong(claims.getSubject());
ViewMemberResponse member = memberService.findMemberById(memberId);

if (member != null) {
System.out.println("==========");
System.out.println("member == " + member.getEmail());
System.out.println("==========");

return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole());
} else {
throw new IllegalArgumentException("해당 ID의 회원을 찾을 수 없습니다.");
}
} else {
throw new IllegalArgumentException("토큰에서 유효한 클레임을 추출할 수 없습니다.");
}
}
}
}
throw new IllegalArgumentException("유효한 인증 정보가 없습니다.");
}
}
11 changes: 11 additions & 0 deletions src/main/java/roomescape/auth/LoginUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package roomescape.auth;

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)
public @interface LoginUser {
}
Loading