-
Notifications
You must be signed in to change notification settings - Fork 0
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
4 week mission #20
base: master
Are you sure you want to change the base?
4 week mission #20
Changes from all commits
98320e4
fffd9c6
0cd4fa0
dcf37ab
20ced64
d0439bc
c6ea3cc
b3a0d88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package com.ll.exam.ebooks.app.api.controller; | ||
|
||
import com.ll.exam.ebooks.app.base.dto.RsData; | ||
import com.ll.exam.ebooks.app.base.rq.Rq; | ||
import com.ll.exam.ebooks.app.member.entity.Member; | ||
import com.ll.exam.ebooks.app.member.form.LoginForm; | ||
import com.ll.exam.ebooks.app.member.service.MemberService; | ||
import com.ll.exam.ebooks.app.myBook.entity.MyBook; | ||
import com.ll.exam.ebooks.app.myBook.service.MyBookService; | ||
import com.ll.exam.ebooks.app.security.dto.MemberContext; | ||
import com.ll.exam.ebooks.util.Ut; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
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.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import java.util.List; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1") | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class ApiController { | ||
|
||
private final MemberService memberService; | ||
private final MyBookService myBookService; | ||
|
||
@PostMapping("/member/login") | ||
public ResponseEntity<RsData> login(@RequestBody LoginForm loginForm) { | ||
Member member = memberService.findByUsername(loginForm.getUsername()); | ||
|
||
if (member == null) { | ||
return Ut.spring.responseEntityOf(RsData.of("F-1", "일치하는 회원이 존재하지 않습니다.")); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. username, password 검증은 어디에서 이루어지나요??? |
||
String accessToken = memberService.genAccessToken(member); | ||
|
||
return Ut.spring.responseEntityOf( | ||
RsData.of( | ||
"S-1", | ||
"로그인 성공, Access Token을 발급합니다.", | ||
Ut.mapOf( | ||
"accessToken", accessToken | ||
) | ||
), | ||
Ut.spring.httpHeadersOf("Authentication", accessToken) | ||
); | ||
} | ||
|
||
@GetMapping("/member/me") | ||
public ResponseEntity<RsData> me(@AuthenticationPrincipal MemberContext memberContext) { | ||
if (memberContext == null) { // 임시코드, 나중에는 시프링 시큐리티를 이용해서 로그인을 안했다면, 아예 여기로 못 들어오도록 | ||
return Ut.spring.responseEntityOf(RsData.failOf(null)); | ||
} | ||
|
||
return Ut.spring.responseEntityOf(RsData.successOf(memberContext.getMember())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. REST API 로 설계할 때는 엔티티(member)객체를 넘겨주는 것보다 Response 용 DTO 를 만들어 요구사항에서 주어진 값들만 응답바디에 포함시키는게 좋을 것 같습니다!! |
||
} | ||
|
||
@GetMapping("/myBooks") | ||
public ResponseEntity<RsData> myBooks(@AuthenticationPrincipal MemberContext memberContext) { | ||
Member member = memberContext.getMember(); | ||
|
||
log.debug("member: " + member.getId()); | ||
|
||
if (member == null) { | ||
return Ut.spring.responseEntityOf( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다음과 같이 예외가 발생했을 때? 비즈니스 로직상 문제가 생겼을 때 소망님과 같이 바로 실패 응답 메시지를 컨트롤러에서 처리하신거 예외를 빠짐없이 잘 처리하신 것 같습니다. |
||
RsData.of( | ||
"F-1", | ||
"조회 권한이 없습니다." | ||
) | ||
); | ||
} | ||
Comment on lines
+70
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 member == null 일때를 검사하는 로직이 꼭 필요할까요??? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 이 부분 궁금합니다! 저는 혹시 몰라서 member null체크를 해주긴 했는데 승연님 말씀처럼 세션이든 토큰이든 만료가 되면 1차적으로 필터에서(필터에 세션 정보를 업데이트하는 로직도 포함시켰다는 전제 하에) 걸러주기 때문에 컨트롤러에서 member null 체크는 필요 없는지 궁금해요!! |
||
|
||
List<MyBook> myBooks = myBookService.findAllByOwnerId(member.getId()); | ||
|
||
log.debug("myBooks: " + myBooks); | ||
|
||
return Ut.spring.responseEntityOf( | ||
RsData.successOf( | ||
Ut.mapOf( | ||
"myBooks", myBooks | ||
) | ||
) | ||
); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.ll.exam.ebooks.app.base.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
|
||
@Data | ||
@AllArgsConstructor | ||
public class RsData<T> { | ||
private String resultCode; | ||
private String msg; | ||
private T data; | ||
|
||
public static <T> RsData<T> of(String resultCode, String msg, T data) { | ||
return new RsData<>(resultCode, msg, data); | ||
} | ||
|
||
public static <T> RsData<T> of(String resultCode, String msg) { | ||
return of(resultCode, msg, null); | ||
} | ||
|
||
public static <T> RsData<T> successOf(T data) { | ||
return of("S-1", "성공", data); | ||
} | ||
|
||
public static <T> RsData<T> failOf(T data) { | ||
return of("F-1", "실패", data); | ||
} | ||
|
||
public boolean isSuccess() { | ||
return resultCode.startsWith("S-1"); | ||
} | ||
|
||
public boolean isFail() { | ||
return isSuccess() == false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
import com.fasterxml.jackson.annotation.JsonIgnore; | ||
import com.ll.exam.ebooks.app.base.entity.BaseEntity; | ||
import com.ll.exam.ebooks.util.Ut; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
@@ -16,6 +17,7 @@ | |
import javax.persistence.Entity; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
@Entity | ||
@Getter | ||
|
@@ -36,10 +38,13 @@ public class Member extends BaseEntity { | |
|
||
private String nickname; | ||
|
||
@JsonIgnore | ||
private int authLevel; | ||
|
||
@JsonIgnore | ||
private long restCash; // 예치금 | ||
|
||
@JsonIgnore | ||
Comment on lines
+41
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 JsonIgnore 을 붙여 JSON 응답에 포함하지 않도록 하셨군요! |
||
public String getName() { | ||
if (nickname == null) { | ||
return username; | ||
|
@@ -51,6 +56,7 @@ public Member(long id) { | |
super(id); | ||
} | ||
|
||
@JsonIgnore | ||
public List<GrantedAuthority> getAuthorities() { | ||
List<GrantedAuthority> authorities = new ArrayList<>(); | ||
authorities.add(new SimpleGrantedAuthority("MEMBER")); | ||
|
@@ -65,4 +71,17 @@ public List<GrantedAuthority> getAuthorities() { | |
|
||
return authorities; | ||
} | ||
|
||
@JsonIgnore | ||
public Map<String, Object> getAccessTokenClaims() { | ||
return Ut.mapOf( | ||
"id", getId(), | ||
"createDate", getCreateDate(), | ||
"modifyDate", getModifyDate(), | ||
"username", getUsername(), | ||
"email", getEmail(), | ||
"authLevel", getAuthLevel(), | ||
"authorities", getAuthorities() | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.ll.exam.ebooks.app.member.form; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
import javax.validation.constraints.NotEmpty; | ||
|
||
@Data | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class LoginForm { | ||
@NotEmpty | ||
private String username; | ||
|
||
@NotEmpty | ||
private String password; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
import com.ll.exam.ebooks.app.member.exception.MemberNotFoundException; | ||
import com.ll.exam.ebooks.app.member.repository.MemberRepository; | ||
import com.ll.exam.ebooks.app.security.dto.MemberContext; | ||
import com.ll.exam.ebooks.app.security.jwt.JwtProvider; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.context.SecurityContext; | ||
|
@@ -28,6 +29,7 @@ public class MemberService { | |
private final PasswordEncoder passwordEncoder; | ||
private final MailService mailService; | ||
private final CashService cashService; | ||
private final JwtProvider jwtProvider; | ||
|
||
|
||
@Transactional | ||
|
@@ -74,6 +76,10 @@ public Member adminJoin(JoinForm joinForm) { | |
return member; | ||
} | ||
|
||
public String genAccessToken(Member member) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. jwt 관련 코드를 한 곳에 모으면 좋을 것 같다?라는 생각이 들었습니다!! |
||
return jwtProvider.generatedAccessToken(member.getAccessTokenClaims(), 60 * 60 * 24 * 90); | ||
} | ||
|
||
public Member findByEmail(String email) { | ||
return memberRepository.findByEmail(email).orElse(null); | ||
} | ||
|
@@ -132,7 +138,7 @@ public boolean beAuthor(Member member, String nickname) { | |
} | ||
|
||
private void forceAuthentication(Member member) { | ||
MemberContext memberContext = new MemberContext(member, member.getAuthorities()); | ||
MemberContext memberContext = new MemberContext(member); | ||
|
||
UsernamePasswordAuthenticationToken authentication = | ||
UsernamePasswordAuthenticationToken.authenticated( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package com.ll.exam.ebooks.app.security.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import com.ll.exam.ebooks.app.member.entity.Member; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
@@ -8,27 +9,28 @@ | |
|
||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
@Getter | ||
@JsonIgnoreProperties({"id", "createDate", "modifyDate", "username", "email", "nickname"}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분을 추가하신 이유가 궁금합니다! |
||
public class MemberContext extends User { | ||
private final Long id; | ||
private final String username; | ||
@Setter | ||
private String email; | ||
@Setter | ||
private String nickname; | ||
private final String email; | ||
private final String nickname; | ||
private final LocalDateTime createDate; | ||
@Setter | ||
private LocalDateTime modifyDate; | ||
|
||
public MemberContext(Member member, List<GrantedAuthority> authorities) { | ||
super(member.getUsername(), member.getPassword(), authorities); | ||
this.id = member.getId(); | ||
this.username = member.getUsername(); | ||
this.email = member.getEmail(); | ||
this.nickname = member.getNickname(); | ||
this.createDate = member.getCreateDate(); | ||
this.modifyDate = member.getModifyDate(); | ||
private final LocalDateTime modifyDate; | ||
|
||
public MemberContext(Member member) { | ||
super(member.getUsername(), "", member.getAuthorities()); | ||
|
||
id = member.getId(); | ||
username = member.getUsername(); | ||
email = member.getEmail(); | ||
nickname = member.getNickname(); | ||
createDate = member.getCreateDate(); | ||
modifyDate = member.getModifyDate(); | ||
} | ||
|
||
public Member getMember() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ApiController 를 member, myBooks 로 분리하는게 어떨까요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동의합니다!