-
Notifications
You must be signed in to change notification settings - Fork 0
사용자 회원가입 및 로그인 #2
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
Conversation
5aba919 to
aa833ba
Compare
f-lab-moony
left a comment
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.
고생 많으셨습니다 ~ 피드백 확인 부탁드려요!
| import org.springframework.security.web.SecurityFilterChain; | ||
|
|
||
|
|
||
| @Configuration |
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.
혹시 어플리케이션 수행 해 보셨나요 ?
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.
네 직접 포스트맨으로 회원가입, 로그인 동작하는 것 확인하고 DB에도 사용자 레코드 확인했습니다
| http | ||
| .csrf(csrf -> csrf.disable()) | ||
| .authorizeHttpRequests(authz -> authz | ||
| .requestMatchers("/api/v1/users/register").permitAll() |
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.
다 permitAll인데 나눌 필요가 있을까요 ?
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.
일단은 나누는 것은 고려하지 않고 진행하겠습니다. (사실 어드민 등급을 따로 만들어서 어드민 사용자만 호출할 수 있는 api들도 추가하려 했는데, 이건 조금 먼 나중의 일이 될 것 같아서요)
| return "users"; | ||
| } | ||
|
|
||
| @PostMapping("/register") |
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.
생성요청도 Post로 제어가 가능해서 register는 굳이 안붙여도 될 것 같아요~
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.
수정하겠습니다
| } | ||
|
|
||
| @PostMapping("/register") | ||
| public ResponseEntity<?> registerUser(@RequestBody UserCreateRequest userCreateRequest) { |
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.
이거는 저번시간에 말씀드렸던 것 같은데, ok 이외의 응답은 여기서 처리 할 이유가 없어서 ResponseEntity는 안써주셔도 될 것 같아요 ~ 그리고 와일드카드(?)는 사용을 지양해주세요~
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.
수정하겠습니다
| public ResponseEntity<?> registerUser(@RequestBody UserCreateRequest userCreateRequest) { | ||
| try { | ||
| Long userId = userService.save(userCreateRequest); | ||
| return ResponseEntity.ok().body("User registered successfully with ID: " + userId); |
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.
body는 왜 이렇게 응답하신걸까요 ?
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.
아직 응답 dto 작업이 안되어있어서 일단 성공/실패 여부를 확인하기 위해 적어두었습니다
멘토님 질문의 의도가 혹시 응답 dto가 있어야하는데 없어서 물어보신 걸까요?
| validateUserRegistration(userCreateRequest); | ||
|
|
||
| // 새 사용자 생성 | ||
| User user = new User(); |
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.
setter대신 생성자를 이용해서 만드는 건 어떨까요? 그리고 lombok을 이용하면 builder 패턴을 쉽게 사용하실 수 있어요
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.
lombok 학습해서 적용해보겠습니다
| this.bCryptPasswordEncoder = bCryptPasswordEncoder; | ||
| } | ||
|
|
||
| public Long save(UserCreateRequest userCreateRequest) { |
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.
db작업은 @Transactional을 붙여주세요 ~
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.
수정하겠습니다
| return savedUser.getId(); | ||
| } | ||
|
|
||
| public User login(UserLoginRequest userLoginRequest) { |
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.
로그인 관련한건 별도의 서비스나 Dto를 확장 한 애들이 필요하지 않나요 ?
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.
분명 개념적으로는 단일책임원칙을 학습했지만 실제로 적용하여 분리하는 것이 미숙한 것 같습니다. 😭
UserService 안에서 User의 모든 로직을 처리한다- 라는 인식이 박혀있어서, 코드 작성 당시에 분리할 생각조차 못했는데 수정하겠습니다
| } | ||
|
|
||
| private void validateUserRegistration(UserCreateRequest userCreateRequest) { | ||
| if (userRepository.findByUsername(userCreateRequest.getUsername()).isPresent()) { |
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.
이렇게 검증을 해도 괜찮긴한데, 유니크 제약조건은 DB에 걸어놓기만 해도 커밋 시에 예외가 발생하니 굳이 안잡아줘도 된다고 생각해요. 검증을 위해 조회 쿼리가 매번 나가야해서요
| throw new RuntimeException("Username already exists: " + userCreateRequest.getUsername()); | ||
| } | ||
|
|
||
| if (userRepository.findByEmail(userCreateRequest.getEmail()).isPresent()) { |
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.
그리고 유효성검사할때 guava 라이브러리에 있는 Preconditions 사용하면 좀 사용하기 편하실거같아요 ~
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.
이것도 학습해서 적용해보겠습니다
f-lab-moony
left a comment
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.
고생 많으셨습니다 ~
이번 피처는 여기서 머지 할게요 ~
| import java.io.IOException; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor |
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.
@RequiredArgsConstructor 의 단점은 어떤게 있을까요 ?
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.
- 명시적 생성자가 없어서 어떤 필드가 필수인지 한눈에 파악하기 어렵습니다.
- 선택적으로 설정될 수 있는 필드에 대해서는 생성자를 자동으로 생성해주지 않기 때문에 직접 추가해야합니다.
- Autowired는 스프링이 제공 주체이지만, RequiredArgsConstructor는 Lombok에서 제공하기 때문에 Lombok 의존성 추가가 강제됩니다.
크게 3가지 정도 학습해서 답변드립니다
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class JwtAuthenticationFilter extends OncePerRequestFilter { |
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.
Jwt를 쓰기로 한 이유가 있을까요 ?
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.
솔직히 말씀드리면 토큰이 필요하다는 생각이 들었을 때 가장 먼저 떠오른게 JWT여서-이긴 했는데요,
토큰 안에 필요한 정보들을 선별해 담을 수 있고, 서버 어딘가에 인증정보를 저장해야하는 세션 기반 방식과는 다르게 stateless하다는 장점들이 있다고 알고 있습니다.
|
|
||
| @Override | ||
| protected void doFilterInternal(HttpServletRequest request, | ||
| HttpServletResponse response, |
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.
스타일가이드가 없는 것 같은데요, 스타일가이드 적용 예제 보고 한번 따라해보시면 좋을 것 같아요 ~
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.
수정하겠습니다
|
|
||
| private String getJwtFromRequest(HttpServletRequest request) { | ||
| String bearerToken = request.getHeader("Authorization"); | ||
| if (bearerToken != null && bearerToken.startsWith("Bearer ")) { |
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.
이런건 상수로 정의해보면 좋을 것 같아요 ~
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.
수정하겠습니다
| try { | ||
| String jwt = getJwtFromRequest(request); | ||
|
|
||
| if (jwt != null && jwtTokenProvider.validateToken(jwt)) { |
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.
null체크같은 경우는 조건이 맞지 않는 경우 얼리리턴을 해주는게 가독성에 도움이 될 것 같아요 ~
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.
수정하겠습니다
| } catch (Exception e) { | ||
| return ResponseEntity.badRequest().body("Registration failed: " + e.getMessage()); | ||
| } | ||
| @PostMapping("") |
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.
빈 문자열은 굳이 필요 없어보여요 ~
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.
수정하겠습니다
| return ResponseEntity.badRequest().body("Registration failed: " + e.getMessage()); | ||
| } | ||
| @PostMapping("") | ||
| @ResponseStatus(HttpStatus.CREATED) |
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.
이건 좀 생각해 볼 필요가 있는데요, 클라이언트 사이드에서 성공 응답을 200번대 로 잡는게 아니라 200 을 잡는 경우가 많아서 성공 응답을 세분화하진 않는 편이에요. 물론 잘못된건 아니고 제가 겪지 못한 부분에서 나누는게 의미가 있을 수도 있겠지만, 제가 경험한 범위에서는 그랬었고, 당장 생각해봤을때 성공 응답을 세분화하는게 의미가 있나 싶긴 하네요!
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.
성공 응답 세분화까지는 안하는 방향으로 수정하겠습니다
| } catch (Exception e) { | ||
| return ResponseEntity.badRequest().body("Login failed: " + e.getMessage()); | ||
| } | ||
| public ResponseEntity<UserLoginResponse> loginUser(@Valid @RequestBody UserLoginRequest userLoginRequest) { |
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.
요거는 몇번 말씀드렸는데, 그냥 응답에 성공하면 기본적으로 200 응답이 내려가기때문에 굳이 응답을 감싸진 않아도 될 것 같아요
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.
코드를 쓰고 지우고 반복하는 과정에서 또 되살아난 것 같습니다. 주의하겠습니다!
| public String getEmail() { | ||
| return email; | ||
| } | ||
| @NotBlank(message = "Email is required") |
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.
유효성검사를 여기서 하는건 좀 생각해볼 필요가 있는데요, 설정이 쉽고 요청단에서 어떤 값을 가지는지 의미를 명확히 할 수 있다는 장점도 있지만, 코드 침투가 일어나서 가독성이 떨어지는 것과, 어차피 서비스단에서 유효성검사를 하기 때문에 중복으로 검사를 하게 되고 관리 포인트가 늘어나는 단점이 있어요.
컨트롤러 - 서비스 - 도메인 중 유효성검사는 어디서 해야하나 라는걸로 의견이 갈릴때가 많은데, 굳이 따지자면 저는 도메인에 가까운 순으로 하는게 맞다고 생각하고, 제일 좋은건 각 레이어에서 각각 하는게 제일 좋긴 하다고 생각합니다
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.
컨트롤러, 서비스, 도메인 3개의 레이어에서 각각 유효성검사를 수행하되, dto에서까지는 수행하지 않는 것으로 이해했습니다.
수정하겠습니다.
|
제가 머지 권한이 없네요? 직접 머지 해주시고 저 권한좀 주세요 ~ |
Description
사용자 회원가입 및 로그인 기능을 구현했습니다.
Changes
Technical Details
Testing
Checklist
🔗 Related Issues
Closes #1