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] 안준영 미션 제출합니다. #86

Open
wants to merge 26 commits into
base: junyeong-an
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5956b9f
feat: Auth 패키지 생성 및 클래스 생성
Junyeong-An Jul 26, 2024
df79257
refactor: 테스트명 수정
Junyeong-An Jul 26, 2024
ac7b77f
feat: 2단계 테스트 추가
Junyeong-An Jul 26, 2024
eea343a
feat: 로그인 예약 응답 추가
Junyeong-An Jul 26, 2024
786d931
feat: 테스트명 수정
Junyeong-An Jul 26, 2024
5bf4530
feat: jwtTokenTest 추가
Junyeong-An Jul 26, 2024
91be93a
Feat: 유정 정보 응답 클래스 생성
Junyeong-An Aug 4, 2024
9162bd3
Refactor: 토큰으로 유저 정보를 반환
Junyeong-An Aug 4, 2024
efa199b
Refactor: 테스트 코드에 맞춰 String 타입으로 반환
Junyeong-An Aug 4, 2024
47783ba
refactor: record로 변환
Junyeong-An Aug 6, 2024
b4e8cf8
refactor: 인증 메서드 분리
Junyeong-An Aug 6, 2024
152a202
refactor: 필요없는 import문 제거
Junyeong-An Aug 6, 2024
e489728
Feat: JpaRepository 추가
Junyeong-An Aug 13, 2024
863e220
Feat: data-jpa 의존성 추가
Junyeong-An Aug 13, 2024
98b8366
Remove: Dao 삭제
Junyeong-An Aug 13, 2024
f4acdea
Refactor: jpa 엔티티화
Junyeong-An Aug 13, 2024
60bf826
Feat: jpa 테스트 코드 추가
Junyeong-An Aug 13, 2024
ea9bc3e
Remove: 필요없는 sql구문 제거
Junyeong-An Aug 13, 2024
f54660d
Refactor: JpaRepository 의존성 연결
Junyeong-An Aug 13, 2024
373efb4
Feat: 테스트 코드 추가
Junyeong-An Aug 13, 2024
12d2d36
Feat: member 의존성 추가
Junyeong-An Aug 13, 2024
be4f692
Feat: 내 예약 목록 반환 DTO 추가
Junyeong-An Aug 13, 2024
b0ae410
Feat: sql 구문 수정
Junyeong-An Aug 13, 2024
8343015
Feat: 토큰으로 내 예약 목록 조회 기능 추가
Junyeong-An Aug 13, 2024
94635f0
Feat: Waiting 엔티티 생성 및 연관관계
Junyeong-An Aug 13, 2024
aae16bb
Refactor: Member 찾는 로직 수정
Junyeong-An Aug 13, 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,13 +15,13 @@ 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 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-gson:0.11.2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
Expand Down
90 changes: 90 additions & 0 deletions src/main/java/roomescape/auth/AuthInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package roomescape.auth;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.servlet.HandlerInterceptor;

import java.io.IOException;
import java.util.Arrays;

public class AuthInterceptor implements HandlerInterceptor {

private final AuthService authService;

public AuthInterceptor(AuthService authService) {
this.authService = authService;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ResponseEntity<String> authCheck = checkAuthentication(request);
Cookie[] cookies = request.getCookies();
if (authCheck != null) {
handleResponseEntity(response, authCheck);
return false;
}

LoginResponse loginResponse = getLoginResponse(request);

if (loginResponse == null) {
ResponseEntity<String> loginRedirect = ResponseEntity.status(HttpStatus.FOUND)
.header(HttpHeaders.LOCATION, "/login")
.body("유저를 찾을 수 없습니다. 로그인 페이지로 이동합니다.");
handleResponseEntity(response, loginRedirect);
return false;
}
if ("ADMIN".equals(loginResponse.getRole())) {
return true; // 관리자 페이지 접근 허용
} else {
ResponseEntity<String> accessDenied = ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.header(HttpHeaders.LOCATION, "/access-denied")
.body("관리자 페이지입니다. 접근 권한이 없습니다.");
handleResponseEntity(response, accessDenied);
return false;
}
}

private ResponseEntity<String> checkAuthentication(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return ResponseEntity.status(HttpStatus.FOUND)
.header(HttpHeaders.LOCATION, "/login")
.body("쿠키를 찾을 수 없습니다. 로그인 페이지로 이동합니다.");
}

Cookie tokenCookie = Arrays.stream(cookies)
.filter(cookie -> "token".equals(cookie.getName()))
.findFirst()
.orElse(null);

if (tokenCookie == null) {
return ResponseEntity.status(HttpStatus.FOUND)
.header(HttpHeaders.LOCATION, "/login")
.body("토큰을 찾을 수 없습니다. 로그인 페이지로 이동합니다.");
}
return null;
}

private LoginResponse getLoginResponse(HttpServletRequest request) {
Cookie tokenCookie = Arrays.stream(request.getCookies())
.filter(cookie -> "token".equals(cookie.getName()))
.findFirst()
.orElse(null);

if (tokenCookie == null) {
return null;
}

return authService.findUserByToken(tokenCookie.getValue());
}

private void handleResponseEntity(HttpServletResponse response, ResponseEntity<String> entity) throws IOException, IOException {
response.setStatus(entity.getStatusCodeValue());
response.setHeader(HttpHeaders.LOCATION, entity.getHeaders().getFirst(HttpHeaders.LOCATION));
response.getWriter().write(entity.getBody());
}
}
19 changes: 19 additions & 0 deletions src/main/java/roomescape/auth/AuthInterceptorConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package roomescape.auth;

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

@Configuration
public class AuthInterceptorConfig {

private final AuthService authService;

public AuthInterceptorConfig(AuthService authService) {
this.authService = authService;
}

@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor(authService);
}
}
70 changes: 70 additions & 0 deletions src/main/java/roomescape/auth/AuthService.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.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class AuthService {

private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private static final long EXPIRATION_TIME = 86400000;

public TokenResponse createToken(TokenRequest tokenRequest) {

String role = "USER";
if ("[email protected]".equals(tokenRequest.getEmail())) {
role = "ADMIN";
}

Map<String, Object> claims = new HashMap<>();
claims.put("role", role);

String token = Jwts.builder()
.setClaims(claims)
.setSubject(tokenRequest.getEmail())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SECRET_KEY)
.compact();

return new TokenResponse(token);
}

public LoginResponse findUserByToken(String token) {

String role = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody()
.get("role", String.class);
String email = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();

return new LoginResponse(email, role);
}

public UserResponse checkUserByToken(String token) {
Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
String email = claims.getSubject();
if ("[email protected]".equals(email)) {
return new UserResponse("어드민", 1L);
} else if ("[email protected]".equals(email)) {
return new UserResponse("brown",2L);
} else {
return null;
}
}
}
58 changes: 58 additions & 0 deletions src/main/java/roomescape/auth/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package roomescape.auth;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {
private final AuthService authService;

public LoginController(AuthService authService) {
this.authService = authService;
}

@PostMapping("/login")
public ResponseEntity<Void> login(@RequestBody TokenRequest tokenRequest, HttpServletResponse response) {
TokenResponse tokenResponse = authService.createToken(tokenRequest);

response.addCookie(createHttpOnlyCookie("token", tokenResponse.getAccessToken()));

return ResponseEntity.ok().build();
}

private Cookie createHttpOnlyCookie(String name, String value) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setHttpOnly(true);
return cookie;
}

@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletResponse response) {
Cookie cookie = new Cookie("token", "");
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
return ResponseEntity.ok().build();
}

@GetMapping("/login/check")
public ResponseEntity<UserResponse> checkLogin(@CookieValue("token") String token) {
UserResponse user = authService.checkUserByToken(token);
if (user == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
return ResponseEntity.ok(user);
}
}
14 changes: 14 additions & 0 deletions src/main/java/roomescape/auth/LoginReservationResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package roomescape.auth;

public record LoginReservationResponse(
String name
) {
public LoginReservationResponse(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

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

public record LoginResponse(
String email,
String role
) {
public LoginResponse(String email, String role) {
this.email = email;
this.role = role;
}

public String getEmail() {
return email;
}

public String getRole() {
return role;
}
}
15 changes: 15 additions & 0 deletions src/main/java/roomescape/auth/TokenRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package roomescape.auth;

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

public String getEmail() {
return email;
}
}
13 changes: 13 additions & 0 deletions src/main/java/roomescape/auth/TokenResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package roomescape.auth;

public record TokenResponse(
String accessToken
) {
public TokenResponse(String accessToken) {
this.accessToken = accessToken;
}

public String getAccessToken() {
return accessToken;
}
}
22 changes: 22 additions & 0 deletions src/main/java/roomescape/auth/UserResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package roomescape.auth;

public record UserResponse(
String name,
long id
) {
public UserResponse(String name, long id) {
this.name = name;
this.id = id;

}

public String getName() {
return name;

}
public long getId() {
return id;

}

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Autowired
private AuthInterceptor authInterceptor;

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

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

@Entity
public class Member {
@Id
@GeneratedValue(strategy = jakarta.persistence.GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
Expand All @@ -21,6 +28,10 @@ public Member(String name, String email, String password, String role) {
this.role = role;
}

public Member() {

}

public Long getId() {
return id;
}
Expand Down
Loading