Day 48 - Spring Security

haxxru log;·2026년 5월 11일
post-thumbnail

이 글은 2026년 05월 11일 작성된 글입니다.

오늘은 Thymeleaf 기반 폼 처리 개선, 게시글 목록과 상세 리다이렉트,
그리고 Spring Security와 회원가입 기능까지 정리했다.


1. Thymeleaf 도입

기존에는 컨트롤러에서 HTML을 직접 만들거나 문자열을 조립하는 방식에 가까웠지만,
이제는 Thymeleaf 템플릿 파일을 사용해서 화면을 분리했다.

<form action="/posts/write" method="post">
    <input type="text" name="title">
    <textarea name="content"></textarea>
    <button type="submit">등록</button>
</form>
  • Controller는 요청을 처리한다.
  • Thymeleaf는 화면을 렌더링한다.
  • 역할이 더 명확해진다.

2. th:text와 th:utext

Thymeleaf에서 값을 출력할 때는 th:textth:utext를 사용할 수 있다.

문법설명
th:textHTML 특수문자를 이스케이프해서 출력
th:utextHTML 태그를 해석해서 출력
<span th:text="${user.name}"></span>
<span th:utext="${htmlContent}"></span>

일반 텍스트는 th:text를 사용하는 것이 안전하다.
th:utext는 HTML을 그대로 렌더링하므로 신뢰할 수 있는 데이터에만 사용해야 한다.


3. GET 요청에서도 Form 객체가 필요한 이유

글 작성 페이지에 처음 들어올 때도 폼 객체가 필요하다.

@GetMapping("/posts/write")
public String showWrite(WriteForm form) {
    return "post/write";
}

Thymeleaf에서 form.title 같은 값을 사용하려면
처음 화면을 보여줄 때도 form 객체가 존재해야 한다.

  • GET 요청에서는 빈 form 객체가 필요하다.
  • POST 실패 시에는 사용자가 입력한 form 객체가 필요하다.

4. writeForm?.title 과 writeForm.title

Thymeleaf에서는 null 안전 접근을 위해 ?.를 사용할 수 있다.

<input th:value="${writeForm?.title}">
표현식특징
writeForm?.titlewriteForm이 null이어도 에러를 막는다.
writeForm.titlewriteForm이 null이면 에러가 발생할 수 있다.

초기 GET 요청에서 form 객체가 없을 수 있다면 ?.를 쓰면 안전하다.
하지만 form 객체를 항상 모델에 넣는 구조라면 직접 접근해도 된다.


5. @ModelAttribute("form")

폼 객체 이름을 명확하게 지정하기 위해 @ModelAttribute("form")을 사용했다.

public String write(@ModelAttribute("form") WriteForm form) {
    return "post/write";
}

이렇게 하면 Thymeleaf에서 writeForm 대신 form이라는 이름으로 접근할 수 있다.

<input th:value="${form.title}">

6. POST URL 정리

처음에는 작성 처리 URL을 따로 두었다.

POST /posts/doWrite

하지만 POST 자체에 이미 생성이라는 의미가 있기 때문에
다음처럼 정리하는 것이 더 자연스럽다.

GET  /posts/write
POST /posts/write

같은 URL이라도 HTTP 메서드가 다르면 스프링은 서로 다른 액션으로 구분할 수 있다.

  • GET /posts/write는 작성 폼을 출력한다.
  • POST /posts/write는 작성 처리를 담당한다.

7. th:object와 th:field

폼 관련 중복을 줄이기 위해 th:objectth:field를 사용했다.

<form th:object="${form}" method="post">
    <input th:field="*{title}">
    <textarea th:field="*{content}"></textarea>
</form>

th:object를 사용하면 내부에서 form. 접두어를 생략할 수 있다.

th:field는 다음 속성들을 자동으로 처리해준다.

  • name
  • id
  • value

8. #fields로 에러 메시지 출력

Validation 실패 시 Thymeleaf에서 에러 메시지를 출력했다.

<div th:if="${#fields.hasAnyErrors()}">
    <div th:each="err : ${#fields.allErrors()}" th:text="${err}"></div>
</div>

서버에서 검증한 결과를 사용자에게 다시 보여줄 수 있다.

  • 검증 실패 메시지를 출력할 수 있다.
  • 폼 화면을 유지할 수 있다.
  • 사용자 입력 흐름이 더 자연스러워진다.

9. 등록 후 상세 페이지로 리다이렉트

게시글 등록이 끝나면 목록이 아니라
생성된 게시글의 상세 페이지로 이동하도록 처리했다.

return "redirect:/posts/" + post.getId();

등록 결과를 바로 확인할 수 있어서 사용자 흐름이 더 자연스럽다.
HTTP 응답 코드 기준으로 보면 리다이렉트는 3xx 응답에 해당한다.


10. 게시글 목록 구현

게시글 목록 페이지를 구현했다.

<tr th:each="post : ${posts}">
    <td th:text="${post.id}"></td>
    <td>
        <a th:href="@{|/posts/${post.id}|}" th:text="${post.title}"></a>
    </td>
</tr>

목록에서 상세 페이지로 이동할 수 있도록 링크도 함께 연결했다.


11. Member 도메인 추가

회원 기능을 위해 Member 도메인을 추가했다.

  • Member 엔티티
  • MemberRepository
  • MemberService

게시글 기능과 마찬가지로
회원 기능도 도메인 단위로 분리해서 관리한다.


12. Spring Security 의존성 추가

회원 기능과 인증 처리를 위해 Spring Security를 추가했다.

implementation("org.springframework.boot:spring-boot-starter-security")

Security를 추가하면 기본적으로 인증/인가 기능이 적용된다.


13. SecurityConfig 설정

Security 설정을 직접 구성하기 위해 SecurityConfig를 만들었다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
}

기본 보안 설정을 프로젝트에 맞게 수정하기 위한 단계이다.


14. h2-console 접근 허용

Spring Security가 적용되면 h2-console 접근도 막힐 수 있다.
그래서 개발 중에는 h2-console에 접근할 수 있도록 SecurityConfig에서 허용했다.

  • 개발 편의성을 확보할 수 있다.
  • DB 상태를 브라우저에서 확인할 수 있다.

15. 회원가입 기능 구현

회원가입 기능을 구현하고 테스트 회원 3명을 생성했다.

회원가입에서는 다음 값을 받는다.

  • username
  • password
  • nickname 또는 name

16. 회원가입 폼 구현

회원가입 입력 화면을 만들었다.

<form th:object="${form}" method="post">
    <input th:field="*{username}">
    <input th:field="*{password}" type="password">
    <input th:field="*{nickname}">
    <button type="submit">회원가입</button>
</form>

17. 회원가입 Validation

회원가입 폼에도 validation을 적용했다.

@NotBlank
private String username;

@NotBlank
private String password;

빈 값이나 잘못된 값이 들어오지 않도록 서버에서 검증했다.


18. 회원가입 데이터 검증

단순히 빈 값만 확인하는 것이 아니라
회원가입 처리 과정에서 필요한 데이터 검증도 추가했다.

예를 들면 다음과 같은 검증이 필요하다.

  • 아이디 중복 여부
  • 비밀번호 조건
  • 필수 입력값 확인

19. 회원가입 검증 리팩토링

회원가입 검증 로직을 정리하여
컨트롤러가 너무 복잡해지지 않도록 개선했다.

검증과 저장 로직을 적절히 분리하면
회원 기능이 커져도 유지보수하기 쉬워진다.


✅ 정리

  • Thymeleaf를 사용하면서 컨트롤러와 화면의 역할을 더 명확하게 나눌 수 있었다.
  • th:objectth:field를 사용하면 폼 코드를 훨씬 간결하게 작성할 수 있다.
  • Validation과 #fields를 함께 사용하면 사용자에게 에러 메시지를 자연스럽게 보여줄 수 있다.
  • GET과 POST를 같은 URL로 두고 메서드로 구분하면 REST 흐름에 더 가까운 구조가 된다.
  • Spring Security를 추가하면서 회원가입과 인증 기능을 위한 기반을 만들 수 있었다.

0개의 댓글