현재 구현하고 있는 프로젝트는 회원의 관심주제라는 서비스가 필요하다. 따라서 회원(Account)와 관심주제(Tag) 는 연관관계를 맺고 있다. 그리고 관심주제에 대한 등록 ,삭제는 tagify를 사용해서 구현하였다.
npm install @yaireo/tagify
위와 같이 CMD창에 ls 를 치면 현재 경로에 package.json이 존재하는 위치에 위의 명령어로 tagify를 다운 받아주자.
<input id="tags" type="text" name="tags" th:value="${#strings.listJoin(tags,',')}"
class="tagify-outside" aria-describedby="tagHelp"/>
<script type="application/javascript" th:inline="javascript">
$(function() {
function tagRequest(url, tagTitle) {
$.ajax({
dataType: "json",
autocomplete: {
enabled: true,
rightKey: true,
},
contentType: "application/json; charset=utf-8",
method: "POST",
url: "/settings/tags" + url,
data: JSON.stringify({'tagTitle': tagTitle})
}).done(function (data, status) {
console.log("${data} and status is ${status}");
});
}
function onAdd(e) {
tagRequest("/add", e.detail.data.value);
}
var tagInput = document.querySelector("#tags");
var tagify = new Tagify(tagInput, {
pattern: /^.{0,20}$/,
dropdown : {
enabled: 1,
}
});
tagify.on("add", onAdd);
// add a class to Tagify's input element
tagify.DOM.input.classList.add('form-control');
// re-place Tagify's input element outside of the element (tagify.DOM.scope), just before it
tagify.DOM.scope.parentNode.insertBefore(tagify.DOM.input, tagify.DOM.scope);
});
</script>
우선 등록을 Ajax를 통해 Post방식의 url은 "/settings/tags/add"로 요청하도록 코드를 짰다. 하지만 여기서 문제가 발생했다. 바로 시큐리티 적용으로 인해 CSRF Token을 추가 적으로 설정해주어야했다.
CSRF Token이란?
CSRF Token은 임의의 난수를 생성하고 세션에 저장한다.
그리고 사용자의 매 요청마다 해당 난수 값을 포함시켜서 전송시킨다.
이후 백엔드에서는 요청을 받을 때 마다 세션에 저장된 토큰값과 요청 파라미터에 전달된 토큰 값이 같은지 검사한다.
스프링 시큐리티에서는 공식적으로 이 CSRF 공격에 대한 방어 기능을 시큐리티 모듈에서 제공해주고 있다.
<script type="application/javascript" th:inline="javascript" th:fragment="ajax-csrf-header">
$(function() {
var csrfToken = /*[[${_csrf.token}]]*/ null;
var csrfHeader = /*[[${_csrf.headerName}]]*/ null;
$(document).ajaxSend(function (e, xhr, options) {
xhr.setRequestHeader(csrfHeader, csrfToken);
});
});
</script>
HTTPmessage Body로 들어오는 요청값을 받기위해 @RequestBody TagForm tagForm로 요청을 받는다
@PostMapping("/settings/tags/add")
@ResponseBody
public ResponseEntity addTags(@CurrentUser Account account, @RequestBody TagForm tagForm) {
log.info("에이작스 요청 :{}",tagForm);
//태그 제목이 없을 시에 save
Tag tag = tagService.findOrCreateNew(tagForm.getTagTitle());
accountService.addTag(account, tag);//연관관계 편의 메서드
return ResponseEntity.ok().build();
}
아래의 메서드는 tagService단에서 이미 관심주제를 등록했는지 검사하는 메서드이다. 존재한다면 조회 후 반환하고 존재하지 않는다면 save를 한다.
@Service
@Transactional
@RequiredArgsConstructor
public class TagService {
private final TagRepository tagRepository;
public Tag findOrCreateNew(String tagTitle) {
Tag tag = tagRepository.findByTitle(tagTitle);
if (tag == null) {
tag = tagRepository.save(Tag.builder().title(tagTitle).build());
}
return tag;
}
}
등록 후 저장된 관심주제
strings.listJoin
문자열 리스트를 ' , ' 를 사용해서 변환해준다.
관심주제를 문자열 List로 MODEL에서 넘겨주기 때문에 ${#strings.listJoin(tags,',')}를 사용해서 등록 후 페이지에 조회하게 구현했다.EX) List tags = List.of("Spring", "JPA");
=> Spring, JPA
<input id="tags" type="text" name="tags" th:value="${#strings.listJoin(tags,',')}" class="tagify-outside" aria-describedby="tagHelp"/>