
์นํ์ด์ง์ ๋์์ธ ๋ฐ ์น ํผ๋ธ๋ฆฌ์ฑ ๐ ์คํ๋ง ๊ตฌํ ํ ๋ ํ๋ ์ ์ ํธ์ํฐ์์ ๋ง๋ ์คํ์์ค ๋ถํธ์คํธ๋ฉ์ ํตํด ์๋น๊ฒ ํ ์ ์๋ค.
๋ถํธ์คํธ๋ฉ CDN๐ ๋น ๋ฅด๊ฒ ์๋น์ค ์ ๊ณต, ๋ค์ด๋ก๋๊ฐ ํ์ ์๋ค
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
๋ฐ๋ผ์น์ง๋ง๊ณ ์ง์ ์ฐพ์๋ณด๊ณ ๊ฒ์ํ๋ฉฐ ์ด๋ค ์์ผ๋ก ์ ์ฉ ๋๋์ง ๋ณด๋ฉด์ ํ์
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
<th:block layout:fragment = "script"></th:block>
<th:block layout:fragment = "css"></th:block>
<!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<!-- JS, Popper.js and Jquery -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
</head>
<body>
<div th:replace = "~{fragments/header::header}"></div>
<div layout:fragment = "content" class = "content"></div>
<div th:replace = "~{fragments/footer::footer}"></div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:fragment = "header">
<nav class = "navbar navbar-expand-lg bg-body-tertiary" data-bs-theme="dark">
<div class="container-fluid">
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarTogglerDemo03"
aria-controls="navbarTogglerDemo03"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="/">Shop</a>
<div class="collapse navbar-collapse" id="navbarTogglerDemo03">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/admin/item/new">์ํ ๋ฑ๋ก</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/items">์ํ ๊ด๋ฆฌ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/cart">์ฅ๋ฐ๊ตฌ๋</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/orders">๊ตฌ๋งค์ด๋ ฅ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/members/login">๋ก๊ทธ์ธ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/members/logout">๋ก๊ทธ์์</a>
</li>
</ul>
<form class="d-flex" th:action="@{/}" method="get">
<input name="searchQuery" class="form-control me-2" type="search"
placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</div>
</nav>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class = "footer" th:fragment = "footer">
<footer class="page-footer font-small cyan darken-3">
<div class="footer-copyright text-center py-3">
2024 Shopping Mall TheJ WebSite
</div>
</footer>
</div>
</body>
</html>

๐ ๊ธฐ๋ณธ ํ์ด์ง

๐ ํ๋ ์์ ํ ๊ธ ๊ฐ๋ฅ ๐ฅ๋ถํธ์คํธ๋ฉ์ผ๋ก ๋ฐ์ ํ ์น ์๋์ผ๋ก!

html{
position: relative;
min-height: 100%;
margin: 0;
}
body{
min-height: 100%;
}
.footer{
position: absolute;
left: 0;
right: 0;
bottom: 0;
width: 100%;
padding: 15px 0;
text-align: center;
}
.content{
margin-bottom: 100px;
margin-top: 50px;
margin-left: 200px;
margin-right: 200px;
}
...
<!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link th:href="@{/css/layout1.css}" rel="stylesheet"> ๐ ์ถ๊ฐ
<!-- JS, Popper.js and Jquery -->
...

Spring ๊ธฐ๋ฐ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด์(์ธ์ฆ๊ณผ ์ธ๊ฐ ๋ฑ)์ ๋ด๋นํ๋ ์คํ๋ง ํ์ ํ๋ ์์ํฌ
๐ ์ธ์ฆ/์ธ๊ฐ๋ก ์ต์ํ์ผ๋ก ํด๋ณผ ๊ฒ. ๊ทธ ์ด์์ ์์ธํ ๊ณต๋ถ๋ฅผ ํด์ผํ๋ค.์ผํ๋ชฐ ์์ ์ธ์ฆ/์ธ๊ฐ
์ธ์ฆ์ด ํ์ ์๋ ๊ฒฝ์ฐ: ์ํ ์์ธ ํ์ด์ง ์กฐํ์ธ์ฆ์ด ํ์ํ ๊ฒฝ์ฐ: ์ํ ์ฃผ๋ฌธ๊ด๋ฆฌ์ ๊ถํ์ด ํ์ํ ๊ฒฝ์ฐ: ์ํ ๋ฑ๋ก
์ต์ 3 ๋ฒ์ ์ผ๋ก !
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
๐ ์์กด์ฑ ์ถ๊ฐ


๐
id : user
pw : d4604623-b7de-4a15-9860-b41b7e733282, ์ผํ์ฉ



๐ ๊ฐ๋จํ๊ฒ ์ ์ฉ.
@Configuration
@EnableWebSecurity
์ฝ๋๋ฅผ ์
๋ ฅํ์ธ์
package com.shop.constant;
public enum Role {
USER, ADMIN
}
package com.shop.dto;
import lombok.Getter;
import lombok.Setter;
// ํ์๊ฐ์
Dto
@Getter
@Setter
public class MemberFormDto {
private String name;
private String email;
private String password;
private String address;
}
package com.shop.entity;
import com.shop.constant.Role;
import com.shop.dto.MemberFormDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.crypto.password.PasswordEncoder;
@Entity
@Table (name = "member")
@Getter
@Setter
@ToString
public class Member {
@Id
@Column(name = "member_id")
@GeneratedValue(strategy = GenerationType.AUTO) // autoincrement
private Long id;
private String name;
@Column (unique = true) // ์ค๋ณต X
private String email;
private String password;
private String address;
private String telNumber;
@Enumerated(EnumType.STRING)
private Role role;
// Member๋ฅผ DB ๋ก ๋๊ฒจ์ฃผ๋ ๋ฐฉ์
public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder){
Member member = new Member();
member.setName(memberFormDto.getName());
member.setEmail(memberFormDto.getEmail());
member.setAddress(memberFormDto.getAddress());
member.setTelNumber(memberFormDto.getTelNumber());
String password = passwordEncoder.encode(memberFormDto.getPassword());
member.setPassword(password);
member.setRole(Role.ADMIN);
return member;
}
}
package com.shop.repository;
import com.shop.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemberRepository extends JpaRepository<Member, Long> {
// ์ด๋ฉ์ผ ์ค๋ณต ํ์ธ
// findBy ๋ ์นด๋ฉ ํ๊ธฐ, ๊ทธ๋์ Email
Member findByEmail(String email); // select email, UNIQUE ์ด๋ฏ๋ก NULL ์๋๋ฉด 1
}
package com.shop.service;
import com.shop.entity.Member;
import com.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
// ์๋น์ค
@Service
// ํธ๋์ญ์
์ค์ : ์ฑ๊ณตํ๋ฉด ์ ์ฉ ์คํจํ๋ฉด ๋กค๋ฐฑ
@Transactional
// final ๋๋ @NonNull ๋ช
๋ น์ด๊ฐ ๋ถ์ผ๋ฉด ๊ฐ์ฒด๋ฅผ ์๋์ผ๋ก ๋ถ์ฌ์ค๋ค. @Autowired๊ฐ ํ์ ์๋ค๋ ๋ป
@RequiredArgsConstructor // ๋กฌ๋ณต ์ด๋
ธํ
์ด์
public class MemberService {
//@Autowired
//MemberRepository memberRepository;
private final MemberRepository memberRepository;
// ์ค๋ณต ๊ฒ์ฌ ํ์ ์์ผ๋ฉด ์ ์ฅ
public Member saveMember(Member member) {
validateDuplicateMember(member);
return memberRepository.save(member); // ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ์ ํ๋ผ๋ ๋ช
๋ น
}
// ์ด๋ฉ์ผ ์ค๋ณต ๊ฒ์ฌ ๋ฉ์๋
private void validateDuplicateMember(Member member) {
Member findMember = memberRepository.findByEmail(member.getEmail());
// Controller ์์ try/catch ๋ก ๋์ค๊ฒ
if (findMember != null) {
throw new IllegalStateException("์ด๋ฏธ ๊ฐ์
๋ ํ์์
๋๋ค.");
}
}
}
package com.shop.service;
import com.shop.dto.MemberFormDto;
import com.shop.entity.Member;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@TestPropertySource(locations = "classpath:application-test.properties")
class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
PasswordEncoder passwordEncoder;
public Member createMember(){
MemberFormDto memberFormDto = new MemberFormDto();
memberFormDto.setEmail("test@email.com");
memberFormDto.setName("ํ๊ธธ๋");
memberFormDto.setAddress("์์ธ์ ๋งํฌ๊ตฌ ํฉ์ ๋");
memberFormDto.setPassword("1234");
return Member.createMember(memberFormDto, passwordEncoder);
}
@Test
@DisplayName("ํ์๊ฐ์
ํ
์คํธ")
public void saveMemberTest(){
Member member = createMember();
Member savedMember = memberService.saveMember(member);
// assertEquals ๋ ์์ฒญํ ๊ฐ(member)๊ณผ ์ค์ ์ ์ฅ ๋(savedMember) ๋ฐ์ดํฐ๋ฅผ ๋น๊ต
assertEquals(member.getEmail(), savedMember.getEmail());
assertEquals(member.getName(), savedMember.getName());
assertEquals(member.getAddress(), savedMember.getAddress());
assertEquals(member.getPassword(), savedMember.getPassword());
assertEquals(member.getRole(), savedMember.getRole());
assertEquals(member.getTelNumber(), savedMember.getTelNumber();
System.out.println(savedMember.getEmail());
System.out.println(savedMember.getName());
System.out.println(savedMember.getAddress());
System.out.println(savedMember.getPassword());
System.out.println(savedMember.getRole());
System.out.println(savedMember.getTelNumber());
}
}
ํ ์คํธ JUnit Assert ๋ฉ์๋ (1)
ํ ์คํธ JUnit Assert ๋ฉ์๋ (2)
๋ ๋ค์ํ Assert ๋ผ์ด๋ธ๋ฌ๋ฆฌ JUnit - AssertJ
Hibernate:
select
m1_0.member_id,
m1_0.address,
m1_0.email,
m1_0.name,
m1_0.password,
m1_0.role
from
member m1_0
where
m1_0.email=?
Hibernate:
select
next value for member_seq
test@email.com
ํ๊ธธ๋
์์ธ์ ๋งํฌ๊ตฌ ํฉ์ ๋
$2a$10$4bBxO.1rPw6GV4tAofOZ7efgerYQ2wyujcE53zJ07cZRGM4NsRAXW
ADMIN
๐ Equals ์ด๋ฏ๋ก ์ ์คํ ๋๋ค. ๋ค๋ฅด๋ฉด ์๋ฌ
@Test
@DisplayName("์ค๋ณต ํ์ ๊ฐ์
ํ
์คํธ")
public void saveDuplicateMemberTest(){
Member member1 = createMember();
Member member2 = createMember();
memberService.saveMember(member1);
System.out.println("member1 : " + member1);
// assertThrows ๐ ์์ธ์ฒ๋ฆฌ ์ํฉ์ ํ์ธ ํ๊ธฐ ์ํด ์ฌ์ฉ
// ํ์ดํ ํจ์๋ก ๋ฐ๋ก์คํ
Throwable e = assertThrows(IllegalStateException.class, () -> {
memberService.saveMember(member2);
System.out.println("member2 : " + member2);});
assertEquals("์ด๋ฏธ ๊ฐ์
๋ ํ์์
๋๋ค.", e.getMessage());
}

๐ member1 ์์ validateDuplicateMember ์ ์ํด IllegalStateException("์ด๋ฏธ ๊ฐ์
๋ ํ์์
๋๋ค.") ๋ฐ์

๐ ์ member1์ ์๋ฌ "์ด๋ฏธ ๊ฐ์
๋ ํ์์
๋๋ค." ์ e.getMessage()/IllegalStateException ๊ฐ ๋์ผํ๋ฉด ์คํํ๋ค
Hibernate:
select
next value for member_seq
member1 : Member(id=1, name=ํ๊ธธ๋, email=test@email.com, password=$2a$10$lxRf8mMn.UOlcaK81QVYUeEXD5CtdwmqVeifSbrHLiDpauhF5dnha, address=์์ธ์ ๋งํฌ๊ตฌ ํฉ์ ๋, role=ADMIN)
Hibernate:
insert
into
member
(address, email, name, password, role, member_id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
select
m1_0.member_id,
m1_0.address,
m1_0.email,
m1_0.name,
m1_0.password,
m1_0.role
from
member m1_0
where
m1_0.email=?
๐
package com.shop.controller;
import com.shop.dto.MemberFormDto;
import com.shop.entity.Member;
import com.shop.service.MemberService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private final PasswordEncoder passwordEncoder;
// method = "get" ์ผ ๋ ์คํ
@GetMapping(value = "/new")
public String memberForm(Model model){
model.addAttribute("memberFormDto", new MemberFormDto());
return "member/memberForm";
}
// method = "post" ์ผ ๋ ์คํ DB์ ์ ์ฅ
@PostMapping(value = "/new")
// memberForm ์ค๋ฒ๋ก๋ฉ
public String memberForm(@Valid MemberFormDto memberFormDto, BindingResult bindingResult, Model model){
// ์๋ฌ๋๋ฉด ๋ค์ ๊ฐ์
if(bindingResult.hasErrors()){
return "member/memberForm";
}
try {
Member member = Member.createMember(memberFormDto, passwordEncoder);
memberService.saveMember(member);
// ๋ง์ถฐ๋์ IllegalStateException ์๋ฌ
}catch (IllegalStateException e){
model.addAttribute("errorMessage", e.getMessage());
return "member/memberForm";
}
return "redirect:/";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/layout1}">
<!-- ์ฌ์ฉ์ CSS ์ถ๊ฐ -->
<th:block layout:fragment = "css">
<style>
.fieldError{
color: #bd2130;
}
</style>
</th:block>
<!-- ์ฌ์ฉ์ JS ์ถ๊ฐ-->
<th:block layout:fragment = "script">
<script th:inline = "javascript">
$(document).ready(function(){
var errorMessage = [[${errorMessage}]];
if(errorMessage != null){
alert(errorMessage);
}
});
</script>
</th:block>
<div layout:fragment = "content">
<form action="/members/new" role="form" method="post" th:object = "${memberFormDto}">
<div class = "form-group">
<label th:for = "name">์ด๋ฆ</label>
<input type="text" th:field="*{name}" class="form-control" placeholder="์ด๋ฆ์ ์
๋ ฅํด์ฃผ์ธ์">
<!--์กฐ๊ฑด๋ฌธ์ผ๋ก name ์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ก ์๋ฌ๊ฐ ๋ฐ์ ํ๋ฉด th:errors = "*{name}" ์ ์ํด ("์ด๋ฆ์ด ๋๋ฌด ๊น๋๋ค") ๊ฐ์ name์ ๋ํ ์๋ฌ๋ฅผ ์คํ
Spring validate๋ฅผ ์ง์ ํ๋ฉฐ ์๋ฌ ๋ฐ์์ ์๋ฌ๊ตฌ๋ฌธ์ด Controller์์ ์ค์ง ์์ ๋ Incorrect data ์ถ๋ ฅ -->
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="fieldError">Incorrect date</p>
</div>
<div class="form-group">
<label th:for="email">์ด๋ฉ์ผ์ฃผ์</label>
<input type="text" th:field="*{email}" class="form-control" placeholder="์ด๋ฉ์ผ์ ์
๋ ฅํด์ฃผ์ธ์">
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="fieldError">Incorrect date</p>
</div>
<div class="form-group">
<label th:for="password">์ํธ</label>
<input type="password" th:field="*{password}" class="form-control" placeholder="๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์">
<p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="fieldError">Incorrect
date</p>
</div>
<div class="form-group">
<label th:for="address">์ฃผ์</label>
<input type="text" th:field="*{address}" class="form-control" placeholder="์ฃผ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์">
<p th:if="${#fields.hasErrors('address')}" th:errors="*{address}" class="fieldError">Incorrect date</p>
</div>
<div class="form-group">
<label th:for="telNumber">์ ํ ๋ฒํธ</label>
<input type="text" th:field="*{telNumber}" class="form-control" placeholder="์ ํ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์">
<p th:if="${#fields.hasErrors('telNumber')}" th:errors="*{telNumber}" class="fieldError">Incorrect date</p>
</div>
<div style="text-align: center">
<button type="submit" class="btn btn-success" style="">Submit</button>
</div>
<!-- CSRF๋ฅผ ๋ฐฉ์ดํ๊ธฐ ์ํด ์ถ๊ฐ. POST ๋ฐฉ์์ผ ๋ ์ฌ์ฉํ๋ฉฐ CSRF ํ ํฐ์ด ์์ด์ผํ๋ค.
ํ์ฉํ ์์ฒญ์ด ๋ง๋์ง ํ์ธํ๊ธฐ ์ํ ํ ํฐ ์ด๋ค. -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
</form>
</div>
</html>
package com.shop.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MainController {
@GetMapping(value = "/")
public String main (){
return "main";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/layout1}" lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div layout:fragment = "content">
<h1>๋ฉ์ธ ํ์ด์ง ์
๋๋ค.</h1>
</div>
</body>
</html>
package com.shop.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// ์ ๋ถ ํ๋ฝ
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.authorizeRequests(auth -> auth.requestMatchers("/", "/members/**").permitAll())
.formLogin(formLogin -> formLogin.permitAll())
.logout(logout -> logout.permitAll());
return http.build();
}
// ๋น๋ฐ๋ฒํธ ์ํธํ
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

๐ http://localhost/members/new.์์ง ์น ํ์ด์ง๋ค์ด ๋ฏธ์์ฑ์ด๊ธฐ ๋๋ฌธ์ 403 ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ๋ค๋ก๊ฐ๊ธฐํ๋ฉฐ ์คํํ๋ฉด ๋๋ค

๐ ์ ํจ์ฑ ๊ฒ์ฌ๋ ์ ๋ถ ์ ์๋ํ๋ค. ๋น๋ฐ๋ฒํธ, ์ด๋ฉ์ผ์ ์ ์ธํ๊ณ EMPTY ๊ฐ์๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์คํํ๊ฒ ๋์ด ์๋ค.




๐ ์ ์ถ ํ root ๋ก ์ด๋

๐ ์์ง ROLE ์ ์ธ๋ถ ์ค์ ํ์ง ์์๋ค. ์ ๋ฑ๋ก๋ ์ด๋ฉ์ผ๋ก ๊ฐ์
์ ์งํํ๋ฉด




โ ๊ตฌ์กฐ ํ์ ์ ์ ํ๋ฉด์ ๋์ด๊ฐ์ผ ๋ ๋ณต์กํด ์ง ๋ ์ดํด๋ฅผ ํ ์ ์๋ค. ๊ณ์ ์ดํดํ์ง ๋ชปํ๋ฉด ๋๋ฉ์ด ์ฒ๋ผ ๊ตด๋ฌ๊ฐ ์ดํด๊ฐ ๋ถ๊ฐ ํ ๊ฒ
memberForm.htmlํ์๊ฐ์submit์ผ๋กform action
MemberFormDto๋ฅผController์ ์ ๋ฌth:object = "${memberFormDtd}"๋กdto์th:field = "*{name}","*{email}","*{password}","*{address}","*{telNumber}"์ด ์ฐ๊ฒฐ๋์ด ์ด๋ฆ๊ณผ ๋ง๋ ๋ณ์์ ๋์
Controller์@Valid๊ฐ ๋ถ์memberFormDto๋ก ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์คํํ๋ค.BindingResult๊ฐ์ฒด๋ฅผ ํตํด ๋ฐ์ธ๋ฉ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๊ณ , ์๋ฌ๊ฐ ์์ผ๋ฉด"member/memberForm"๋ทฐ๋ก ๋ค์ ์ด๋
Service๋ก ์ด๋ ์ด ๋MemberFormDto๋ฅผMember๋ก ๋ฐ๋๋ค
saveMember์คํvalidateDuplicateMemberMemberRepository์findByEmail๋ก ์ค๋ณต ์ฒดํฌ
- ๋ฌธ์ ์์ผ๋ฉด
createMemberํsaveMember๋ก ์ ์ฅ
- ๋ฌธ์ ์์ผ๋ฉด(์ด๋ฉ์ผ ์ค๋ณต)
try/catch๋กsaveMember์์ ๋ฐ์ "์ด๋ฏธ ๊ฐ์ ๋ ํ์์ ๋๋ค." ๋ฅผModel๋ก ๋ณด๋ด[[${errorMessage}]];์ ๋ด์alert(errorMessage)์ถ๋ ฅ