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] 한상우 미션 제출합니다. #29

Open
wants to merge 2 commits into
base: sangu1026
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 build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ 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.projectlombok:lombok'


implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0'

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

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
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 roomescape.infrastructure.JwtTokenDecoder;
import roomescape.infrastructure.TokenExtractor;
import roomescape.member.Member;
import roomescape.member.MemberService;

@Component
public class AdminHandlerInterceptor implements HandlerInterceptor {
private MemberService memberService;

public AdminHandlerInterceptor(MemberService memberService) {
this.memberService = memberService;
}

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

String token = TokenExtractor.extractTokenFromCookie(request.getCookies());
Long id = JwtTokenDecoder.decodeToken(token);
Member member = memberService.findById(id);

if (member == null || !member.getRole().equals("ADMIN")) {
response.setStatus(401);
return false;
}
Comment on lines +30 to +33

Choose a reason for hiding this comment

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

member가 존재하지 않는 것에 대해서도 같은 에러처리를 하셨네요! 혹시 이유가 따로 있을까요??


return true;
}


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

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
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 roomescape.infrastructure.JwtTokenDecoder;
import roomescape.infrastructure.TokenExtractor;
import roomescape.member.LoginMember;
import roomescape.member.Member;
import roomescape.member.MemberService;

@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

private MemberService memberService;

public LoginMemberArgumentResolver(MemberService memberService) {
this.memberService = memberService;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(LoginMember.class);
// return Member.class.isAssignableFrom(parameter.getParameterType());
}

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

Choose a reason for hiding this comment

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

Suggested change
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {

받는 파라미터의 길이가 길고 개수가 4개나 되니 이렇게 수정하면 가독성이 더 좋지 않을까요??

HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Cookie[] cookies = request.getCookies();
String token = TokenExtractor.extractTokenFromCookie(cookies);
if (token == null) return null;

Long id = JwtTokenDecoder.decodeToken(token);
Member member = memberService.findById(id);
return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getPassword());
}


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

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {
private final LoginMemberArgumentResolver loginMemberArgumentResolver;
private final AdminHandlerInterceptor adminHandlerInterceptor;


public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, AdminHandlerInterceptor adminHandlerInterceptor) {
this.loginMemberArgumentResolver = loginMemberArgumentResolver;
this.adminHandlerInterceptor = adminHandlerInterceptor;
}

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

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminHandlerInterceptor)
.addPathPatterns("/admin");
Comment on lines +30 to +31

Choose a reason for hiding this comment

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

저는 resolvers.add(loginMemberArgumentResolver);로만 코드를 작성했는데
.addPathPatterns("/admin"); 하게 될 경우 어떤 차이가 발생하는지 설명해주실 수 있을까요?

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

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

public class JwtTokenDecoder {
public static Long decodeToken(String token) {
Long memberId = Long.valueOf(Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=".getBytes()))
.build()
.parseClaimsJws(token)
.getBody().getSubject());
return memberId;
}
}
24 changes: 24 additions & 0 deletions src/main/java/roomescape/infrastructure/JwtTokenProvider.java

Choose a reason for hiding this comment

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

JwtTokenDecoder, JwtTokenEnoder, TokenExtractor 를 분리해서 각각 하는 역할을 다르게 객체를 생성하셨네요!

혹시 Util 이라는 단어에 대해서 아실까요??
https://kong-dev.tistory.com/229
위의 사이트를 참고해서 간단하게 얘기 드리자면
"utils 패키지 내에 있는 구현들은 최대한 단순하고, 변경가능성이 낮고, 또한 도메인 로직과는 관계가 없어야 한다." 가 핵심이 되겠네요!

하나로 구현하는건 어떨지 한번 같이 이야기 해보고 싶어서 리뷰 남깁니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package roomescape.infrastructure;


import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import roomescape.member.Member;

@Component
public class JwtTokenProvider {
@Value("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=")
private String secretKey;

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

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

import jakarta.servlet.http.Cookie;

public class TokenExtractor {

public static String extractTokenFromCookie(Cookie[] cookies) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("token")) {
return cookie.getValue();
}
}
return "";
}
}
30 changes: 30 additions & 0 deletions src/main/java/roomescape/member/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package roomescape.member;
public class LoginMember{
private Long id;
private String name;
private String email;
private String password;

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

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

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

import lombok.Getter;


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

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

public String getEmail() {
return email;
}

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

public class LoginResponse {
private String name;

public LoginResponse(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
22 changes: 22 additions & 0 deletions src/main/java/roomescape/member/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import roomescape.infrastructure.TokenExtractor;

import java.net.URI;

Expand All @@ -25,6 +26,27 @@ public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) {
return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member);
}

@PostMapping("/login")
public ResponseEntity login (@RequestBody LoginRequest loginRequest, HttpServletResponse response) {
String token = memberService.login(loginRequest);

Cookie cookie = new Cookie("token", token);
cookie.setHttpOnly(true);
cookie.setPath("/");
response.addCookie(cookie);

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

@GetMapping("login/check")
public ResponseEntity checkLogin(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
String token = TokenExtractor.extractTokenFromCookie(cookies);
LoginResponse loginResponse= memberService.checkLogin(token);
return ResponseEntity.ok(loginResponse);
}


@PostMapping("/logout")
public ResponseEntity logout(HttpServletResponse response) {
Cookie cookie = new Cookie("token", "");
Expand Down
12 changes: 12 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,16 @@ public Member findByName(String name) {
name
);
}
public Member findById(Long id) {
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")
),
id
);
}
}
27 changes: 26 additions & 1 deletion src/main/java/roomescape/member/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
package roomescape.member;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;
import roomescape.infrastructure.JwtTokenDecoder;
import roomescape.infrastructure.JwtTokenProvider;

@Service
public class MemberService {
private MemberDao memberDao;
private JwtTokenProvider jwtTokenProvider;

public MemberService(MemberDao memberDao) {
public MemberService(MemberDao memberDao,JwtTokenProvider jwtTokenProvider) {
this.memberDao = memberDao;
this.jwtTokenProvider = jwtTokenProvider;
}
Comment on lines 11 to 17

Choose a reason for hiding this comment

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

@Autowired 어노테이션을 사용해보면 어떨까요?

저도 해당 어노테이션을 사용해보면 좋을것 같다는 리뷰를 받아서 말씀드려요😊


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());
}

public String login(LoginRequest loginRequest){
Member member = memberDao.findByEmailAndPassword(loginRequest.getEmail(),loginRequest.getPassword());

String accessToken = jwtTokenProvider.createToken(member);

return accessToken;
}

public LoginResponse checkLogin (String token) {

Long memberId = JwtTokenDecoder.decodeToken(token);
Member member = memberDao.findById(memberId);

return new LoginResponse(member.getName());
}
public Member findById(Long id) {
return memberDao.findById(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import roomescape.member.LoginMember;

import java.net.URI;
import java.util.List;
Expand All @@ -25,15 +26,15 @@ public List<ReservationResponse> list() {
return reservationService.findAll();
}


@PostMapping("/reservations")
public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) {
if (reservationRequest.getName() == null
|| reservationRequest.getDate() == null
public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, LoginMember member) {
if (reservationRequest.getDate() == null
|| reservationRequest.getTheme() == null
|| reservationRequest.getTime() == null) {
return ResponseEntity.badRequest().build();
}
ReservationResponse reservation = reservationService.save(reservationRequest);
ReservationResponse reservation = reservationService.save(reservationRequest, member);

return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation);
}
Expand Down
4 changes: 4 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,10 @@ public class ReservationRequest {
private Long theme;
private Long time;

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}
Expand Down
Loading