-
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
[4Week_Mission] 한승연 - Jwt-Security 로그인 구현, REST API 개발, SpringDoc #38
Changes from all commits
d31c078
295851c
60aa1ce
96bf825
058a935
a06a89d
fc972c3
330e0f3
53ea4b8
7e4ffe9
0dd7bab
4c258b7
886bdf2
8523f92
c26d357
1e1c454
cef4d55
260b27e
0e92a77
45af459
11034d5
976e7dc
b2e8bfc
24e0b94
c23e7a8
033f34e
9c18dbe
50fe75f
cc25bf4
7b5090d
9ea8927
30980c6
67d55ee
ad7198d
b6b1040
1ccb91f
2b18a0a
588b51b
d6c753e
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,70 @@ | ||
package com.example.mutbooks.app.api.member.controller; | ||
|
||
import com.example.mutbooks.app.api.member.dto.response.MemberDto; | ||
import com.example.mutbooks.app.base.dto.RsData; | ||
import com.example.mutbooks.app.api.member.dto.request.LoginDto; | ||
import com.example.mutbooks.app.member.entity.Member; | ||
import com.example.mutbooks.app.member.service.MemberService; | ||
import com.example.mutbooks.app.security.dto.MemberContext; | ||
import com.example.mutbooks.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.security.crypto.password.PasswordEncoder; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/member") | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class MemberApiController { | ||
private final MemberService memberService; | ||
private final PasswordEncoder passwordEncoder; | ||
|
||
@PostMapping("/login") | ||
public ResponseEntity<RsData> login(@RequestBody LoginDto loginDto) { | ||
log.info("로그인"); | ||
// 입력 데이터 유효성 검증 | ||
if(loginDto.isNotValid()) { | ||
return Ut.spring.responseEntityOf(RsData.of("F-1", "로그인 정보가 올바르지 않습니다..")); | ||
} | ||
|
||
Member member = memberService.findByUsername(loginDto.getUsername()); | ||
// 1. 존재하지 않는 회원 | ||
if(member == null) { | ||
log.info("존재하지 않는 회원"); | ||
return Ut.spring.responseEntityOf(RsData.of("F-2", "일치하는 회원이 존재하지 않습니다.")); | ||
} | ||
// 2. 올바르지 않은 비밀번호 | ||
// matches(비밀번호 원문, 암호화된 비밀번호) | ||
if(!passwordEncoder.matches(loginDto.getPassword(), member.getPassword())) { | ||
log.info("비밀번호 틀림"); | ||
return Ut.spring.responseEntityOf(RsData.of("F-3", "비밀번호가 일치하지 않습니다.")); | ||
} | ||
|
||
log.debug("Ut.json.toStr(member.getAccessTokenClaims()) : " + Ut.json.toStr(member.getAccessTokenClaims())); | ||
// accessToken 발급 | ||
String accessToken = memberService.genAccessToken(member); | ||
// 응답 헤더, 바디에 accessToken 담기 | ||
return Ut.spring.responseEntityOf( | ||
RsData.of( | ||
"S-1", | ||
"로그인 성공, Access Token을 발급합니다.", | ||
Ut.mapOf("accessToken", accessToken) | ||
), | ||
Ut.spring.httpHeadersOf("Authentication", accessToken) | ||
); | ||
} | ||
|
||
// 회원 정보 | ||
@GetMapping("/me") | ||
public ResponseEntity<RsData> test(@AuthenticationPrincipal MemberContext memberContext) { | ||
if(memberContext == null) { | ||
return Ut.spring.responseEntityOf(RsData.failOf(null)); | ||
} | ||
MemberDto memberDto = MemberDto.toDto(memberContext.getMember()); | ||
|
||
return Ut.spring.responseEntityOf(RsData.successOf(Ut.mapOf("member", memberDto))); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.example.mutbooks.app.api.member.dto.request; | ||
|
||
import lombok.Data; | ||
|
||
import javax.validation.constraints.NotBlank; | ||
|
||
@Data | ||
public class LoginDto { | ||
@NotBlank(message = "username 을(를) 입력해주세요.") | ||
private String username; | ||
@NotBlank(message = "password 을(를) 입력해주세요.") | ||
private String password; | ||
|
||
public boolean isNotValid() { | ||
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. 제가 알기론 dto에는 Getter, Setter를 제외하고 다른 로직이 포함되면 안되는 것로 알고 있습니다. |
||
return username == null || password == null || username.trim().length() == 0 || password.trim().length() == 0; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.example.mutbooks.app.api.member.dto.response; | ||
|
||
import com.example.mutbooks.app.member.entity.Member; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
@Getter | ||
@Setter | ||
@Builder | ||
@AllArgsConstructor | ||
public class MemberDto { | ||
private Long id; | ||
private LocalDateTime createDate; | ||
private LocalDateTime modifyDate; | ||
private String username; | ||
private String email; | ||
private boolean emailVerified; | ||
private String nickname; | ||
|
||
public static MemberDto toDto(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. 해당 static 메서드를 통해서 엔티티와 Dto 매핑을 잘 해주신 것 같습니다! |
||
return MemberDto.builder() | ||
.id(member.getId()) | ||
.createDate(member.getCreateDate()) | ||
.modifyDate(member.getUpdateDate()) | ||
.username(member.getUsername()) | ||
.email(member.getEmail()) | ||
.nickname(member.getNickname()) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package com.example.mutbooks.app.api.myBooks; | ||
|
||
import com.example.mutbooks.app.base.dto.RsData; | ||
import com.example.mutbooks.app.member.entity.Member; | ||
import com.example.mutbooks.app.mybook.dto.response.MyBookDetailDto; | ||
import com.example.mutbooks.app.mybook.dto.response.MyBookDto; | ||
import com.example.mutbooks.app.mybook.service.MyBookService; | ||
import com.example.mutbooks.app.security.dto.MemberContext; | ||
import com.example.mutbooks.util.Ut; | ||
import lombok.RequiredArgsConstructor; | ||
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.PathVariable; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import java.util.List; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/myBooks") | ||
@RequiredArgsConstructor | ||
public class MyBooksApiController { | ||
private final MyBookService myBookService; | ||
|
||
// 내 도서 리스트 | ||
@GetMapping("") | ||
public ResponseEntity<RsData> list(@AuthenticationPrincipal MemberContext memberContext) { | ||
Member member = memberContext.getMember(); | ||
List<MyBookDto> myBookDtos = myBookService.findAllByOwner(member); | ||
|
||
return Ut.spring.responseEntityOf( | ||
RsData.successOf(Ut.mapOf("myBooks", myBookDtos)) | ||
); | ||
} | ||
|
||
// 도서 상세 조회 | ||
@GetMapping("/{myBookId}") | ||
public ResponseEntity<RsData> detail(@PathVariable long myBookId, @AuthenticationPrincipal MemberContext memberContext) { | ||
MyBookDetailDto myBookDetailDto = myBookService.findByIdForDetail(myBookId, memberContext.getId()); | ||
|
||
return Ut.spring.responseEntityOf( | ||
RsData.successOf(Ut.mapOf("myBook", myBookDetailDto)) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.example.mutbooks.app.base; | ||
|
||
import io.swagger.v3.oas.models.ExternalDocumentation; | ||
import io.swagger.v3.oas.models.OpenAPI; | ||
import io.swagger.v3.oas.models.info.Info; | ||
import io.swagger.v3.oas.models.info.License; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class SpringDocConfig { | ||
@Bean | ||
public OpenAPI springShopOpenAPI() { | ||
return new OpenAPI() | ||
.info(new Info().title("SpringShop API") | ||
.description("Spring shop sample application") | ||
.version("v0.0.1") | ||
.license(new License().name("Apache 2.0").url("http://springdoc.org"))) | ||
.externalDocs(new ExternalDocumentation() | ||
.description("SpringShop Wiki Documentation") | ||
.url("https://springshop.wiki.github.org/docs")); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.example.mutbooks.app.base.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
@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; | ||
} | ||
} |
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.
많은 예외 사항에 대해서 고려해서 처리하신 거 잘하신 것 같습니다.!
하지만 만약 다른 컨트롤러에서는 여기보다 더 많은 예외를 처리할 수도 있을 것 같은데, 이럴 경우는 예외를 공통으로 처리하는 RestControllerAdvice를 사용하는 것이 좋아보입니다. RestControllerAdvice는 예외가 발생했을 때 처리하는 것이므로 컨트롤러에서 예외를 검증하지 않고, 서비스단에서 예외가 발생하였을 때 예외를 던지면 RestControllerAdvice가 처리해주기 때문에 컨트롤러 단이 많이 깔끔해질 수 있을 것 같습니다!