댓글 수정, 삭제를 구현하기 전에 Spring Security와 Thymeleaf로 게시글을 작성자만 삭제, 수정 가능하게 리팩터링 한다.
Spring Security에서 관리하는 세션을 Thymeleaf 에서 접근할 수 있다.
https://www.thymeleaf.org/doc/articles/springsecurity.html
Thymeleaf + Spring Security integration basics - Thymeleaf
Have you switched to Thymeleaf but your login and error pages are still using JSP? In this article we will see how to configure your Spring application to use Thymeleaf for login and error pages. All the code seen here comes from a working application. You
www.thymeleaf.org
Build.gradle
dependencies {
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
}
Thymeleaf Add name space
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extars/spring-security">
Spring Security에 접근할 Thymeleaf View 파일에서 해당 xml name space을 추가한다.
Thymeleaf use
<div sec:authorize="isAuthenticated()">
This content is only shown to authenticated users. // 로그인한 사용자만 보임
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')">
This content is only shown to administrators. // 관리자 권한을 가진 사용자는 보임
</div>
<div sec:authorize="hasRole('ROLE_USER')">
This content is only shown to users. // 유저 권한을 가진 사용자는 보임
</div>
authentication
Logged user: <span sec:authentication="name">Bob</span> // 인증한 세션의 Usernmae 출력
Roles: <span sec:authentication="principal.authorities">[ROLE_USER, ROLE_ADMIN]</span> // 인증한 세션의 권한목록 출력
리팩터링 전 코드
리팩터링 전에는 글 작성자가 누구든 인증만 하면 다른 사람의 글도 수정, 삭제 할 수 있었다.
<div class="text-end" sec:authorize="isAuthenticated()">
<a th:href="@{|/board/edit/${post.getId()}|}" class="btn btn-primary">수정</a>
<a th:href="@{|/board/delete/${post.getId()}|}" class="btn btn-danger">삭제</a>
</div>
리팩터링 후 코드
PostDto
@Getter
public class PostResponse {
private Long id;
private String title;
private String content;
private String nickname;
private String username;
private LocalDate createDate;
private List<CommentResponse> commentResponses;
@Builder
public PostResponse(Post post) {
this.id = post.getId();
this.title = post.getTitle();
this.content = post.getContent();
this.nickname = post.getUser().getNickname();
this.username = post.getUser().getUsername();
this.createDate = post.getCreatedDate();
this.commentResponses = post.getComments().stream().map(CommentResponse::new).collect(Collectors.toList());
}
}
PostController
public class PostController{
private final PostService postService;
@GetMapping("/posts/{postId}")
public String get(@PathVariable Long postId, Model modeler) {
PostResponse postResponse = postService.get(postId);
List<CommentResponse> comments = postResponse.getCommentResponses();
if (!comments.isEmpty()) {
model.addAttribute("comments", comments);
}
model.addAttribute("post", postResponse);
return "post/postView";
}
}
Thymeleaf 에서 Spring Security가 로그인 성공 후에 관리하는 AuthenticationToken에 접근할 수 있는데
Authentication구현체를 가져오면 로그인할 때 만든 UserDetail로 만든 Principal를 상속받고 있다.
Thymeleaf View
PostResponse 내의 작성자와 Spring Security에서 관리하는 세션의 username을 비교해서 같을 때에만 수정, 삭제 버튼을 보이게 만들었다. > 인가
<div class="text-end" th:if="${#authentication.name}==${post.username}">
<a th:href="@{|/board/edit/${post.getId()}|}" class="btn btn-primary">수정</a>
<a th:href="@{|/board/delete/${post.getId()}|}" class="btn btn-danger">삭제</a>
</div>
결과
개선
View에서 작성자가 아니면 게시글을 컨트롤할 수 없게끔 보이게 했지만,
컨트롤러에서는 여전히 인증만하면 수정, 삭제가 가능하다. 컨트롤러에서 세션을 가져와 해당 부분을 컨트롤하고 뷰로 넘기는 방법이 더 안전할 것 같긴한데 Thymeleaf Security는 그럼 왜 쓰는지? 더 공부를 해봐야할 것 같다.
public class PostController{
private final PostService postService;
@GetMapping("/posts/{postId}") // 글 상세보기
public String get(@PathVariable Long postId, Model modeler) {
PostResponse postResponse = postService.get(postId);
List<CommentResponse> comments = postResponse.getCommentResponses();
if (!comments.isEmpty()) {
model.addAttribute("comments", comments);
}
model.addAttribute("post", postResponse);
return "post/postView";
}
@GetMapping("/posts/edit/{postId}") // 글 수정 폼 불러오기
public String edit(@PathVariable Long postId, Model model, @AuthenticationPrincipal CustomUserDetails userDetails) {
PostResponse post = postService.get(postId);
if(userDetails != null){
existsSession(model, userDetails);
}
if(!userDetails.getUsername().equals(post.getUsername())) return "/account/login"; // 작성자가 아니면 로그인 페이지로 이동
model.addAttribute("postEdit", new PostEdit(post.getTitle(), post.getContent()));
model.addAttribute("postId", postId);
return "post/postEdit";
}
}
Thymeleaf Security로 View 를 막고 컨트롤러에서는 세션 사용자와 수정할 글의 작성자를 비교해서 일치할 경우만 수정 폼으로 넘겨주도록 바꿨다.