Spring Lv4 과제
Spring Security 만 적용한 것이므로, API 명세서와 ERD 는 생략한다
회원 가입 API
최소 4자 이상, 10자 이하이며 알파벳 소문자(a~z), 숫자(0~9)
로 구성되어야 한다.최소 8자 이상, 15자 이하이며 알파벳 대소문자(a~z, A~Z), 숫자(0~9)
로 구성되어야 한다.로그인 API
댓글 작성 API
댓글 수정 API
댓글 삭제 API
예외 처리
전체 게시글 목록 조회 API
게시글 작성 API
선택한 게시글 조회 API
선택한 게시글 수정 API
선택한 게시글 삭제 API
UserController 는 Lv3 과 동일하다
게시글 전체/선택 조회를 제외하고, 작성/수정/삭제에는 @AuthenticationPrincipal 가 사용되었다.
// 게시글 작성
@PostMapping("/board")
public ResponseEntity<BoardResponseDto> createBoard(@RequestBody BoardRequestDto requestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(boardService.createBoard(requestDto, userDetails.getUser()));
}
...
// 게시글 수정
@PutMapping("/board/{boardId}")
public ResponseEntity<BoardResponseDto> updateBoard(@PathVariable Long boardId, @RequestBody BoardRequestDto requestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(boardService.updateBoard(boardId, requestDto, userDetails.getUser()));
}
// 게시글 삭제
@DeleteMapping("/board/{boardId}")
public ResponseEntity<MsgResponseDto> deleteBoard(@PathVariable Long boardId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(boardService.deleteBoard(boardId, userDetails.getUser()));
}
@AuthenticationPrincipal
// 댓글 작성
@PostMapping("/{boardId}")
public ResponseEntity<CommentResponseDto> createComment(@PathVariable Long boardId, @RequestBody CommentRequestDto commentRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(commentService.createComment(boardId, commentRequestDto, userDetails.getUser()));
}
// 댓글 수정
@PutMapping("/{boardId}/{cmtId}")
public ResponseEntity<CommentResponseDto> updateComment(@PathVariable Long boardId, @PathVariable Long cmtId, @RequestBody CommentRequestDto commentRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(commentService.updateComment(boardId, cmtId, commentRequestDto, userDetails.getUser()));
}
// 댓글 삭제
@DeleteMapping("/{boardId}/{cmtId}")
public ResponseEntity<MsgResponseDto> deleteComment(@PathVariable Long boardId, @PathVariable Long cmtId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(commentService.deleteComment(boardId, cmtId, userDetails.getUser()));
}
User Entity 에 구현해둔 정규 표현식을 SignupRequestDto 로 옮겼고, 댓글 작성에 commenta (댓글 내용) 만 입력하도록 수정했다.
이를 제외하고는 Lv3 과 동일하다
Service 에서 JWT 호출 및 검증 부분은 모두 빠지고, 다른 패키지에 구현해두었다.
// 게시글 수정
@Transactional
public BoardResponseDto updateBoard(Long boardId, BoardRequestDto requestDto, User user) {
// 게시글이 있는지 & 사용자의 권한 확인
Board board = userService.findByBoardIdAndUser(boardId, user);
board.update(requestDto);
List<CommentResponseDto> commentList = new ArrayList<>();
for (Comment comment : board.getCommentList()) {
commentList.add(new CommentResponseDto(comment));
}
return new BoardResponseDto(board, commentList);
}
// 게시글 삭제
public MsgResponseDto deleteBoard(Long boardId, User user) {
// 게시글이 있는지 & 사용자의 권한 확인
Board board = userService.findByBoardIdAndUser(boardId, user);
boardRepository.delete(board);
return new MsgResponseDto("게시글을 삭제했습니다.", HttpStatus.OK.value());
}
// 댓글 수정
@Transactional
public CommentResponseDto updateComment(Long boardId, Long cmtId, CommentRequestDto commentRequestDto, User user) {
// 게시글이 있는지
Board board = boardRepository.findById(boardId).orElseThrow(
() -> new CustomException(NOT_FOUND_BOARD)
);
// 댓글이 있는지 & 사용자의 권한 확인
Comment comment = userService.findByCmtIdAndUser(cmtId, user);
comment.update(commentRequestDto);
return new CommentResponseDto(comment);
}
// 댓글 삭제
public MsgResponseDto deleteComment(Long boardId, Long cmtId, User user) {
// 게시글이 있는지
Board board = boardRepository.findById(boardId).orElseThrow (
() -> new CustomException(NOT_FOUND_BOARD)
);
// 댓글이 있는지 & 사용자의 권한 확인
Comment comment = userService.findByCmtIdAndUser(cmtId, user);
commentRepository.delete(comment);
return new MsgResponseDto("댓글을 삭제했습니다.", HttpStatus.OK.value());
}
로그인은 Lv3 과 동일하다
// 회원 가입
public void signup(SignupRequestDto requestDto) {
String username = requestDto.getUsername();
String password = passwordEncoder.encode(requestDto.getPassword());
// 회원 중복 확인
Optional<User> checkUsername = userRepository.findByUsername(username);
if (checkUsername.isPresent()) {
throw new CustomException(DUPLICATED_USERNAME);
}
// 사용자 ROLE 확인 (admin = true 일 경우 아래 코드 수행)
UserRoleEnum role = UserRoleEnum.USER;
if (requestDto.isAdmin()) {
if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
throw new CustomException(NOT_MATCH_ADMIN_TOKEN);
}
role = UserRoleEnum.ADMIN;
}
// 사용자 등록 (admin = false 일 경우 아래 코드 수행)
User user = new User(username, password, role);
userRepository.save(user);
}
// 사용자의 권한 확인 - 게시글
Board findByBoardIdAndUser(Long boardId, User user) {
Board board;
// ADMIN
if (user.getRole().equals(UserRoleEnum.ADMIN)) {
board = boardRepository.findById(boardId).orElseThrow(
() -> new CustomException(NOT_FOUND_BOARD)
);
// USER
} else {
board = boardRepository.findByIdAndUserId(boardId, user.getId()).orElseThrow (
() -> new CustomException(NOT_FOUND_BOARD_OR_AUTHORIZATION)
);
}
return board;
}
// 사용자의 권한 확인 - 댓글
Comment findByCmtIdAndUser(Long cmtId, User user) {
Comment comment;
// ADMIN
if (user.getRole().equals(UserRoleEnum.ADMIN)) {
comment = commentRepository.findById(cmtId).orElseThrow(
() -> new CustomException(NOT_FOUND_COMMENT)
);
// USER
} else {
comment = commentRepository.findByIdAndUserId(cmtId, user.getId()).orElseThrow (
() -> new CustomException(NOT_FOUND_COMMENT_OR_AUTHORIZATION)
);
}
return comment;
}
Lv3 과 동일하다
UserRoleEnum 을 제외하곤 Lv3 과 동일하다
public enum UserRoleEnum {
USER(Authority.USER), // 사용자 권한
ADMIN(Authority.ADMIN); // 관리자 권한
private final String authority;
UserRoleEnum(String authority) {
this.authority = authority;
}
public String getAuthority() {
return this.authority;
}
public static class Authority {
public static final String USER = "ROLE_USER";
public static final String ADMIN = "ROLE_ADMIN";
}
}
Lv3 과 동일하다
WebSecurityConfig,
JwtUtil, JwtAuthenticationFilter, JwtAuthorizationFilter,
UserDetailsImpl, UserDetailsServiceImpl 에 대한 내용은 아래 링크에 정리해두었다.
참고: Spring Security - JWT 방식
Lv3 까지는 Service 단에서 각 기능마다 메서드를 호출해서 JWT 토큰을 검증했으나 Lv4 에서는 Spring Security 가 인증/인가를 해주므로 그럴 필요가 없어졌다.
ErrorCode 를 제외하곤 Lv3 과 동일하다
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
// 토큰 전달 하지 않은 경우, 정상 토큰이 아닌 경우
INVALID_TOKEN(HttpStatus.BAD_REQUEST, "토큰이 유효하지 않습니다."),
// 토큰이 있으며 유효한 토큰이나, 해당 사용자의 게시글/댓글이 아닌 경우 (즉, 해당 사용자의 토큰이 아닌 경우)
NOT_FOUND_COMMENT_OR_AUTHORIZATION(HttpStatus.BAD_REQUEST, "댓글을 찾을 수 없거나 작성자만 삭제/수정할 수 있습니다."),
NOT_FOUND_BOARD_OR_AUTHORIZATION(HttpStatus.BAD_REQUEST, "게시글을 찾을 수 없거나 작성자만 삭제/수정할 수 있습니다."),
// DB 에 이미 존재하는 username 으로 회원가입 요청한 경우
DUPLICATED_USERNAME(HttpStatus.BAD_REQUEST, "중복된 username 입니다"),
// 로그인 시, username 과 password 중 일치하지 않는 정보가 있을 경우
NOT_MATCH_INFORMATION(HttpStatus.BAD_REQUEST, "회원을 찾을 수 없습니다."),
// DB 에 해당 게시글이 존재하지 않는 경우
NOT_FOUND_BOARD(HttpStatus.BAD_REQUEST, "게시글을 찾을 수 없습니다."),
// DB 에 해당 댓글이 존재하지 않는 경우
NOT_FOUND_COMMENT(HttpStatus.BAD_REQUEST, "댓글을 찾을 수 없습니다."),
// admin 계정으로 회원가입 시, ADMIN_TOKEN 과 일치하지 않을 경우
NOT_MATCH_ADMIN_TOKEN(HttpStatus.BAD_REQUEST, "관리자 암호가 일치하지 않습니다.");
private final HttpStatus httpStatus;
private final String message;
}
각 상황에 맞춘 에러 코드를 전달하도록 수정이 있었다.
안녕하세요! 좋은 자료 감사합니다!
다름이 아니라 위 내용이랑 작성자분 깃허브에 있는 코드 참고해서 현재 회원 및 게시글 등록 연결을 구현하고 있습니다.
하지만 Postman으로 회원가입, 로그인 이후 게시글을 등록하려 하면 java.lang.NullPointerException: Cannot invoke "MyUserDetails.getMember()" because "myUserDetails" is null이라는 에러가 뜨는데, 디버깅해보니 MyUserDetails.getMember()가 anonymousMember라고 뜹니다. 이에 대한 해결책을 알 수 있을까요?ㅠㅠ