DevLog ๐Ÿ˜ถ

[Spring] ์ƒํ’ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์— ํšŒ์›๊ฐ€์ž… / ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ๋กœ์ง ์ถ”๊ฐ€ํ•˜๊ธฐ, ์ฟ ํ‚ค๋ฅผ ํ†ตํ•œ ์‹๋ณ„ ์ฒ˜๋ฆฌ ๋ณธ๋ฌธ

Back-end/Spring

[Spring] ์ƒํ’ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์— ํšŒ์›๊ฐ€์ž… / ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ๋กœ์ง ์ถ”๊ฐ€ํ•˜๊ธฐ, ์ฟ ํ‚ค๋ฅผ ํ†ตํ•œ ์‹๋ณ„ ์ฒ˜๋ฆฌ

dolmeng2 2022. 8. 23. 17:13

๊น€์˜ํ•œ ๋‹˜์˜ '์Šคํ”„๋ง MVC 2ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ™œ์šฉ ๊ธฐ์ˆ '์„ ๋ณด๊ณ  ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค ๐Ÿ˜Š

 

์Šคํ”„๋ง MVC 2ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ™œ์šฉ ๊ธฐ์ˆ  - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๋ชจ๋“  ์›น ๊ธฐ์ˆ ์„ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ์ดํ•ดํ•˜๊ณ , ์™„์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MVC 2ํŽธ์—์„œ๋Š” MVC 1ํŽธ์˜ ํ•ต์‹ฌ ์›๋ฆฌ์™€ ๊ตฌ์กฐ ์œ„์— ์‹ค๋ฌด ์›น ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๋ชจ๋“  ํ™œ์šฉ ๊ธฐ์ˆ ๋“ค์„ ํ•™์Šตํ•  ์ˆ˜ ์žˆ

www.inflearn.com


- ์ง€๋‚œ ํฌ์ŠคํŒ…๊ณผ ์ด์–ด์ง‘๋‹ˆ๋‹ค :D

 

[Spring] Bean Validation - ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ๊ฒ€์ฆ ์ง„ํ–‰ํ•˜๊ธฐ

๊น€์˜ํ•œ ๋‹˜์˜ '์Šคํ”„๋ง MVC 2ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ™œ์šฉ ๊ธฐ์ˆ '์„ ๋ณด๊ณ  ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค ๐Ÿ˜Š ์Šคํ”„๋ง MVC 2ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ™œ์šฉ ๊ธฐ์ˆ  - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๋ชจ๋“  ์›น ๊ธฐ์ˆ ์„

cl8d.tistory.com


 

| ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ์ง„ํ–‰ํ•˜๊ธฐ

- ์ง€๋‚œ ํฌ์ŠคํŒ…๋ถ€ํ„ฐ ๊ณ„์†ํ•ด์„œ ์ œ์ž‘ํ•ด์™”๋˜ ์ƒํ’ˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์— ํšŒ์› ๊ฐ€์ž… ์‹œ์Šคํ…œ์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

 

โœ” ๊ฐ„๋‹จํ•œ ํ™ˆ ํ™”๋ฉด ์ œ์ž‘ํ•˜๊ธฐ

[LHomeController.java]


  
@Controller
@RequiredArgsConstructor
@RequestMapping("/login")
public class LHomeController {
@GetMapping("/")
public String home() {
return "login/home";
}
}

- ๊ฐ„๋‹จํ•œ ๋ฉ”์ธ ํ™ˆ ํ™”๋ฉด์ด๋‹ค.

 

[home.html] - /resources/login


  
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="mt-3 py-2 text-center">
<h1 class="font-monospace fw-bold mb-3"
th:text="#{page.home}"> ITEM SERVICE HOME :D</h1>
<hr class="my-4">
</div>
<div class="row">
<div class="text-center float-end mb-2 fw-bold" th:text="#{lang}">์–ธ์–ด ์„ ํƒ</div>
<select id="lang" name="lang" class="form-select mb-4"
onchange="if(this.value) location.href=(this.value);">
<option value="" th:text="#{select.lang.default}">== SELECT LANGUAGE TYPE ==</option>
<option value="?lang=en" th:text="#{select.lang.en}">์˜์–ด</option>
<option value="?lang=ja" th:text="#{select.lang.ja}">์ผ๋ณธ์–ด</option>
</select>
<hr class="my-4">
<img class="my-2 mb-5"
src="https://cdn.discordapp.com/attachments/805265715179159623/1011307630284783697/jerryHello.gif"
alt="hello">
<div class="col">
<button class="font-monospace w-100 btn btn-secondary btn-lg" type="button"
th:text="#{label.login.join}"
th:onclick="|location.href='@{/login/members/add}'|">
JOIN
</button>
</div>
<div class="col">
<button class="font-monospace w-100 btn btn-dark btn-lg"
onclick="location.href='items.html'"
th:text="#{label.login.login}"
th:onclick="|location.href='@{/login/members/login}'|" type="button">
LOGIN
</button>
</div>
</div>
<hr class="my-4">
</div>
</body>
</html>

- ๊ธฐ์กด์— items.html์— ์žˆ์—ˆ๋˜ ์–ธ์–ด ์„ ํƒ ๋กœ์ง์„ ํ™ˆ ํ™”๋ฉด์œผ๋กœ ์˜ฎ๊ฒผ๋‹ค.

- ๋˜ํ•œ, ์ „๋ฐ˜์ ์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ํ™œ์šฉํ•œ ๊ตญ์ œํ™”๋ฅผ ์ ์šฉํ•˜์˜€๋‹ค.

- ์ด๋ฏธ์ง€๋Š” ๊ทธ๋ƒฅ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค... ๋„ˆ๋ฌด ํ—ˆ์ „ํ•ด๋ณด์—ฌ์„œ...

 

โœ” ๋ฉค๋ฒ„ ๋„๋ฉ”์ธ ์ œ์ž‘ํ•˜๊ธฐ

[LMember.java]


  
@Getter
public class LMember {
private Long id;
private String loginId;
private String name;
private String password;
public void setId(Long id) {
this.id = id;
}
@Builder(builderMethodName = "createMember")
public LMember(String loginId, String name, String password) {
this.loginId = loginId;
this.name = name;
this.password = password;
}
}

- ํ•˜๋‚˜์˜ ํ”„๋กœ์ ํŠธ์— ํ•˜๋‹ค ๋ณด๋‹ˆ ์ด๋ฆ„์„ ์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜์˜€๋‹ค...!

- @Setter๋ฅผ ์—ด์ง€ ์•Š๊ณ  ๋กฌ๋ณต์˜ Builder ํŒจํ„ด๊ณผ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ setter๋ฅผ ์ถ”๊ฐ€ํ•˜๋„๋ก ์ง„ํ–‰ํ•˜์˜€๋‹ค.

 

[LMemberService.java]


  
@Service
@RequiredArgsConstructor
public class LMemberService {
private final LMemberRepository repository;
public LMember save(LMemberSaveRequest saveRequest) {
LMember member = LMember.createMember()
.loginId(saveRequest.getLoginId())
.name(saveRequest.getName())
.password(saveRequest.getPassword())
.build();
return repository.save(member);
}
}

- ํšŒ์› ์ €์žฅ ๋กœ์ง์ด๋‹ค. ์„œ๋น„์Šค๋‹จ์—์„œ DTO->์‹ค์ œ entity๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์ž‘์—…์„ ์ง„ํ–‰ํ•œ๋‹ค.

 

[LMemberRepository.java]


  
@Repository
public class LMemberRepository {
private static Map<Long, LMember> store = new ConcurrentHashMap<>();
private static AtomicLong sequence = new AtomicLong(0L);
public LMember save(LMember member) {
member.setId(sequence.incrementAndGet());
store.put(member.getId(), member);
return member;
}
public Optional<LMember> findByLoginId (String loginId) {
return findAll().stream()
.filter(m -> m.getLoginId().equals(loginId))
.findFirst();
}
public LMember findById(Long id) {
return store.get(id);
}
public List<LMember> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore() {
store.clear();
}
}

- ๊ธฐ์กด์˜ ์ƒํ’ˆ ์ €์žฅ ์‹œ์Šคํ…œ๊ณผ ๊ฑฐ์˜ ์œ ์‚ฌํ•˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์งฐ๋‹ค.

 

 

โœ” ํšŒ์›๊ฐ€์ž… ์‹œ์Šคํ…œ ์ œ์ž‘ํ•˜๊ธฐ

[LMemeberSaveRequest.java]


  
@Data
public class LMemberSaveRequest {
private Long id;
@NotEmpty
private String loginId;
@NotEmpty
private String password;
@NotEmpty
private String name;
public LMemberSaveRequest(String loginId, String password, String name) {
this.loginId = loginId;
this.password = password;
this.name = name;
}
}

- ํšŒ์› ์ €์žฅ ์‹œ ๋ชจ๋“  ์ •๋ณด๊ฐ€ ํ•„์ˆ˜๋กœ ๋“ค์–ด์™€์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— @NotEmpty๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

- ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์ƒ์„ฑ์ž ํ•˜๋‚˜๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

 

[LMemberController.java]


  
@Controller
@RequiredArgsConstructor
@RequestMapping("/login/members")
public class LMemberController {
private final LMemberService service;
// ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
@PostConstruct
public void addMember() {
service.save(new LMemberSaveRequest("test@naver.com", "test1234", "Spring"));
}
@GetMapping("/add")
public String addForm(@ModelAttribute("member") LMember member) {
// ํƒ€์ž„๋ฆฌํ”„์—์„œ th:object์—์„œ member๋ฅผ ๋„ฃ์–ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—
// GET, POST ๋ฐฉ์‹ ๋ชจ๋‘ addMemberForm์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด @ModelAttribute ์‚ฌ์šฉ
// ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ์–ด์ฐจํ”ผ ์‚ฌ์šฉ์ž์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋„˜์–ด์˜ค์ง€๋Š” ์•Š๊ธฐ ๋•Œ๋ฌธ์—
// ModelAttribute๊ฐ€ ๋นˆ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์—
// model.addAttribute("member", new LMember())๋กœ ์จ๋„ ๋ฌด๋ฐฉํ•˜๋‹ค.
return "login/members/addMemberForm";
}
@PostMapping("/add")
public String save(@Valid @ModelAttribute("member") LMemberSaveRequest saveRequest,
BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "login/members/addMemberForm";
}
service.save(saveRequest);
return "redirect:/login/";
}
}

- ํ…Œ์ŠคํŠธ์šฉ ๋ฉค๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค. (์–ด์ฐจํ”ผ ์•„์ดํ…œ์€ SItemController์—์„œ ์ถ”๊ฐ€ํ•ด์ฃผ๋‹ˆ๊นŒ)

๐Ÿšฉ addForm ๋ถ€๋ถ„์—์„œ ๊ตณ์ด @ModelAttribute๋ฅผ ์จ์•ผ ํ•˜๋‚˜? ๋ผ๋Š” ์ƒ๊ฐ์„ ํ–ˆ์—ˆ๋Š”๋ฐ, 

์–ด์ฐจํ”ผ ์‚ฌ์šฉ์ž์˜ ๊ฐ’์ด ๋„˜์–ด์˜ค์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— @ModelAttribute๊ฐ€ LMember ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ๋•Œ ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์ด new LMember()๋กœ ๋งŒ๋“ ๋‹ค.

- ๊ทธ๋ž˜์„œ ๊ทธ๋ƒฅ Model์„ ์„ ์–ธํ•œ ๋‹ค์Œ model.addAttribute("member", new LMember())๋กœ ์จ๋„ ๋ฌด๋ฐฉํ•˜๋‹ค!

 

[addMemberForm.html]


  
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="mt-5 mb-2 text-center">
<h2 class="font-monospace fw-bold" th:text="#{label.login.join}">JOIN</h2>
</div>
<hr class="my-4">
<form action="" th:action th:object="${member}" method="post">
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error text-center fw-bold"
th:each="err: ${#fields.globalErrors()}"
th:text="${err}">
์ „์ฒด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€</p>
</div>
<div class="row fw-bold font-monospace mb-2">
<div class="col text-center">
<label for="loginId">ID</label>
<input type="text"
id="loginId"
th:field="*{loginId}"
class="form-control text-center"
th:errorclass="field-error"
placeholder="Please Enter the Login ID.">
<div class="field-error"
th:errors="*{loginId}">
</div>
</div>
</div>
<div class="row fw-bold font-monospace mb-2">
<div class="col text-center">
<label for="password" th:text="#{label.login.password}">PASSWORD</label>
<input type="text"
id="password"
th:field="*{password}"
class="form-control text-center"
th:errorclass="field-error"
placeholder="Please Enter the Password.">
<div class="field-error"
th:errors="*{password}">
</div>
</div>
</div>
<div class="row fw-bold font-monospace mb-2">
<div class="col text-center">
<label for="name" th:text="#{label.login.name}">NAME</label>
<input type="text"
id="name"
th:field="*{name}"
class="form-control text-center"
th:errorclass="field-error"
placeholder="Please Enter the Name.">
<div class="field-error"
th:errors="*{name}">
</div>
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-warning btn-lg"
type="submit">
โญ•
</button>
</div>
<div class="col">
<button class="w-100 btn btn-danger btn-lg"
th:onclick="|location.href='@{/login/}'|"
type="button">
โŒ
</button>
</div>
</div>
</form>
</div>
</body>
</html>

- ํšŒ์› ๊ฐ€์ž… ํผ์ด๋‹ค. ๋”ฐ๋กœ ๋””์ž์ธํ•˜๊ธฐ ๊ท€์ฐฎ์•„์„œ ๊ทธ๋ƒฅ ์ƒํ’ˆ ๋“ฑ๋ก ํผ์ด๋ž‘ ์œ ์‚ฌํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.

 

โœ” ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ ์ œ์ž‘ํ•˜๊ธฐ

[LMemberLoginRequest.java]


  
@Data
public class LMemberLoginRequest {
@NotEmpty
private String loginId;
@NotEmpty
private String password;
}

 

[LMemberService.java] - ์ถ”๊ฐ€


  
public LMember login(LMemberLoginRequest loginRequest) {
return repository.findByLoginId(loginRequest.getLoginId())
.filter(m -> m.getPassword().equals(loginRequest.getPassword()))
.orElse(null);
}

-  ์ €์žฅ๋œ id, pwd๊ฐ€ ๋‹ค๋ฅผ ๊ฒฝ์šฐ Member ์—”ํ‹ฐํ‹ฐ๋ฅผ null๋กœ ๋ฆฌํ„ดํ•œ๋‹ค.

 

[LMemberController.java] - ์ถ”๊ฐ€


  
@GetMapping("/login")
public String loginForm(@ModelAttribute("loginForm") LMemberLoginRequest loginRequest) {
return "login/members/loginForm";
}
@PostMapping("/login")
public String login(@Valid @ModelAttribute("loginForm") LMemberLoginRequest loginRequest,
BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "login/members/loginForm";
}
LMember loginMember = service.login(loginRequest);
if(loginMember == null) {
bindingResult.reject("loginFail", "์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
return "login/members/loginForm";
}
return "redirect:/login/";
}

- 1์ฐจ์ ์œผ๋กœ ๊ธ€๋กœ๋ฒŒ ์˜ค๋ฅ˜ (@NotEmpty)๋ฅผ ์ฒดํฌํ•˜๊ณ , ๋งŒ์•ฝ ์„œ๋น„์Šค์—์„œ member๊ฐ€ null๋กœ ๋ฆฌํ„ด๋œ๋‹ค๋ฉด bindingResult์— ํ•ด๋‹น ๊ฒฐ๊ณผ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

[loginForm.html]


  
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="mt-5 mb-2 text-center">
<h2 class="font-monospace fw-bold" th:text="#{label.login.login}">LOGIN</h2>
</div>
<hr class="my-4">
<form action="" th:action th:object="${loginForm}" method="post">
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error text-center fw-bold"
th:each="err: ${#fields.globalErrors()}"
th:text="${err}">
์ „์ฒด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€</p>
</div>
<div class="row fw-bold font-monospace mb-2">
<div class="col text-center">
<label for="loginId">ID</label>
<input type="text"
id="loginId"
th:field="*{loginId}"
class="form-control text-center"
th:errorclass="field-error"
placeholder="Please Enter the Login ID.">
<div class="field-error"
th:errors="*{loginId}">
</div>
</div>
</div>
<div class="row fw-bold font-monospace mb-2">
<div class="col text-center">
<label for="password" th:text="#{label.login.password}">PASSWORD</label>
<input type="password"
id="password"
th:field="*{password}"
class="form-control text-center"
th:errorclass="field-error"
placeholder="Please Enter the Password.">
<div class="field-error"
th:errors="*{password}">
</div>
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-warning btn-lg"
type="submit">
โญ•
</button>
</div>
<div class="col">
<button class="w-100 btn btn-danger btn-lg"
th:onclick="|location.href='@{/login/}'|"
type="button">
โŒ
</button>
</div>
</div>
</form>
</div>
</body>
</html>

- ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์ด๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํšŒ์›๊ฐ€์ž…์ด๋ž‘ ๊ฑฐ์˜ ๋˜‘๊ฐ™์ด ์ œ์ž‘ํ•˜์˜€๋‹ค.

- ๊ธ€๋กœ๋ฒŒ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ฐ ID/PWD ๊ฒ€์ฆ๋„ ์„ฑ๊ณต์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 


| ์ฟ ํ‚ค๋ฅผ ์ด์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌํ•˜๊ธฐ

- ๊ธฐ๋ณธ์ ์ธ ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

- ๊ทธ๋Ÿฌ๋‚˜, ๋‹จ์ˆœํžˆ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ณ„์†ํ•ด์„œ ์†Œ๋ชจํ•˜๋ฉด์„œ ๋ณด๋‚ด๋Š” ๊ฒƒ์€ ๋ฒˆ๊ฑฐ๋กญ๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๋Š” HTTP ์‘๋‹ต์— ์ฟ ํ‚ค๋ฅผ ๋‹ด์„ ์˜ˆ์ •์ด๋‹ค.

- โญ ๋ธŒ๋ผ์šฐ์ €๋Š” ํ•ด๋‹น ์ฟ ํ‚ค ๊ฐ’์„ ๋ฐ›์•„์„œ ์ฟ ํ‚ค ์ €์žฅ์†Œ์— ๋„ฃ์–ด๋‘๊ณ , ์•ž์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ๋งˆ๋‹ค ์ฟ ํ‚ค ๊ฐ’์„ ํ•จ๊ป˜ ๋ณด๋‚ด์ค„ ๊ฒƒ์ด๋‹ค.

- ๊ทธ๋Ÿผ ์„œ๋ฒ„๋Š” ์š”์ฒญ๊ณผ ํ•จ๊ป˜ ์˜จ ์ฟ ํ‚ค ๊ฐ’์„ ํ†ตํ•ด ํšŒ์›์„ ์‹๋ณ„ํ•˜๊ณ , ๊ทธ์— ๋งž๋Š” ํ–‰๋™์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค!

- ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ข…๋ฃŒ๋˜๋ฉด ๋กœ๊ทธ์•„์›ƒ์ด ๋˜๋„๋ก ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ๊ตฌํ˜„ํ•˜์ž.

 

[LMemberController.java] - ์ˆ˜์ •


  
@PostMapping("/login")
public String login(@Valid @ModelAttribute("loginForm") LMemberLoginRequest loginRequest,
BindingResult bindingResult, HttpServletResponse response) {
if(bindingResult.hasErrors()) {
return "login/members/loginForm";
}
LMember loginMember = service.login(loginRequest);
if(loginMember == null) {
bindingResult.reject("loginFail", "์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
return "login/members/loginForm";
}
Cookie cookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
cookie.setPath("/");
response.addCookie(cookie);
return "redirect:/login/";
}

- ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์ฟ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  HttpServletResponse์— ๋‹ด์•„์ค€๋‹ค.

- ์ด๋•Œ, ์ฟ ํ‚ค ์ด๋ฆ„์„ memberId๋กœ ์„ค์ •ํ•˜์—ฌ ํšŒ์› Id๋ฅผ ๋‹ด์•„๋‘”๋‹ค.

- ์ฐธ๊ณ ๋กœ, cookie์˜ path๋ฅผ ์ง€์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

๐Ÿšฉ๋ณด๋‹ˆ๊นŒ path ๊ฐ’์„ ๋”ฐ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ฟ ํ‚ค๋ฅผ ์ƒ์„ฑํ–ˆ๋˜ ํŽ˜์ด์ง€์˜ ๊ฒฝ๋กœ๋กœ๋งŒ ์ „์†ก๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

๋ชจ๋“  ๊ฒฝ๋กœ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ cookie ๊ฐ’์„ ์ „๋‹ฌํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

 

[LHomeController.java] - ๋กœ๊ทธ์ธ ์ „์šฉ ํŽ˜์ด์ง€


  
@GetMapping("/")
public String home(@CookieValue(name = "memberId", required = false) Long memberId,
Model model) {
if(memberId == null) {
return "login/home";
}
LMember member = service.findById(memberId);
if(member == null) {
return "login/home";
}
model.addAttribute("member", member);
return "login/loginHome";
}

- ๋‹ค์Œ๊ณผ ๊ฐ™์ด @CookieValue๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟ ํ‚ค์— ์žˆ๋Š” ๊ฐ’์„ ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•˜๋„๋ก ์ œ์ž‘ํ•˜์˜€๋‹ค.

- ์ด๋•Œ, ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋„ ํ™ˆ ํ™”๋ฉด์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— required=false ์กฐ๊ฑด์„ ๊ฑธ์–ด์ฃผ์—ˆ๋‹ค.

 

[loginHome.html]


  
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="mt-5 py-2 text-center">
<h1 class="font-monospace fw-bold mb-3"
th:text="#{start.message(${member.name})}">๐Ÿ‘‹๐Ÿป Hello</h1>
<hr class="my-4">
</div>
<div class="row">
<div class="text-center float-end mb-2 fw-bold" th:text="#{lang}">์–ธ์–ด ์„ ํƒ</div>
<select id="lang" name="lang" class="form-select mb-4"
onchange="if(this.value) location.href=(this.value);">
<option value="" th:text="#{select.lang.default}">== SELECT LANGUAGE TYPE ==</option>
<option value="?lang=en" th:text="#{select.lang.en}">์˜์–ด</option>
<option value="?lang=ja" th:text="#{select.lang.ja}">์ผ๋ณธ์–ด</option>
</select>
<hr class="my-4">
<img class="my-2 mb-5"
src="https://cdn.discordapp.com/attachments/703627120320184320/1011530716741369927/img.gif"
alt="hello">
<div class="col">
<button class="font-monospace w-100 btn btn-secondary btn-lg" type="button"
th:onclick="|location.href='@{/basic/items}'|">
๐Ÿ“‘
</button>
</div>
<div class="col">
<form th:action="@{/login/members/logout}" method="post">
<button class="font-monospace w-100 btn btn-dark btn-lg"
th:onclick="|location.href='@{/login/}'|"
th:text="#{label.login.logout}"
type="submit">
LOGOUT
</button>
</form>
</div>
</div>
<hr class="my-4">
</div>
</body>
</html>

 

- ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž ์ „์šฉ ํ™”๋ฉด์ด๋‹ค. ์ƒ๋‹จ์— ์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„์ด ๋œฌ๋‹ค.

- ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ์ œ๋ฆฌ๋ฅผ, ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ํ†ฐ์„ ๋„์›Œ์ฃผ์—ˆ๋‹ค... ใ…Žใ…Žใ…Ž

 

 

โœ” ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌํ•˜๊ธฐ

- ์„ธ์…˜ ์ฟ ํ‚ค์ด๊ธฐ ๋•Œ๋ฌธ์— ์›น ๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ ์‹œ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ๊ฐ€ ๋œ๋‹ค.

- ํ˜น์€, ์„œ๋ฒ„์—์„œ ์ž„์˜๋กœ ์ฟ ํ‚ค์˜ ์ข…๋ฃŒ ๋‚ ์งœ๋ฅผ 0์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

[LMemberController.java]


  
@PostMapping("/logout")
public String logout(HttpServletResponse response) {
Cookie cookie = new Cookie("memberId", null);
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);
return "redirect:/login/";
}

- ์—ฌ๊ธฐ์„œ ์•ฝ๊ฐ„ ์˜๋ฌธ์ธ ์ ์ด, ๊ธฐ์กด์— ์กด์žฌํ•˜๋Š” ์ฟ ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ๊ตณ์ด Cookie๋ฅผ new๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋‚˜? ๋ผ๋Š” ๊ฒƒ์ด์—ˆ๋Š”๋ฐ

โญ ์ฐพ์•„๋ณด๋‹ˆ๊นŒ cookie์˜ name ์†์„ฑ์ด ๋™์ผํ•  ๊ฒฝ์šฐ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ์ถ”๊ฐ€๋œ ์ฟ ํ‚ค๋งŒ ์ €์žฅ๋œ๋‹ค๊ณ  ํ•œ๋‹ค!

- ๊ทธ๋ž˜์„œ ์–ด์ฐจํ”ผ ์ฟ ํ‚ค 1๊ฐœ๋‹ˆ๊นŒ ๊ทธ๋ƒฅ ์ƒˆ๋กญ๊ฒŒ ์ƒ์„ฑํ•ด์„œ "memberId"์ธ ์ฟ ํ‚ค์˜ ์ข…๋ฃŒ ๋‚ ์งœ๋ฅผ 0์œผ๋กœ ์„ค์ •ํ•˜์˜€๋‹ค.


 

| ์ฟ ํ‚ค์˜ ๋ณด์•ˆ ์ด์Šˆ

- ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฟ ํ‚ค์˜ ๊ฐ’์€ ์ž„์˜๋กœ ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

    - ๊ฐœ๋ฐœ์ž ๋ชจ๋“œ -> Application -> Cookie์—์„œ ํ™•์ธ

- ์ฟ ํ‚ค์— ๋ณด๊ด€๋œ ์ •๋ณด๋Š” ํƒˆ์ทจ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

- ํƒˆ์ทจ๋œ ์ฟ ํ‚ค๋Š” ํ•ด์ปค๊ฐ€ ์•…์˜์ ์ธ ์š”์ฒญ์„ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

- ๊ทธ๋ž˜์„œ ์ฟ ํ‚ค์˜ ๊ฐ’์€ ์ค‘์š”ํ•œ ๊ฐ’์„ ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ , ์‚ฌ์šฉ์ž๋ณ„๋กœ ์˜ˆ์ธก์ด ์•ˆ ๋˜๋Š” ๋žœ๋ค๊ฐ’์„ ๋…ธ์ถœํ•ด์•ผ ํ•œ๋‹ค.

- ์„œ๋ฒ„์—์„œ ํ† ํฐ์„ ๊ด€๋ฆฌํ•ด์„œ ๋žœ๋ค๊ฐ’์„ ํ†ตํ•ด ๋งคํ•‘๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์–ป์–ด์˜ค๋Š” ๊ฒŒ ๋ฐ”๋žŒ์งํ•˜๋‹ค.

- ํ•ด์ปค๊ฐ€ ์ฟ ํ‚ค์˜ ๊ฐ’์„ ํ›”์ณ๊ฐ€๋”๋ผ๋„ ์ง€์† ์‹œ๊ฐ„์„ ์งง๊ฒŒ ํ•œ๋‹ค.

- ํ˜น์€, ํ•ดํ‚น์ด ์˜์‹ฌ๋œ๋‹ค๋ฉด ํ•ด๋‹น ํ† ํฐ์€ ๊ฐ•์ œ๋กœ ์ œ๊ฑฐ์‹œํ‚จ๋‹ค.

 

- ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ฟ ํ‚ค ๋Œ€์‹  ์„ธ์…˜์„ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ๋ฅผ ๋ณ€๊ฒฝํ•ด๋ณด์ž.

Comments