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 MVC] 인상진 미션 제출합니다. #83

Open
wants to merge 3 commits into
base: sangjin6439
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
31 changes: 31 additions & 0 deletions src/main/java/roomescape/global/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package roomescape.global;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

패키지 구조를 global로 설정하신 이유가 있을까요?


public class LoginMember {
private Long id;
private String name;
private String email;
private String role;

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

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

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

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 jakarta.servlet.http.HttpServletRequest;
import roomescape.member.MemberResponse;
import roomescape.member.MemberService;

@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HandlerMethodArgumentResolver를 사용하는 이유는 무엇인가요? 어떤 장점이 있나요?

private 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 {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
String token = memberService.extractTokenFromCookie(request.getCookies());
MemberResponse member = memberService.findByToken(token);
return new LoginMember(member.getId(), member.getName(), member.getEmail(), "USER");
}
Comment on lines +27 to +33

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

누가 로그인을 하더라도 LoginMember의 객체에서 role 필드는 항상 "USER"인 것인가요??

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

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import roomescape.member.MemberResponse;
import roomescape.member.MemberService;

@Component
public class RoleHandler implements HandlerInterceptor {

private final MemberService memberService;

public RoleHandler(final MemberService memberService) {
this.memberService = memberService;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Cookie[] cookie =request.getCookies();
String token = memberService.extractTokenFromCookie(cookie);
MemberResponse member = memberService.findByToken(token);

if (member == null || !member.getRole().equals("ADMIN")) {
response.setStatus(401);
return false;
}

return true;
}
}
24 changes: 24 additions & 0 deletions src/main/java/roomescape/global/TokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package roomescape.global;

import org.springframework.stereotype.Component;

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

@Component
public class TokenProvider {

public static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=";

public String createToken(Member member) {
String accessToken = Jwts.builder()
.setSubject(member.getId().toString())
.claim("name", member.getName())
.claim("role", member.getRole())
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.compact();
return accessToken;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EOL(파일 마지막 줄 공백)을 지키면 좋을 거 같습니다!

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

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 LoginMemberArgumentResolver loginMemberArgumentResolver;
private final RoleHandler roleHandler;

public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, final RoleHandler roleHandler) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final RoleHandler roleHandler 여기만 한번더 final을 붙인 이유가 있을까요?

this.loginMemberArgumentResolver = loginMemberArgumentResolver;
this.roleHandler = roleHandler;
}

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

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(roleHandler)
.addPathPatterns("/admin/**");
}
Comment on lines +27 to +31

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"/admin/**"와 같은 Ant 경로 패턴 사용 굳!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ant 경로 패턴을 사용하셨는데, 이 패턴을 사용하면 어떤 단점이 있을까요?
어노테이션을 사용하는 방식과 같은 다른 방식을 고려해보셨나요?

}
17 changes: 17 additions & 0 deletions src/main/java/roomescape/member/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -25,6 +26,22 @@ public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) {
return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member);
}

@PostMapping("/login")
public ResponseEntity login(@RequestBody MemberRequest memberRequest, HttpServletResponse response) {
MemberResponse member = memberService.login(memberRequest);
String token = memberService.createToken(member);
memberService.createCookie(response, token);
return ResponseEntity.ok().build();
}

@GetMapping("/login/check")
public ResponseEntity<MemberResponse> checkLogin(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
String token = memberService.extractTokenFromCookie(cookies);
MemberResponse memberResponse = memberService.findByToken(token);
return ResponseEntity.ok(memberResponse);
}

@PostMapping("/logout")
public ResponseEntity logout(HttpServletResponse response) {
Cookie cookie = new Cookie("token", "");
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/roomescape/member/MemberDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,17 @@ public Member findByName(String name) {
name
);
}

public Member findById(final Long memberId) {
return jdbcTemplate.queryForObject(
"SELECT id, name, email, role FROM member WHERE id = ?",
(rs, rowNum) -> new Member(
rs.getLong("id"),
rs.getString("name"),
rs.getString("email"),
rs.getString("role")
),
memberId
);
}
}
8 changes: 7 additions & 1 deletion src/main/java/roomescape/member/MemberResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ public class MemberResponse {
private Long id;
private String name;
private String email;
private String role;

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

public Long getId() {
Expand All @@ -22,4 +24,8 @@ public String getName() {
public String getEmail() {
return email;
}

public String getRole() {
return role;
}
}
48 changes: 46 additions & 2 deletions src/main/java/roomescape/member/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,60 @@

import org.springframework.stereotype.Service;

import roomescape.global.TokenProvider;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;

@Service
public class MemberService {
private MemberDao memberDao;
private TokenProvider tokenProvider;

public MemberService(MemberDao memberDao) {
public MemberService(MemberDao memberDao, TokenProvider tokenProvider) {
this.memberDao = memberDao;
this.tokenProvider = tokenProvider;
}

public MemberResponse createMember(MemberRequest memberRequest) {
Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER"));
return new MemberResponse(member.getId(), member.getName(), member.getEmail());
return new MemberResponse(member.getId(), member.getName(), member.getEmail(), member.getRole());
}

public MemberResponse login(final MemberRequest memberRequest) {
Member member = memberDao.findByEmailAndPassword(memberRequest.getEmail(), memberRequest.getPassword());
return new MemberResponse(member.getId(), member.getName(), member.getEmail() , member.getRole());
}

public MemberResponse findByToken(String token) {
Long memberId = Long.valueOf(Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=".getBytes()))
.build()
.parseClaimsJws(token)
.getBody().getSubject());
Member member = memberDao.findById(memberId);
return new MemberResponse(member.getId(), member.getName(), member.getEmail(), member.getRole());
}
Comment on lines +31 to +39

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 토큰 서명을 위한 비밀 키를 상수로 빼면 더 좋을 것 같아요! (다른 코드에서는 빼셔서 말씀드려요!)


public String extractTokenFromCookie(Cookie[] cookies) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("token")) {
return cookie.getValue();
}
}
return "";
}

public String createToken(MemberResponse memberResponse){
String accessToken = tokenProvider.createToken(new Member(memberResponse.getId(), memberResponse.getName(), memberResponse.getEmail(), "USER"));
return accessToken;
}
Comment on lines +50 to +53

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

항상 만들어진 token의 role 필드 claim은 USER인건가요?


public void createCookie(final HttpServletResponse response, final String token) {
Cookie cookie = new Cookie("token", token);
cookie.setHttpOnly(true);
cookie.setPath("/");
response.addCookie(cookie);
}
}
13 changes: 5 additions & 8 deletions src/main/java/roomescape/reservation/ReservationController.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package roomescape.reservation;


import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -11,6 +12,8 @@
import java.net.URI;
import java.util.List;

import roomescape.global.LoginMember;

@RestController
public class ReservationController {

Expand All @@ -26,15 +29,9 @@ public List<ReservationResponse> list() {
}

@PostMapping("/reservations")
public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) {
if (reservationRequest.getName() == null
|| reservationRequest.getDate() == null
|| reservationRequest.getTheme() == null
|| reservationRequest.getTime() == null) {
return ResponseEntity.badRequest().build();
}
Comment on lines -29 to -35

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save 메서드를 호출할 때, 이름을 제외한 다른 필드들이 null인 경우에 대한 처리는 어디서 이루어지나요?

ReservationResponse reservation = reservationService.save(reservationRequest);
public ResponseEntity<ReservationResponse> createReservation(@RequestBody ReservationRequest reservationRequest, LoginMember member) {

ReservationResponse reservation = reservationService.save(member, reservationRequest);
return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation);
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/roomescape/reservation/ReservationRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ public class ReservationRequest {
private Long theme;
private Long time;

public ReservationRequest() {
}

public ReservationRequest(final String name, final String date, final Long theme, final Long time) {
this.name = name;
this.date = date;
this.theme = theme;
this.time = time;
}

public String getName() {
return name;
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/roomescape/reservation/ReservationResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ public class ReservationResponse {
private String date;
private String time;

public ReservationResponse() {
}

public ReservationResponse(Long id, String name, String theme, String date, String time) {
this.id = id;
this.name = name;
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/roomescape/reservation/ReservationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import java.util.List;

import roomescape.global.LoginMember;

@Service
public class ReservationService {
private ReservationDao reservationDao;
Expand All @@ -12,10 +14,19 @@ public ReservationService(ReservationDao reservationDao) {
this.reservationDao = reservationDao;
}

public ReservationResponse save(ReservationRequest reservationRequest) {
public ReservationResponse save(LoginMember member, ReservationRequest reservationRequest) {
if (reservationRequest.getName() == null) {
Reservation reservation = reservationDao.save(setReservationMemberName(member, reservationRequest));
return new ReservationResponse(reservation.getId(), reservation.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue());
}


Reservation reservation = reservationDao.save(reservationRequest);
return new ReservationResponse(reservation.getId(), reservation.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue());
}

return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue());
private ReservationRequest setReservationMemberName(LoginMember member, ReservationRequest reservationRequest) {
return new ReservationRequest(member.getName(), reservationRequest.getDate(), reservationRequest.getTheme(), reservationRequest.getTime());
}

public void deleteById(Long id) {
Expand Down
Loading