[Spring] 숙련과제- 나만의 블로그 백엔드 서버 만들기 - 개발과정 정리

조성현·2022년 12월 16일
0

개요

Spring 숙련주차 과제 구현과정을 정리해보며 취지에서 작성된 글입니다.


과제 구현과정 정리

1. 회원가입 API

1) 회원 가입 API 과제명세

  • username, password를 Client에서 전달받기
  • username은 최소 4자 이상, 10자 이하이며 알파벳 소문자(a~z), 숫자(0~9)로 구성되어야 한다.
  • password는 최소 8자 이상, 15자 이하이며 알파벳 대소문자(a~z, A~Z), 숫자(0~9)로 구성되어야 한다.
  • DB에 중복된 username이 없다면 회원을 저장하고 Client 로 성공했다는 메시지, 상태코드 반환하기

참고자료
1. https://mangkyu.tistory.com/174
2. https://ko.wikipedia.org/wiki/정규_표현식
3. https://bamdule.tistory.com/35

1-1. 회원가입API 개발과정

1) 먼저 ERD(Entity Relationship Diagram) 설계도를 살펴보며 User Entity를 설계했다.

@Getter
@NoArgsConstructor
@Entity(name = "users")
public class User {
    @Id
    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    public User(SignupRequestDto signupRequestDto) {
        this.username = signupRequestDto.getUsername();
        this.password = signupRequestDto.getPassword();
    }
}
  • 과제 명세에서 username에 대한 중복검증을 요구하고, 사용자가 작성한 글에 대해서만 데이터변경이 가능하도록 하라는 점들로부터 username을 PK로 정해야겠다는 결론을 내리고 @Id 어노테이션을 달아주었다.

2) username, password가 요구사항에 맞는지 검증하는 로직을 구현했다.

// @RestController는 @Controller에 @ResponseBody가 추가된 것. 주용도는 Json형태로 객체 데이터를 반환하는 것.
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class UserController {
    private final UserService userService;
    
    @PostMapping("/signup")
    public ResponseEntity<String> signup(@RequestBody @Valid SignupRequestDto signupRequestDto) {
        userService.signup(signupRequestDto);
        return new ResponseEntity<>("회원가입 성공", HttpStatus.CREATED);
    }
}

@Getter
public class SignupRequestDto {
    @Size(min = 4, max = 10,message = "username Size error")
    @Pattern(regexp = "^[a-z0-9]*$",message = "username Pattern error")
    private String username;
    
    @Size(min = 8, max = 15,message = "password Size error")
    @Pattern(regexp ="^[a-zA-Z0-9]*$", message = "password Pattern error")
    private String password;

}
build.gradle
// @Valid를 통해 유효성검증을 하기 위해서는 의존성을 추가해주어야한다. 
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
}

UserControler.signup의 매개변수(dto)앞에 @Valid를 붙여주고,
SignupRequestDto의 값들에 @Size, @Pattern을 사용하여 제약조건들을 걸어주었다.

3) userDB에 중복되는 username이 없는지 검증하는 로직을 구현했다.

 @Transactional
 public void signup(SignupRequestDto signupRequestDto) {
     //회원 중복 확인 by username
     Optional<User> overlapUser = userRepository.findByUsername(signupRequestDto.getUsername());
     if(overlapUser.isPresent()) throw new OverlapUserExistException();
     userRepository.save(new User(signupRequestDto));
 }

2. 로그인 API

2) 로그인 API 과제명세

  • username, password를 Client에서 전달받기
  • DB에서 username을 사용하여 저장된 회원의 유무를 확인하고 있다면 password 비교하기
  • 로그인 성공 시, 로그인에 성공한 유저의 정보와 JWT를 활용하여 토큰을 발급하고,
    발급한 토큰을 Header에 추가하고 성공했다는 메시지, 상태코드 와 함께 Client에 반환하기

2-1. 로그인API 개발과정

UserController

 @PostMapping("/login")
 public ResponseEntity<String> login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
     userService.login(loginRequestDto, response);
     return new ResponseEntity<>("로그인 성공", HttpStatus.OK);
 }
UserService

@Transactional
public void login(LoginRequestDto loginRequestDto, HttpServletResponse response) {
    User user = userRepository.findByUsername(loginRequestDto.getUsername()).orElseThrow(UserNotExistException::new);
    user.validatePassword(loginRequestDto.getPassword());
    response.addHeader(jwtUtil.AUTHORIZATION_HEADER, jwtUtil.createToken(user.getUsername()));
}

로그인 API를 개발하는 과정에서 JWT와 HttpRequest, Response에 대한 배움을 많이 얻었다.
(역시 직접 써보고, 안되는 이유를 찾아가며 디버깅도 해봐야 이해가 잘된다.)

  • 다른 부분보다 더 신기했던 점은, HttpResponse를 Request받는 시점에서 호출하여 값을 넣어줄수 있다는 점이다. (요청받는 시점에 응답객체도 동시에 생성되기 때문에 가능하다.)

3. 입문주차에 구현한 과제를 요구사항에 맞게 삭제 및 수정

취소선으로 표시된 부분은 삭제 / 배경으로 감싸진 부분은 수정

1) 전체 게시글 목록 조회 API

  • 제목, 작성자명(username), 작성 내용, 작성 날짜를 조회하기
  • 작성 날짜 기준 내림차순으로 정렬하기

2) 게시글 작성 API

  • 토큰을 검사하여, 유효한 토큰일 경우에만 게시글 작성 가능
  • 제목, 작성자명(username), 작성 내용을 저장하고
  • 저장된 게시글을 Client 로 반환하기

3) 선택한 게시글 조회 API

  • 선택한 게시글의 제목, 작성자명(username), 작성 날짜, 작성 내용을 조회하기
    (검색 기능이 아닙니다. 간단한 게시글 조회만 구현해주세요.)

4) 선택한 게시글 수정 API

  • 수정을 요청할 때 수정할 데이터와 비밀번호를 같이 보내서 서버에서 비밀번호 일치 여부를 확인 한 후
  • 토큰을 검사한 후, 유효한 토큰이면서 해당 사용자가 작성한 게시글만 수정 가능
  • 제목, 작성 내용을 수정하고 수정된 게시글을 Client 로 반환하기

5) 선택한 게시글 삭제 API

  • 삭제를 요청할 때 비밀번호를 같이 보내서 서버에서 비밀번호 일치 여부를 확인 한 후
  • 토큰을 검사한 후, 유효한 토큰이면서 해당 사용자가 작성한 게시글만 삭제 가능
  • 선택한 게시글을 삭제하고 Client 로 성공했다는 메시지, 상태코드 반환하기

3-1 개발과정

// 컨트롤러의 updatePost를 예시로 가져왔다.
    @PutMapping("/api/posts/{id}")
    public PostResponseDto updatePost(@PathVariable Long id, @RequestBody PostRequestDto postRequestDto, HttpServletRequest request){
        return postService.updatePost(id, postRequestDto, request);
    }

수정할 Post의 id를 @PathVariable을 통해 받고, @RequestBody를 통해 Dto에 수정할 Post 내용들을 담아서 받고, HttpServletRequest를 받아와서 header에 담긴 JWT를 확인하고자 했다.

  • 친구와 코드리뷰를 하는 과정에서 Request 전체를 다 Service로 넘겨주는 것보다, 내가 필요한 부분들만 추출해서 넘겨주는게 좋다는 피드백을 받을 수 있었다.
// 서비스의 updatePost를 예시로 가져왔다.
    @Transactional
    public PostResponseDto updatePost(Long id, PostRequestDto postRequestDto, HttpServletRequest request) {
        String token = jwtUtil.resolveToken(request);
        String username;
        tokenNullCheck(token);
        if (jwtUtil.validateToken(token)) {
            username = jwtUtil.getUserInfoFromToken(token).getSubject();
        } else throw new IllegalArgumentException("Token Error");

        Post post = postRepository.findById(id).orElseThrow(PostNotExistException::new);
        post.validateUsername(username);
        post.updatePost(postRequestDto);
        postRepository.save(post);
        return new PostResponseDto(post);
    }
    
    public void tokenNullCheck(String token) {
        if (token == null) throw new IllegalArgumentException("토큰이 텅텅 비었어요");
    }

postService.updatePost
1. 가져온 토큰을 resolve 해주고,
2. NullCheck를 해준다. (nullCheck는 여기저기서 계속 쓰이기도하고 직관적으로 어떤 작업을 하는지 알아보기 편하도록 메서드로 만들었다.
3. 유효한 token일 경우에 username을 추출해주고(검증과정과 추출과정에서 2번의 jwt verify가 진행된다는 점이 성능상으로 좋지 못하다는 점도 배웠다.)(강의에서 저렇게 알려줬다는게 킬포)
4. id를 활용해서 선택한 Post를 가져오고 username이 동일한지 검증한다.
5. 1~4가 무사히 진행됐다면? 업데이트를 진행한다.

  • 과제의 다른 구현사항들을 확인하길 원하신다면 게시물 처음에 레파지토리에서 확인 가능합니다.

또한 github에 올리실 때, JWT secret key는 ignore해서 올려야 함을 기억하셔야됩니다. (물론 지금 레파지토리는 연습이니까 올려뒀습니다.)


https://cheershennah.tistory.com/179

profile
맛있는 음식과 여행을 좋아하는 당당한 뚱땡이

0개의 댓글