IT 성장일기
[Spring] Fetch API로 사용자 정보 일치 여부 확인하기 (feat. 403 Error) 본문
반응형
Fetch API로 사용자 정보 일치 여부 확인하기 (feat. 403 Error)
비밀번호를 입력받아 실시간으로 사용자의 비밀번호와 일치하는지 검사하는 로직을 구현해 봤습니다.
사용 언어 및 개발환경은 Java 17, Spring boot 3, Spring security, JPA입니다.
Spring Security 로그인 인증 관련 403 Error가 발생해 해결 내용도 함께 작성합니다.😊
Controller
- @PostMapping("/password/check") : 보안의 이점을 가지기 위해 Post 방식으로 데이터를 받습니다.
- @PreAuthorize("isAuthenticated()") : 인증된 사용자만 해당 URL에 접근할 수 있게 하기 위해 적용했습니다.
- @ResponseBody : boolean 값을 Model에 직접 넘겨주기 위해 애너테이션을 적용했습니다.
- 인증정보(Principal 객체)와 비밀번호를 매개변수로 서비스의 메서드를 호출하고 boolean 값을 리턴합니다. (설레네요 ㅠㅠ)
@PostMapping("/user/password/check")
@PreAuthorize("isAuthenticated()")
@ResponseBody
public boolean isPasswordMatching(Principal principal, @RequestParam String password) {
return this.userService.isPasswordMatching(principal, password);
}
Service
- 인증정보와 비밀번호를 넘겨받아 DB에 저장된 사용자 정보를 가져오고 비밀번호 일치여부를 판단합니다.
public boolean isPasswordMatching(Principal principal, String password) {
if (principal == null || principal.getName() == null) {
throw new IllegalArgumentException("Invalid principal");
}
Optional<SiteUser> userOptional = this.userRepository.findByusername(principal.getName());
if (!userOptional .isPresent()) {
throw new DataNotFoundException("User not found.");
}
SiteUser user = userOptional.get();
String storedPassword = user.getPassword();
return passwordEncoder.matches(password, storedPassword);
}
Template
- 현재 사용 중인 비밀번호를 입력받을 input 요소입니다.
<div class="mb-3">
<label for="currentPassword" class="form-label">Current password</label>
<input type="password" class="form-control currentPassword">
</div>
Javascript
- 여러 개의 input 요소를 querySelectorAll 메서드를 이용해 모두 가져옵니다.
- forEach문을 활용해서 각각의 요소에 EventListener를 붙여줬습니다.
- input 창에 입력을 할 때마다 위에 작성한 Controller의 메서드를 호출해 결괏값을 리턴 받아 요소에 표시해 줍니다.
document.addEventListener("DOMContentLoaded", function() {
const passwordInputs = document.querySelectorAll(".currentPassword");
passwordInputs.forEach(input => {
input.addEventListener("input", function() {
const password = input.value;
fetch("/user/password/check", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `password=${encodeURIComponent(password)}`
})
.then(response => response.json())
.then(data => {
if (data === true) {
input.classList.add("border-success");
input.classList.remove("border-danger");
} else {
input.classList.add("border-danger");
input.classList.remove("border-success");
}
})
.catch(error => {
console.error("Error:", error);
});
});
});
});
여기까지 작성했을 때 로직상으로 아무 문제도 없었지만 콘솔창에는 403 에러가 발생했습니다.
콘솔을 뜯어보면 아래와 같이 fetch 요청을 보내는 부분에서 에러가 발생하고 있었습니다.
그래서 방법이 있을 것 아니에요
Spring Security에서 CSRF 방어를 위해서 CSRF 토큰을 사용하는 경우
이렇게 요청을 보낼 때 CSRF 토큰을 포함해야 한다고 합니다.
Security Configuration에서 CSRF를 사용하지 않도록 설정하는 방법도 있었지만
보안상 불리한 경우가 생길 수 있을 것 같아서 토큰을 포함하는 방법으로 해결했습니다.
Template
- head 태그 내부에 CSRF 토큰 정보를 포함시켜 줍니다.
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="_csrf" th:content="${_csrf.token}">
<meta name="_csrf_header" th:content="${_csrf.headerName}">
<link rel="stylesheet" type="text/css" th:href="@{/style.css}">
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
</head>
Javascript
- 요청 시 토큰 값을 header에 담아줍니다.
document.addEventListener("DOMContentLoaded", function() {
const passwordInputs = document.querySelectorAll(".currentPassword");
passwordInputs.forEach(input => {
input.addEventListener("input", function() {
const password = input.value;
const csrfToken = document.querySelector("[name='_csrf']").getAttribute("content"); // CSRF 토큰 가져오기
fetch("/user/password/check", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-CSRF-TOKEN": csrfToken // CSRF 토큰 추가
},
body: `password=${encodeURIComponent(password)}`
})
.then(response => response.json())
.then(data => {
if (data === true) {
input.classList.add("border-success");
input.classList.remove("border-danger");
} else {
input.classList.add("border-danger");
input.classList.remove("border-success");
}
})
.catch(error => {
console.error("Error:", error);
});
});
});
});
결과적으로 해결된 것 같습니다.
도움이 되어드렸다면 구독 / 좋아요 부탁드립니다.👏🏻
감사합니다.🙂
반응형
'Web > Spring' 카테고리의 다른 글
[Spring] There is no getter for property named 'url' in 'class java.lang.String' (1) | 2025.03.11 |
---|---|
[Spring] AJAX로 화면에 뷰 템플릿 추가하기 (2) | 2024.12.13 |
[Spring] 스프링부트에서 서버 재시작 없이 클래스 변경 사항 반영하기 (0) | 2023.09.08 |
[Spring] BeanDefinitionStoreException이 발생하는 이유 (0) | 2023.09.08 |
[Spring] Spring Tool Suite4 설치하기 (0) | 2023.09.08 |