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

🐷 step3 : 즐겨찾기 κΈ°λŠ₯ κ΅¬ν˜„ #241

Open
wants to merge 10 commits into
base: minyul
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
13 changes: 6 additions & 7 deletions src/main/java/nextstep/auth/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package nextstep.auth;

import com.fasterxml.jackson.databind.ObjectMapper;
import nextstep.auth.authentication.Authorizor;
import nextstep.auth.authentication.Authorizer;
import nextstep.auth.authentication.SessionAuthenticationInterceptor;
import nextstep.auth.authentication.TokenAuthenticationInterceptor;
import nextstep.auth.authorization.AuthenticationPrincipalArgumentResolver;
import nextstep.auth.authorization.SessionSecurityContextPersistenceInterceptor;
import nextstep.auth.authorization.TokenSecurityContextPersistenceInterceptor;
import nextstep.auth.token.JwtTokenProvider;
import nextstep.member.application.CustomUserDetailsService;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
Expand All @@ -20,19 +19,19 @@ public class AuthConfig implements WebMvcConfigurer {
private UserDetailsService userDetailsService;
private JwtTokenProvider jwtTokenProvider;
private ObjectMapper objectMapper;
private Authorizor authorizor;
private Authorizer authorizor;

public AuthConfig(UserDetailsService userDetailsService, JwtTokenProvider jwtTokenProvider) {
public AuthConfig(UserDetailsService userDetailsService, JwtTokenProvider jwtTokenProvider, ObjectMapper objectMapper) {
this.userDetailsService = userDetailsService;
this.jwtTokenProvider = jwtTokenProvider;
this.objectMapper = new ObjectMapper();
this.authorizor = new Authorizor();
this.objectMapper = objectMapper;
this.authorizor = new Authorizer(userDetailsService);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SessionAuthenticationInterceptor(userDetailsService, authorizor)).addPathPatterns("/login/session");
registry.addInterceptor(new TokenAuthenticationInterceptor(userDetailsService, jwtTokenProvider, objectMapper, authorizor))
registry.addInterceptor(new TokenAuthenticationInterceptor(jwtTokenProvider, objectMapper, authorizor))
.addPathPatterns("/login/token");
registry.addInterceptor(new SessionSecurityContextPersistenceInterceptor());
registry.addInterceptor(new TokenSecurityContextPersistenceInterceptor(jwtTokenProvider));
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/nextstep/auth/authentication/Authorizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package nextstep.auth.authentication;

import nextstep.auth.User;
import nextstep.auth.UserDetailsService;
import nextstep.auth.context.Authentication;
import org.springframework.util.ObjectUtils;

public class Authorizer {

private UserDetailsService userDetailsService;

public Authorizer(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}

public Authentication authenticate(AuthenticationToken authenticationToken) {
final String principal = authenticationToken.getPrincipal();
final User userDetails = userDetailsService.loadUserByUsername(principal);
checkAuthentication(userDetails, authenticationToken);

return new Authentication(userDetails);
}

private void checkAuthentication(User userDetails, AuthenticationToken token) {
if (ObjectUtils.isEmpty(userDetails)) {
throw new AuthenticationException();
}

if (!userDetails.checkPassword(token.getCredentials())) {
throw new AuthenticationException();

Choose a reason for hiding this comment

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

AuthenticationException을 λ˜μ§€λ©΄ 401 UNAUTHORIZEDκ°€ μ‘λ‹΅λ©λ‹ˆλ‹€ :)
AuthenticationExceptionν΄λž˜μŠ€μ— 보면 @ResponseStatus(HttpStatus.UNAUTHORIZED) μ–΄λ…Έν…Œμ΄μ…˜μ΄ λΆ™μ–΄μžˆκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€!

Choose a reason for hiding this comment

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

AuthenticationPrincipalArgumentResolver의 resolveArgumentμ—μ„œ authentication 이 null인 κ²½μš°λŠ” 둜그인이 μ•ˆλœ μΌ€μ΄μŠ€λ‘œ λ³Ό 수 μžˆκ² λ„€μš” :)

}
}
}
17 changes: 0 additions & 17 deletions src/main/java/nextstep/auth/authentication/Authorizor.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package nextstep.auth.authentication;

import nextstep.auth.User;
import nextstep.auth.UserDetailsService;
import nextstep.auth.context.Authentication;
import nextstep.auth.context.SecurityContext;
Expand All @@ -20,32 +19,24 @@ public class SessionAuthenticationInterceptor implements HandlerInterceptor, Aut
public static final String PASSWORD_FIELD = "password";

private UserDetailsService userDetailsService;
private Authorizor authorizor;
private Authorizer authorizor;

public SessionAuthenticationInterceptor(UserDetailsService userDetailsService, Authorizor authorizor) {
public SessionAuthenticationInterceptor(UserDetailsService userDetailsService, Authorizer authorizor) {
this.userDetailsService = userDetailsService;
this.authorizor = authorizor;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
AuthenticationToken token = convert(request);
Authentication authentication = authenticate(token);
Authentication authentication = authorizor.authenticate(token);

HttpSession httpSession = request.getSession();
httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, new SecurityContext(authentication));
response.setStatus(HttpServletResponse.SC_OK);
return false;
}

public Authentication authenticate(AuthenticationToken token) {
String principal = token.getPrincipal();
User userDetails = userDetailsService.loadUserByUsername(principal);
authorizor.checkAuthentication(userDetails, token);

return new Authentication(userDetails);
}

@Override
public AuthenticationToken convert(HttpServletRequest request) {
Map<String, String[]> paramMap = request.getParameterMap();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package nextstep.auth.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import nextstep.auth.User;
import nextstep.auth.UserDetailsService;
import nextstep.auth.context.Authentication;
import nextstep.auth.token.JwtTokenProvider;
Expand All @@ -16,16 +15,13 @@

public class TokenAuthenticationInterceptor implements HandlerInterceptor, AuthenticationConverter {

private UserDetailsService userDetailsService;
private JwtTokenProvider jwtTokenProvider;
private ObjectMapper objectMapper;
private Authorizor authorizor;
private Authorizer authorizor;

public TokenAuthenticationInterceptor(UserDetailsService userDetailsService,
JwtTokenProvider jwtTokenProvider,
public TokenAuthenticationInterceptor(JwtTokenProvider jwtTokenProvider,
ObjectMapper objectMapper,
Authorizor authorizor) {
this.userDetailsService = userDetailsService;
Authorizer authorizor) {
this.jwtTokenProvider = jwtTokenProvider;
this.objectMapper = objectMapper;
this.authorizor = authorizor;
Expand All @@ -34,7 +30,7 @@ public TokenAuthenticationInterceptor(UserDetailsService userDetailsService,
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
AuthenticationToken authenticationToken = convert(request);
Authentication authentication = authenticate(authenticationToken);
Authentication authentication = authorizor.authenticate(authenticationToken);

final String payload = objectMapper.writeValueAsString(authentication.getPrincipal());
final String token = jwtTokenProvider.createToken(payload);
Expand All @@ -49,14 +45,6 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
return false;
}

public Authentication authenticate(AuthenticationToken authenticationToken) {
final String principal = authenticationToken.getPrincipal();
final User userDetails = userDetailsService.loadUserByUsername(principal);
authorizor.checkAuthentication(userDetails, authenticationToken);

return new Authentication(userDetails);
}

@Override
public AuthenticationToken convert(HttpServletRequest request) throws IOException {
TokenRequest tokenRequest = objectMapper.readValue(request.getInputStream(), TokenRequest.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package nextstep.auth.authorization;

import nextstep.auth.authentication.AuthenticationException;
import nextstep.auth.context.Authentication;
import nextstep.auth.context.SecurityContextHolder;
import org.springframework.core.MethodParameter;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
Expand All @@ -20,13 +22,20 @@ public boolean supportsParameter(MethodParameter parameter) {
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authenticationValidate(authentication);
if (authentication.getPrincipal() instanceof Map) {
return extractPrincipal(parameter, authentication);
}

return authentication.getPrincipal();
}

private void authenticationValidate(Authentication authentication) {
if (ObjectUtils.isEmpty(authentication)) {
throw new AuthenticationException();
}
Comment on lines +34 to +36

Choose a reason for hiding this comment

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

πŸ‘

}

private Object extractPrincipal(MethodParameter parameter, Authentication authentication) {
try {
Map<String, String> principal = (Map) authentication.getPrincipal();
Expand Down
45 changes: 41 additions & 4 deletions src/main/java/nextstep/member/application/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
package nextstep.member.application;

import nextstep.member.application.dto.FavoriteRequest;
import nextstep.member.application.dto.FavoriteResponse;
import nextstep.member.application.dto.MemberRequest;
import nextstep.member.application.dto.MemberResponse;
import nextstep.member.domain.LoginMember;
import nextstep.member.domain.Member;
import nextstep.member.domain.MemberRepository;
import nextstep.member.domain.*;
import nextstep.subway.applicaion.StationService;
import nextstep.subway.domain.Station;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class MemberService {

private MemberRepository memberRepository;
private FavoriteRepository favoriteRepository;
private StationService stationService;

public MemberService(MemberRepository memberRepository) {
public MemberService(MemberRepository memberRepository,
FavoriteRepository favoriteRepository,
StationService stationService) {
this.memberRepository = memberRepository;
this.favoriteRepository = favoriteRepository;
this.stationService = stationService;
}

public MemberResponse createMember(MemberRequest request) {
Expand Down Expand Up @@ -53,4 +65,29 @@ public void deleteMemberOfMine(LoginMember loginMember) {
Member member = memberRepository.findByEmail(loginMember.getEmail()).orElseThrow(RuntimeException::new);
memberRepository.delete(member);
}

public FavoriteResponse createFavorite(LoginMember loginMember, FavoriteRequest favoriteRequest) {
Member member = memberRepository.findById(loginMember.getId()).orElseThrow(RuntimeException::new);
Station source = stationService.findById(favoriteRequest.getSource());
Station target = stationService.findById(favoriteRequest.getTarget());
Favorite favorite = new Favorite(member.getId(), source, target);
favoriteRepository.save(favorite);

return FavoriteResponse.of(favorite);
}

public List<FavoriteResponse> findFavoritesOfMine(LoginMember loginMember) {
Member member = memberRepository.findById(loginMember.getId()).orElseThrow(RuntimeException::new);
List<Favorite> favorites = favoriteRepository.findByMember(member.getId());

return favorites.stream()
.map(FavoriteResponse::of)
.collect(Collectors.toList());
}

public void deleteFavorite(LoginMember loginMember, Long favoriteId) {
Favorite favorite = favoriteRepository.findById(favoriteId).orElseThrow(RuntimeException::new);

favoriteRepository.delete(favorite);
}
}
15 changes: 15 additions & 0 deletions src/main/java/nextstep/member/application/dto/FavoriteRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package nextstep.member.application.dto;

public class FavoriteRequest {

private Long source;
private Long target;

public Long getSource() {
return source;
}

public Long getTarget() {
return target;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package nextstep.member.application.dto;

import nextstep.member.domain.Favorite;
import nextstep.subway.applicaion.dto.StationResponse;

public class FavoriteResponse {

private Long id;
private StationResponse source;
private StationResponse target;

public static FavoriteResponse of(Favorite favorite) {

return new FavoriteResponse(
favorite.getId(),
StationResponse.of(favorite.getSource()),
StationResponse.of(favorite.getTarget()));
}

public FavoriteResponse(Long id, StationResponse source, StationResponse target) {
this.id = id;
this.source = source;
this.target = target;
}

public Long getId() {
return id;
}

public StationResponse getSource() {
return source;
}

public StationResponse getTarget() {
return target;
}
}
48 changes: 48 additions & 0 deletions src/main/java/nextstep/member/domain/Favorite.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nextstep.member.domain;

import nextstep.subway.domain.Station;

import javax.persistence.*;

@Entity
public class Favorite {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@JoinColumn(name = "member_id")
private Long member;

Choose a reason for hiding this comment

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

ν•„λ“œλͺ…이 memberId둜 ν•˜λŠ”κ²Œ μ ν•©ν•˜μ§€ μ•Šμ„κΉŒμš”?

Choose a reason for hiding this comment

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

@minyul

κ³Όκ±° μ‹ μž… ν”„λ‘œμ νŠΈλ‘œ 둜그인μͺ½μ„ κ΅¬ν˜„ν•˜κ³  μ½”λ“œλ¦¬λ·°λ₯Ό λ°›μ•˜μ„ λ•Œ, μ‚¬μš©μžμ™€ κ΄€λ ¨λœ 뢀뢄은 직접참쑰, μ•„λ‹Œ κ²½μš°μ—λŠ” κ°„μ ‘ μ°Έμ‘°λ₯Ό ν•˜λΌκ³  ν”Όλ“œλ°±μ„ λ°›μ•˜μ—ˆλŠ”λ°μš”! μ–΄λ–»κ²Œ μƒκ°ν•˜μ‹œλ‚˜μš”?

μ‹ μž… ν”„λ‘œμ νŠΈμ—μ„œ μ–΄λ–€ μƒν™©μ΄μ—ˆλŠ” μ§€λŠ” μ œκ°€ μ •ν™•νžˆ μ•Œ 지 λͺ»ν•˜μ—¬μ„œ μ •ν™•νžˆ λ‹΅λ³€λ“œλ¦¬μ§€ λͺ»ν•˜κ² λ„€μš”.
ν•˜μ§€λ§Œ μ•„λ§ˆ ν”Όλ“œλ°± μ£Όμ‹  μ‚¬μˆ˜λΆ„μ˜ μ˜λ„κ°€ 항상 μ‚¬μš©μžκ°€ κ΄€λ ¨λœ 뢀뢄은 무쑰건 μ§μ ‘μ°Έμ‘°ν•˜κ³ , 그게 μ•„λ‹ˆλΌλ©΄ κ°„μ ‘ 참쑰해라. μš”λŸ°μ˜λ―ΈλŠ” μ•„λ‹ˆμ—ˆμ„ 것 κ°™μ•„μš”.

μœ„μ—λ„ λ§μ”€λ“œλ Έλ‹€μ‹œν”Ό 직접참쑰, κ°„μ ‘μ°Έμ‘°λŠ” 각자 μž₯단점이 μžˆμŠ΅λ‹ˆλ‹€. μ € 같은 κ²½μš°λŠ” μ‚¬μš©μžμ™€ κ΄€λ ¨λœ μ—”ν‹°ν‹°μ—μ„œ λ‹€λ₯Έ 엔티티와 μ§μ ‘μ°Έμ‘°ν•˜λ©΄ λ°”λ‘œ 데이터λ₯Ό κ°€μ Έμ™€μ„œ μ‚¬μš©ν•  수 μžˆλŠ” μž₯점 λŒ€λΉ„ λ‹€λ₯Έ 엔티티와 결합도가 λ†’μ•„μ§€λŠ” 문제, N+1이 생길 μœ„ν—˜μ„ 항상 μ‹ κ²½μ¨μ•Όν•˜λŠ” λ¬Έμ œκ°€ 더 크닀고 μƒκ°ν•΄μ„œ κ°„μ ‘μ°Έμ‘°ν•˜κ±°λ‚˜, μƒλŒ€λ°© μ—”ν‹°ν‹° μͺ½μ—μ„œ Memberμͺ½μœΌλ‘œ 단방ν–₯ 연관관계λ₯Ό 맺도둝 ν•˜κΈ°λ₯Ό μ„ ν˜Έν•˜λŠ” νŽΈμž…λ‹ˆλ‹€ πŸ˜…


@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "up_station_id")
private Station source;

@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "down_station_id")
private Station target;
Comment on lines +17 to +23

Choose a reason for hiding this comment

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

cascadeλŠ” 없이 ν•˜λŠ”κ²Œ 더 쒋을 것 κ°™μ•„μš”.
Station의 라이프 사이클이 Favorite에 쒅속될 μ΄μœ κ°€ 없을 것 κ°™μŠ΅λ‹ˆλ‹€!
ManyToOne의 경우 N+1을 ν”Όν•˜κΈ° μœ„ν•΄ fetchType을 LAZY둜 μ„€μ •ν•˜μ‹œκ³  fetch join을 μ‚¬μš©ν•΄λ³΄μ‹œλ©΄ 쒋을 것 κ°™μ•„μš”!

Choose a reason for hiding this comment

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

@minyul

음.. ꡬ간(Section) 에 μ—­ 2개 (Upstation, DownStation) 이 있으며
즐겨찾기(Favorite)에 μ—­ 2개 (Source, Target) 이 μžˆλ‹€.
κ²°κ΅­μ—” κ°™μ§€λ§Œ μ™œ @manytoone이지? λΌλŠ” 의문이 살아지지가 μ•Šμ•„μ„œμš”.γ… γ… γ… γ… γ… 

μš” 질문의 μš”μ§€λ₯Ό μ œκ°€ 잘 μ΄ν•΄ν•˜μ§€ λͺ»ν•˜κ² μŠ΅λ‹ˆλ‹€....... 😭
Section의 upStation, downStation은 ν•œ μ„Ήμ…˜μ˜ 상행역과 λ°”λ‘œ λ‹€μŒ ν•˜ν–‰μ—­μ„ λ§ν•˜λŠ” 것이고,
Favorite은 source(μΆœλ°œμ§€), target(도착지)λΌμ„œ λ‹¨μˆœνžˆ 역을 λ‘κ°œ 가지고 μžˆλ‹€λŠ” κ²ƒλ§Œ 같을 뿐 μ „ν˜€ λ‹€λ₯Έ κ΄€κ³„λΌμ„œμš”....
ν˜Ήμ‹œ μš”κ±΄ λ”°λ‘œ λ””μ— μ΄λ‚˜ μΆ”κ°€ λŒ“κΈ€λ‘œ μ§ˆμ˜μ£Όμ‹œλ©΄ λ‹΅λ³€λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€!


protected Favorite() {}

public Favorite(Long memberId, Station source, Station target) {
this.member = memberId;
this.source = source;
this.target = target;
}

public Long getId() {
return id;
}

public Long getMember() {
return member;
}

public Station getSource() {
return source;
}

public Station getTarget() {
return target;
}
}
Loading