새로 알게된 @AuthenticationPrincipal 덕분에 앞서 이전 코드들이 넓게 넓게 변화한부분들이 있다. @AuthenticationPrincipal 어노테이션은 간단히 말해서 authentication객체에 접근할 수 있는 어노테이션이다. 즉, 필터에서 인증을 통해 authentication객체를 담았다면(나는 jwt인증을 통해서 담아주었다. 밑 코드 참고) 해당 어노테이션을 통해 객체에 접근하여 원하는 데이터를 사용할 수 있다.
-생략-
if (username != null) { //유효한 jwt토큰이 들어왔다면
System.out.println("jwt토큰이 유효함");
User userEntity = userRepository.findByUsername(username);
PrincipalDetails principalDetails = new PrincipalDetails(userEntity);
//jwt 토큰 서명에 근거해서 만들어진 authentication 객체
Authentication authentication =
new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities()); //RoleType지정에 필요함
//강제로 시큐리티 세션에 접근하여 authentication객체를 저장.
SecurityContextHolder.getContext().setAuthentication(authentication);
}
-생략-
<결과: admin으로 로그인 한 경우>
<결과2: user계정으로 로그인 한 경우>
<SecurityConfig> : 접근가능한 권한 설정 조금 수정
.authorizeRequests(authorize->authorize
.antMatchers("/admin/**", "/admin/**", "/admin/**")
.access("hasRole('ROLE_TNUT')")
.antMatchers("/user/**")
.access("hasRole('ROLE_USER') or hasRole('ROLE_TNUT')")
.antMatchers("/authority")
.access("hasRole('ROLE_USER') or hasRole('ROLE_TNUT')")
.anyRequest().permitAll())
<AdminReplyApiController>
package tnut.blogback.controller.api.admin;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import tnut.blogback.service.ReplyService;
@RequiredArgsConstructor
@RestController
public class AdminReplyApiController {
private ReplyService replyService;
@Autowired
public AdminReplyApiController(ReplyService replyService) {
this.replyService = replyService;
}
@DeleteMapping("/admin/api/reply/{id}/delete") // 관리자 댓글 삭제
public String replyDelete (@PathVariable Long id) {
return replyService.replyDelete(id);
}
}
<UserReplyApiController>: 시작에서 설명한 @AuthenticationPrincipal로 유저 정보를 댓글에 담아줌, 댓글 수정, 삭제는 왜 필요하냐? -> 유효한 accessToken + localStorage에 담긴 username을 본인이 아닌 다른 사람의 이름으로 담아서 보내면 요청이 승인됨 따라서 accessToken에 담긴 유저 정보와 댓글의 유저정보를 비교해서 다른 사람이 요청한 내 댓글에 대한 삭제, 수정을 막을 수 있음
package tnut.blogback.controller.api.user;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import tnut.blogback.config.auth.PrincipalDetails;
import tnut.blogback.dto.replyDTO.ReplySaveRequestDto;
import tnut.blogback.dto.replyDTO.ReplyUpdateDto;
import tnut.blogback.dto.replyDTO.RereplySaveRequestDto;
import tnut.blogback.dto.ResponseDto;
import tnut.blogback.service.ReplyService;
@RequiredArgsConstructor
@RestController
public class UserReplyApiController {
private ReplyService replyService;
@Autowired
public UserReplyApiController(ReplyService replyService) {
this.replyService = replyService;
}
@PostMapping("/user/api/reply/save") //댓글 내용 받아서 저장
public ResponseDto<?> replySave (@RequestBody ReplySaveRequestDto replySaveRequestDto, @AuthenticationPrincipal PrincipalDetails principal) {
return new ResponseDto<>(HttpStatus.OK.value(), replyService.replySave(replySaveRequestDto, principal.getUser()));
}
@PostMapping("/user/api/reReply/save") //대댓글 내용 받아서 저장
public ResponseDto<?> reReplySave (@RequestBody RereplySaveRequestDto reReplySaveRequestDto, @AuthenticationPrincipal PrincipalDetails principal) {
return new ResponseDto<>(HttpStatus.OK.value(), replyService.reReplySave(reReplySaveRequestDto, principal.getUser()));
}
@DeleteMapping("/user/api/reply/{id}/delete") //댓글 삭제
public String replyDelete (@PathVariable Long id, @AuthenticationPrincipal PrincipalDetails principal) {
return replyService.replyDelete(id, principal.getUser());
}
@PutMapping("/user/api/reply/{id}/update") // 댓글 수정
public ResponseDto<?> replyUpdate (@RequestBody ReplyUpdateDto replyUpdateDto, @AuthenticationPrincipal PrincipalDetails principal) {
return new ResponseDto<>(HttpStatus.OK.value(), replyService.replyUpdate(replyUpdateDto, principal.getUser()));
}
}
<AuthorityController> : react에서 admin과 user에 따라 삭제나, 수정 버튼을 렌더링 하도록 해줘야 해서 authority와 username을 localStorage에 담아서 적용할 때 필요함
package tnut.blogback.controller.auth;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import tnut.blogback.config.auth.PrincipalDetails;
import tnut.blogback.dto.ResponseDto;
import tnut.blogback.model.User;
import tnut.blogback.repository.UserRepository;
import java.util.HashMap;
import java.util.Map;
@RequiredArgsConstructor
@RestController
public class AuthorityController {
private UserRepository userRepository;
@Autowired
public AuthorityController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/authority")
public ResponseDto<?> authority(@AuthenticationPrincipal PrincipalDetails principal) {
User user = userRepository.findByUsername(principal.getUser().getUsername());
Map<String, String> userAuthority = new HashMap<>();
userAuthority.put("role", user.getRoleType().toString());
userAuthority.put("username", user.getUsername());
return new ResponseDto<>(HttpStatus.OK.value(), userAuthority);
}
}
<User 와 Reply의 관계 설정>
<Reply>
@ManyToOne
@JsonIgnoreProperties(value = {"password", "email", "provider", "providerId", "roleType", "createDate"})
@JoinColumn(name = "user_id")
private User user;
----------------------------------------------------------------------------
<User>
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
@OrderBy("id desc") //최신 댓글 순으로 정렬
@JsonIgnore //무한참조 방지
private List<Reply> replies = new ArrayList<>();
<ReplyRepository> : 위에서 요청한 user정보와 댓글의 user정보를 비교한다는 얘기는 사실, principal의 유저와 댓글의 id를 함께 가진 댓글을 찾아낸다는 소리였음, 올바른 요청이라면 당연히 찾아질 것이고, 다른 사람이 다른 유저의 댓글을 삭제 요청한다면 찾을 수 없을 것임
package tnut.blogback.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import tnut.blogback.model.Reply;
import tnut.blogback.model.User;
import java.util.Optional;
public interface ReplyRepository extends JpaRepository<Reply, Long> {
Optional<Reply> findByIdAndUser(Long id, User user);
}
<ReplyService> : 삭제 시 user를 굳이 비교할 필요가 없는 관리자용 삭제 로직이 추가되고, user비교로 수정됨
@Transactional
public String replyDelete(Long id, User user) { //유저 댓글 삭제 DeleteMapping
Reply replyEntity = replyRepository.findByIdAndUser(id, user)
.orElseThrow(() -> new IllegalArgumentException("이미 삭제되거나 권한이 없는 이용자 입니다."));
if (!replyEntity.getSubReplies().isEmpty()) { //자식이 있을경우
replyEntity.setContent(null); //내용을 비우고
replyEntity.setDeletable(true); //삭제 가능상태로 업데이트
} else { //자식이 없을 경우
replyRepository.deleteById(id); //삭제 대상은 삭제하고
replyRepository.flush(); //플러쉬 하지 않으면 삭제가 맨 마지막에 DB에서 처리 되기 때문에 부모댓글의 대댓글이 존재한 채로 다음 로직이 먼저 실행
if (replyEntity.getParentReply() == null) {
return "success delete!";
} else {
if (replyEntity.getParentReply().isDeletable()) {
replyDelete(replyEntity.getParentReply().getId(), replyEntity.getParentReply().getUser()); //부모에 대한 삭제도 진행
}
}
}
return "success delete!";
}
@Transactional
public String replyDelete(Long id) { //관리자 댓글 삭제
Reply replyEntity = replyRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("이미 삭제된 댓글입니다."));
if (!replyEntity.getSubReplies().isEmpty()) {
replyEntity.setContent(null);
replyEntity.setDeletable(true);
} else {
replyRepository.deleteById(id);
replyRepository.flush();
if (replyEntity.getParentReply() == null) {
return "success delete!";
} else {
if (replyEntity.getParentReply().isDeletable()) {
replyDelete(replyEntity.getParentReply().getId());
}
}
}
return "success delete!";
}
@Transactional
public Reply replyUpdate(ReplyUpdateDto replyUpdateDto, User user) {
Reply replyEntity = replyRepository.findByIdAndUser(replyUpdateDto.getId(), user)
.orElseThrow(() -> new IllegalArgumentException("이미 삭제된 댓글이거나 권한이 없습니다."));
replyEntity.setContent(replyUpdateDto.getContent());
return replyEntity;
}
'개인 프로젝트 > 블로그' 카테고리의 다른 글
나만의 블로그 만들기 깃허브 주소 (0) | 2022.08.05 |
---|---|
댓글에 유저 정보를 담아보자 2 (0) | 2022.08.02 |
네이버 카카오 Oauth2.0 로그인 구현 (0) | 2022.08.01 |
댓글 수정 구현 + react에서 수정,삭제하기 (0) | 2022.07.31 |
댓글 삭제 구현 로직 생각 및 구현 (0) | 2022.07.29 |