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