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 Data JPA] 신혜빈 미션 제출합니다. #108

Open
wants to merge 61 commits into
base: shin378378
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
e233cc2
feat : 로그인 기능 추가
shin378378 Dec 25, 2024
b043419
feat : 1단계 통과
shin378378 Dec 25, 2024
f7a3a4b
refactor : 로그인 리팩토링
shin378378 Dec 25, 2024
7a17aa9
feat : 이름없이 회원정보 조회하기
shin378378 Dec 25, 2024
aa8ee73
refactor : reservationController 주소 리팩토링하기
shin378378 Dec 25, 2024
9cff672
feat : 권한이 있는 자만 접근하도록 설정
shin378378 Dec 25, 2024
6be6af3
feat : 권한 확인 리팩토링
shin378378 Dec 25, 2024
9f11631
refactor : 안 쓰는 파일 지우기
shin378378 Dec 25, 2024
56723c7
refactor : 안 쓰는 파일 지우기
shin378378 Jan 1, 2025
9db83a8
refactor : 안 쓰는 예외처리 코드 지우기
shin378378 Jan 7, 2025
3cb4153
refactor : key를 지역변수로 바꾸기
shin378378 Jan 7, 2025
7fbcc72
refactor : jwtService로 userId 찾는 부분 옮기기
shin378378 Jan 7, 2025
8b84763
refactor : 안 쓰는 어노테이션 삭제
shin378378 Jan 7, 2025
c57fbfb
refactor : @Autowired 대신 생성자로 주입해주기
shin378378 Jan 7, 2025
0dbb542
refactor : 코드 indent depth 줄이기
shin378378 Jan 7, 2025
903343e
refactor : boolean대신 에러코드 반환하기
shin378378 Jan 7, 2025
fa6d70c
refactor : try-catch를 ExceptionHandler로 처리하기
shin378378 Jan 7, 2025
787dd03
refactor : 커스텀 어노테이션 만들기
shin378378 Jan 7, 2025
6591dba
refactor : loginController의 멤버 찾는 코드를 service계층으로 옮기기
shin378378 Jan 7, 2025
d1d0e04
refactor : LoginController 필요없는 의존성 제거하기
shin378378 Jan 7, 2025
7abea53
refactor : 멤버찾는 코드 중복해결
shin378378 Jan 7, 2025
2ce4d0d
feat : gradle에 의존성 추가하기
shin378378 Jan 8, 2025
62ebd1c
feat : 엔티티 설정하기
shin378378 Jan 8, 2025
c4707f9
feat : test 추가하기
shin378378 Jan 8, 2025
41881e7
feat : @repository 어노테이션 추가하기
shin378378 Jan 8, 2025
8e8c37b
feat : memberRepository 만들기
shin378378 Jan 8, 2025
552ebaf
feat : themeRepository 만들기
shin378378 Jan 8, 2025
cde7be6
feat : reservationRepository 만들기
shin378378 Jan 8, 2025
a8c2c8c
feat : themeRepository 만들기
shin378378 Jan 8, 2025
1672171
refactor : 스키마 삭제
shin378378 Jan 8, 2025
19c1290
feat : jwt에서 memberDao 삭제
shin378378 Jan 8, 2025
60751e7
feat : login에서 dao삭제
shin378378 Jan 8, 2025
b2f3fea
feat : timeDao 삭제
shin378378 Jan 8, 2025
9feb8f4
feat : 주석처리
shin378378 Jan 8, 2025
0676f86
feat : Time 도메인을 JPA로
shin378378 Jan 8, 2025
efd3428
feat : Theme 도메인을 JPA로
shin378378 Jan 8, 2025
754949c
feat : Reservation 도메인을 JPA로
shin378378 Jan 8, 2025
78a0613
feat : member 도메인을 JPA로
shin378378 Jan 8, 2025
0b9afee
feat : 4단계 test 코드 추가
shin378378 Jan 8, 2025
fdb3a20
feat : sql 데이터 수정
shin378378 Jan 8, 2025
880c550
feat : sql 데이터 수정
shin378378 Jan 8, 2025
23e3a4d
feat : 5단계 통과
shin378378 Jan 8, 2025
c5fe67f
feat : 예약초반에 이름 설정해버리기
shin378378 Jan 8, 2025
ec96693
refactor : 정렬하기
shin378378 Jan 8, 2025
0c0a96f
feat : 6단계 테스트 작성
shin378378 Jan 8, 2025
611bcd0
feat : 예약 변경하기
shin378378 Jan 8, 2025
1868459
feat : 예약 대기 추가하기
shin378378 Jan 8, 2025
d416182
feat : 6단계 통과
shin378378 Jan 8, 2025
842d0ed
refactor : 안 쓰는 코드 삭제하기
shin378378 Jan 9, 2025
e5688a0
refactor : 안 쓰는 setter 삭제하기
shin378378 Jan 9, 2025
a87cfc5
feat : @Repository어노테이션 붙이기
shin378378 Jan 9, 2025
37d5690
feat : int 타입으로 변경하기
shin378378 Jan 9, 2025
c09cf97
feat : 다시 Long 타입으로 변경하기
shin378378 Jan 9, 2025
9e59f50
feat : int 타입도 받을 수 있게 변경
shin378378 Jan 9, 2025
ed03913
feat : 7단계 초기설정
shin378378 Jan 13, 2025
ee7409f
feat : cookieUtil 위치 바꾸기
shin378378 Jan 14, 2025
68dd573
feat : 미션 7 사전작업
shin378378 Jan 14, 2025
230c9c3
feat : test2 변경
shin378378 Jan 14, 2025
ce30c3e
feat : 중간 점검
shin378378 Jan 14, 2025
f05d599
feat : 7단계 통과
shin378378 Jan 14, 2025
4eb2fab
feat : 8단계 통과
shin378378 Jan 14, 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
31 changes: 31 additions & 0 deletions .run/MissionStepTest.삼단계.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="MissionStepTest.삼단계" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="JWT_SECRET_KEY" value="bOjgcSvj4CvncQT6+XFS7IJSdTCvsSdIjLaVBAyKG6g=" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":test" />
<option value="--tests" />
<option value="&quot;roomescape.MissionStepTest.삼단계&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>
3 changes: 2 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 @@ -26,6 +26,7 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'

implementation 'com.h2database:h2'
runtimeOnly 'com.h2database:h2'
}

Expand Down
19 changes: 19 additions & 0 deletions src/main/java/roomescape/ExceptionController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
package roomescape;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.server.ResponseStatusException;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class ExceptionController {

@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<Void> handleResponseStatusException(ResponseStatusException e) {
return ResponseEntity.status(e.getStatusCode()).build();
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException e) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<Void> handleRuntimeException(Exception e) {
e.printStackTrace();
return ResponseEntity.badRequest().build();
}
}

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

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.HandlerInterceptor;
import roomescape.jwt.JwtService;
import roomescape.member.Member;

import java.util.Arrays;

@Component
public class AdminInterceptor implements HandlerInterceptor {
private final JwtService jwtService;

public AdminInterceptor(JwtService jwtService) {
this.jwtService = jwtService;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = extractTokenFromCookies(request.getCookies());
if (token == null) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "토큰을 찾을 수 없습니다.");
}

Member member = jwtService.getMemberFromToken(token);
if (!"ADMIN".equals(member.getRole())) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "사용자를 찾을 수 없습니다.");
}

return true;
}

private String extractTokenFromCookies(Cookie[] cookies) {
if (cookies == null) {
return null;
}

return Arrays.stream(cookies)
.filter(cookie -> "token".equals(cookie.getName()))
.map(Cookie::getValue)
.findFirst()
.orElse(null);
}
}
9 changes: 9 additions & 0 deletions src/main/java/roomescape/auth/Authentication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package roomescape.auth;

import java.lang.annotation.*;

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

import org.springframework.beans.factory.annotation.Autowired;
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 roomescape.jwt.JwtService;
import roomescape.member.Member;
import roomescape.util.CookieUtil;

@Component
public class AuthenticationArgumentResolver implements HandlerMethodArgumentResolver {

@Autowired
private JwtService jwtService;

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

@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {

String token = CookieUtil.extractTokenFromHeader(webRequest)
.orElseThrow(() -> new IllegalArgumentException("토큰이 존재하지 않습니다. 로그인이 필요합니다."));

return jwtService.getMemberFromToken(token);
}
}
32 changes: 32 additions & 0 deletions src/main/java/roomescape/auth/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package roomescape.auth;

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
public class WebConfig implements WebMvcConfigurer {

private final AdminInterceptor adminInterceptor;
private final AuthenticationArgumentResolver authenticationArgumentResolver;

public WebConfig(AdminInterceptor adminInterceptor, AuthenticationArgumentResolver authenticationArgumentResolver) {
this.adminInterceptor = adminInterceptor;
this.authenticationArgumentResolver = authenticationArgumentResolver;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminInterceptor)
.addPathPatterns("/admin");
}

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

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

import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Service;
import roomescape.member.Member;
import roomescape.member.MemberRepository;

@Service
public class JwtService {
private final JwtUtil jwtUtil;
private final MemberRepository memberRepository;

public JwtService(JwtUtil jwtUtil, MemberRepository memberRepository) {
this.jwtUtil = jwtUtil;
this.memberRepository = memberRepository;
}

public Long getUserIdFromToken(String token) {
Claims claims = jwtUtil.getClaimsFromToken(token);
try {
return Long.valueOf(claims.getSubject());
} catch (Exception e) {
throw new IllegalArgumentException("Claims에서 User ID를 추출할 수 없습니다.", e);
}
}

public Member getMemberFromToken(String token) {
Long userId = getUserIdFromToken(token);
Member member = memberRepository.findById(userId).get();
if (member == null) {
throw new IllegalArgumentException("토큰으로부터 유저를 찾을 수 없습니다.");
}
return member;
}
}
46 changes: 46 additions & 0 deletions src/main/java/roomescape/jwt/JwtUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package roomescape.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;

@Component
public class JwtUtil {
private static final long EXPIRATION_TIME = 86400000;
@Value("${jwt.secret}")
private String secretKey;

private Key generateKey() {
return new SecretKeySpec(secretKey.getBytes(), SignatureAlgorithm.HS256.getJcaName());
}

public String generateToken(Long userId, String role) {
Key key = generateKey();
return Jwts.builder()
.setSubject(String.valueOf(userId))
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}

public Claims getClaimsFromToken(String token) {
try {
Key key = generateKey();
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
throw new IllegalArgumentException("유효하지 않은 토큰입니다.", e);
}
}
}
41 changes: 41 additions & 0 deletions src/main/java/roomescape/login/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package roomescape.login;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.jwt.JwtService;
import roomescape.member.Member;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/login")
public class LoginController {
private LoginService loginService;
private JwtService jwtService;

public LoginController(LoginService loginService, JwtService jwtService) {
this.loginService = loginService;
this.jwtService = jwtService;
}

@PostMapping
public ResponseEntity<Map<String, Object>> login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
String token = loginService.authenticate(loginRequestDto.getEmail(), loginRequestDto.getPassword());
Cookie cookie = loginService.createAuthCookie(token);
response.addCookie(cookie);
Map<String, Object> responseBody = loginService.createLoginResponse(token);
return ResponseEntity.ok(responseBody);
}

@GetMapping("/check")
public ResponseEntity<Map<String, String>> checkLogin(@CookieValue(name = "token", required = true) String token) {
Member member = jwtService.getMemberFromToken(token);

Map<String, String> response = new HashMap<>();
response.put("name", member.getName());
return ResponseEntity.ok(response);
}
}
19 changes: 19 additions & 0 deletions src/main/java/roomescape/login/LoginRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package roomescape.login;

public class LoginRequestDto {
private String email;
private String password;

public LoginRequestDto(String email, String password) {
this.email = email;
this.password = password;
}

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}
}
Loading