03H. ์ํฐํฐ ๋ณ๊ฒฝ
03H. ์ํฐํฐ ๋ณ๊ฒฝ ๊ด๋ จ
๊ฒ์ํ์ ์ง๋ฌธ, ๋ต๋ณ์๋ ๋๊ฐ ๊ธ์ ์์ฑํ๋์ง ์๋ ค์ฃผ๋ "๊ธ์ด์ด" ํญ๋ชฉ์ด ํ์ํ๋ค. ์ด๋ฒ์๋ Question
๊ณผ Answer
์ํฐํฐ์ "๊ธ์ด์ด"์ ํด๋น๋๋ author
์์ฑ์ ์ถ๊ฐํด ๋ณด์.
Question
์์ฑ ์ถ๊ฐ
๋จผ์ Question
์ํฐํฐ์ author
์์ฑ์ ์ถ๊ฐํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/question/
Question.java
// (... ์๋ต ...)
import jakarta.persistence.ManyToOne;
import com.mysite.sbb.user.SiteUser;
// (... ์๋ต ...)
public class Question {
// (... ์๋ต ...)
@ManyToOne
private SiteUser author;
}
author
์์ฑ์ SiteUser
์ํฐํฐ๋ฅผ @ManyToOne
์ผ๋ก ์ ์ฉํ๋ค.
์ฌ๋ฌ๊ฐ์ ์ง๋ฌธ์ด ํ ๋ช ์ ์ฌ์ฉ์์๊ฒ ์์ฑ๋ ์ ์์ผ๋ฏ๋ก
@ManyToOne
๊ด๊ณ๊ฐ ์ฑ๋ฆฝํ๋ค.
Answer
์์ฑ ์ถ๊ฐ
Question
์ํฐํฐ์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก Answer
์ํฐํฐ์๋ author
์์ฑ์ ์ถ๊ฐํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/answer/
Answer.java
// (... ์๋ต ...)
import com.mysite.sbb.user.SiteUser;
// (... ์๋ต ...)
public class Answer {
// (... ์๋ต ...)
@ManyToOne
private SiteUser author;
}
ํ ์ด๋ธ ํ์ธ
์์ ๊ฐ์ด Question
, Answer
์ํฐํฐ๋ฅผ ๋ณ๊ฒฝํ๊ณ H2 ์ฝ์์ ์ ์ํ์ฌ question
, answer
ํ
์ด๋ธ์ ํ์ธํด ๋ณด์.
author
์ ์ฅ
์ด์ Question
, Answer
์ํฐํฐ์ author
์์ฑ์ด ์ถ๊ฐ๋์์ผ๋ฏ๋ก ์ง๋ฌธ๊ณผ ๋ต๋ณ ์ ์ฅ์์ author
๋ ํจ๊ป ์ ์ฅํ ์ ์๋ค.
์ง๋ฌธ, ๋ต๋ณ์ ๊ธ์ด์ด๋ฅผ ์ถ๊ฐํ๋ค๋ ๋๋์ผ๋ก ์์ ์ ์งํํ์.
๋ต๋ณ์ ์์ฑ์ ์ ์ฅํ๊ธฐ
๋จผ์ ๋ต๋ณ์ ์์ฑํ๋ AnswerController
๋ฅผ ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/answer/
AnswerController.java
// (... ์๋ต ...)
import java.security.Principal;
// (... ์๋ต ...)
public class AnswerController {
// (... ์๋ต ...)
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult, Principal principal) {
// (... ์๋ต ...)
}
}
ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ๋ํ ์ ๋ณด๋ฅผ ์๊ธฐ ์ํด์๋ ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ ๊ณตํ๋ Principal
๊ฐ์ฒด๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. ์์ ๊ฐ์ด createAnswer
๋ฉ์๋์ Principal
๊ฐ์ฒด๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์ง์ ํ๋ฉด ๋๋ค.
principal.getName()
์ ํธ์ถํ๋ฉด ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ์ฌ์ฉ์๋ช (์ฌ์ฉ์ID)์ ์์ ์๋ค.
principal
๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด ์ด์ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ์ฌ์ฉ์๋ช
์ ์์ ์์ผ๋ฏ๋ก ์ฌ์ฉ์๋ช
์ ํตํด SiteUser
๊ฐ์ฒด๋ฅผ ์กฐํํ ์ ์๋ค. ๋จผ์ User
์๋น์ค๋ฅผ ํตํด SiteUser
๋ฅผ ์กฐํํ ์ ์๋ getUser
๋ฉ์๋๋ฅผ UserService
์ ์ถ๊ฐํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/user/
UserService.java
// (... ์๋ต ...)
import java.util.Optional;
import com.mysite.sbb.DataNotFoundException;
// (... ์๋ต ...)
@Service
public class UserService {
// (... ์๋ต ...)
public SiteUser getUser(String username) {
Optional<SiteUser> siteUser = this.userRepository.findByusername(username);
if (siteUser.isPresent()) {
return siteUser.get();
} else {
throw new DataNotFoundException("siteuser not found");
}
}
}
UserRepository
์ ์ด๋ฏธ findByusername
์ ์ ์ธํ์ผ๋ฏ๋ก ์ฝ๊ฒ ๋ง๋ค์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๋ต๋ณ ์ ์ฅ์ ์์ฑ์๋ฅผ ์ ์ฅํ ์ ์๋๋ก ๋ค์๊ณผ ๊ฐ์ด AnswerService
๋ฅผ ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/answer/
AnswerService.java
// (... ์๋ต ...)
import com.mysite.sbb.user.SiteUser;
// (... ์๋ต ...)
public class AnswerService {
// (... ์๋ต ...)
public Answer create(Question question, String content, SiteUser author) {
Answer answer = new Answer();
answer.setContent(content);
answer.setCreateDate(LocalDateTime.now());
answer.setQuestion(question);
answer.setAuthor(author);
this.answerRepository.save(answer);
return answer;
}
}
create
๋ฉ์๋์ SiteUser
๊ฐ์ฒด๋ฅผ ์ถ๊ฐ๋ก ์ ๋ฌ๋ฐ์ ๋ต๋ณ ์ ์ฅ์ author
์์ฑ์ ์ธํ
ํ๋ค. ์ด์ ๋ต๋ณ์ ์์ฑํ๋ฉด ์์ฑ์๋ ํจ๊ป ์ ์ฅ๋ ๊ฒ์ด๋ค.
์ด์ ๋ค์๊ณผ ๊ฐ์ด AnswerController
์ createAnswer
๋ฉ์๋๋ฅผ ์์ฑํด ๋ณด์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/answer/
AnswerController.java
// (... ์๋ต ...)
import com.mysite.sbb.user.SiteUser;
import com.mysite.sbb.user.UserService;
// (... ์๋ต ...)
public class AnswerController {
private final QuestionService questionService;
private final AnswerService answerService;
private final UserService userService;
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult, Principal principal) {
Question question = this.questionService.getQuestion(id);
SiteUser siteUser = this.userService.getUser(principal.getName());
if (bindingResult.hasErrors()) {
model.addAttribute("question", question);
return "question_detail";
}
this.answerService.create(question, answerForm.getContent(), siteUser);
return String.format("redirect:/question/detail/%s", id);
}
}
principal
๊ฐ์ฒด๋ฅผ ํตํด ์ฌ์ฉ์๋ช
์ ์ป์ ํ์ ์ฌ์ฉ์๋ช
์ ํตํด SiteUser
๊ฐ์ฒด๋ฅผ ์ป์ด์ ๋ต๋ณ์ ๋ฑ๋กํ๋ AnswerService
์ create
๋ฉ์๋์ ์ ๋ฌํ์ฌ ๋ต๋ณ์ ์ ์ฅํ๋๋ก ํ๋ค.
์ง๋ฌธ์ ์์ฑ์ ์ ์ฅํ๊ธฐ
์ง๋ฌธ๋ ๋ต๋ณ๊ณผ ๋์ผํ ๋ฐฉ๋ฒ์ด๋ฏ๋ก ๋น ๋ฅด๊ฒ ์์ฑํด ๋ณด์. ๋จผ์ ์์ฑ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ธฐ ์ํด QuestionService
๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/question/
QuestionService.java
// (... ์๋ต ...)
import com.mysite.sbb.user.SiteUser;
// (... ์๋ต ...)
public class QuestionService {
// (... ์๋ต ...)
public void create(String subject, String content, SiteUser user) {
Question q = new Question();
q.setSubject(subject);
q.setContent(content);
q.setCreateDate(LocalDateTime.now());
q.setAuthor(user);
this.questionRepository.save(q);
}
}
create
๋ฉ์๋์ SiteUser
๋งค๊ฐ๋ณ์๋ฅผ ์ถ๊ฐํ์ฌ Question
๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ค.
์ด์ด์ QuestionController
๋ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/question/
QuestionController.java
// (... ์๋ต ...)
import java.security.Principal;
import com.mysite.sbb.user.SiteUser;
import com.mysite.sbb.user.UserService;
// (... ์๋ต ...)
public class QuestionController {
private final QuestionService questionService;
private final UserService userService;
// (... ์๋ต ...)
@PostMapping("/create")
public String questionCreate(@Valid QuestionForm questionForm,
BindingResult bindingResult, Principal principal) {
if (bindingResult.hasErrors()) {
return "question_form";
}
SiteUser siteUser = this.userService.getUser(principal.getName());
this.questionService.create(questionForm.getSubject(), questionForm.getContent(), siteUser);
return "redirect:/question/list";
}
}
์ด์ ๋ค์ ๋ก์ปฌ ์๋ฒ๋ฅผ ์์ํ๊ณ ๋ก๊ทธ์ธํ ๋ค์ ์ง๋ฌธ ยท ๋ต๋ณ ๋ฑ๋ก์ ํ ์คํธํด๋ณด์. ์ ๋ ๊ฒ์ด๋ค.
SbbApplicationTests.java
QuestionService
์ create
๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ก SiteUser
๊ฐ ์ถ๊ฐ๋์๊ธฐ ๋๋ฌธ์ ์ด์ ์ ์์ฑํ ํ
์คํธ ์ผ์ด์ค๊ฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค. ํ
์คํธ ์ผ์ด์ค์ ์ค๋ฅ๋ฅผ ์์ ํด๊ฒฐํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์.
package com.mysite.sbb;
//(... ์๋ต ...)
@Test
void testJpa() {
for (int i = 1; i <= 300; i++) {
String subject = String.format("ํ
์คํธ ๋ฐ์ดํฐ์
๋๋ค:[%03d]", i);
String content = "๋ด์ฉ๋ฌด";
this.questionService.create(subject, content, null);
}
}
}
๋ก๊ทธ์ธ์ด ํ์ํ ๋ฉ์๋
ํ์ง๋ง ๋ก๊ทธ์์ ์ํ์์ ์ง๋ฌธ ๋๋ ๋ต๋ณ์ ๋ฑ๋กํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์๋ฒ์ค๋ฅ(500 ์ค๋ฅ)๊ฐ ๋ฐ์ํ๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด principal
๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๋ฉ์๋์ @PreAuthorize("isAuthenticated()")
์ ๋ํ
์ด์
์ ์ฌ์ฉํด์ผ ํ๋ค.@PreAuthorize("isAuthenticated()")
์ ๋ํ
์ด์
์ด ๋ถ์ ๋ฉ์๋๋ ๋ก๊ทธ์ธ์ด ํ์ํ ๋ฉ์๋๋ฅผ ์๋ฏธํ๋ค. ๋ง์ฝ @PreAuthorize("isAuthenticated()")
์ ๋ํ
์ด์
์ด ์ ์ฉ๋ ๋ฉ์๋๊ฐ ๋ก๊ทธ์์ ์ํ์์ ํธ์ถ๋๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋๋๋ค.
QuestionController
๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/question/
QuestionController.java
package com.mysite.sbb.question;
// (... ์๋ต ...)
import org.springframework.security.access.prepost.PreAuthorize;
// (... ์๋ต ...)
public class QuestionController {
// (... ์๋ต ...)
@PreAuthorize("isAuthenticated()")
@GetMapping("/create")
public String questionCreate(QuestionForm questionForm) {
return "question_form";
}
@PreAuthorize("isAuthenticated()")
@PostMapping("/create")
public String questionCreate(@Valid QuestionForm questionForm,
BindingResult bindingResult, Principal principal) {
// (... ์๋ต ...)
}
}
๋ก๊ทธ์ธ์ด ํ์ํ ๋ฉ์๋๋ค์ @PreAuthorize("isAuthenticated()")
์ ๋ํ
์ด์
์ ์ ์ฉํ๋ค.
AnswerController
๋ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/answer/
AnswerController.java
// (... ์๋ต ...)
import org.springframework.security.access.prepost.PreAuthorize;
// (... ์๋ต ...)
public class AnswerController {
// (... ์๋ต ...)
@PreAuthorize("isAuthenticated()")
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id, @Valid AnswerForm answerForm,
BindingResult bindingResult, Principal principal) {
// (... ์๋ต ...)
}
}
๊ทธ๋ฆฌ๊ณ @PreAuthorize
์ ๋ํ
์ด์
์ด ๋์ํ ์ ์๋๋ก SecurityConfig
๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํด์ผ ํ๋ค.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/SecurityConfig.
java
// (... ์๋ต ...)
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
// (... ์๋ต ...)
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
// (... ์๋ต ...)
}
SecurityConfig
์ ์ ์ฉํ @EnableMethodSecurity
์ ๋ํ
์ด์
์ prePostEnabled = true
์ค์ ์ QuestionController
์ AnswerController
์์ ๋ก๊ทธ์ธ ์ฌ๋ถ๋ฅผ ํ๋ณํ๊ธฐ ์ํด ์ฌ์ฉํ๋ @PreAuthorize
์ ๋ํ
์ด์
์ ์ฌ์ฉํ๊ธฐ ์ํด ๋ฐ๋์ ํ์ํ๋ค.
์ด๋ ๊ฒ ์์ ํํ ๋ก๊ทธ์์ ์ํ์์ ์ง๋ฌธ์ ๋ฑ๋กํ๊ฑฐ๋ ๋ต๋ณ์ ๋ฑ๋กํ๋ฉด ์๋์ผ๋ก ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ ๊ฒ์ด๋ค.
๋ก๊ทธ์ธ ํ์ง ์์ ์ํ์์ "์ง๋ฌธ ๋ฑ๋ก" ๋ฒํผ์ ๋๋ฅด๋ฉด "๋ก๊ทธ์ธ" ํ๋ฉด์ผ๋ก ์ด๋ํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ก๊ทธ์ธ์ ์งํํ๋ฉด ์๋ ํ๋ ค๊ณ ํ๋ "์ง๋ฌธ ๋ฑ๋ก" ํ๋ฉด์ผ๋ก ์ด๋ํ๋ค. ์ด๊ฒ์ ๋ก๊ทธ์ธ ํ์ ์๋ ํ๋ ค๊ณ ํ๋ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ ์ํค๋ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ธฐ๋ฅ์ด๋ค.
disabled
ํ๊ฐ์ง ๋ ์๊ฐํด ๋ด์ผ ํ ๊ฒ์ด ์๋ค. ํ์ฌ ์ง๋ฌธ ๋ฑ๋ก์ ๋ก๊ทธ์์ ์ํ์์๋ ์์ ๊ธ์ ์์ฑํ ์ ์์ด์ ๋ง์กฑ์ค๋ฝ๋ค. ํ์ง๋ง ๋ต๋ณ ๋ฑ๋ก์ ๋ก๊ทธ์์ ์ํ์์๋ ๊ธ์ ์์ฑํ ์ ์๋ค. ๋ฌผ๋ก ๋ต๋ณ ์์ฑ ํ [์ ์ฅํ๊ธฐ]
๋ฅผ ๋๋ฅด๋ฉด ์๋์ผ๋ก ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋๋๋ฏ๋ก ํฐ ๋ฌธ์ ๋ ์๋์ง๋ง ์์ฑํ ๋ต๋ณ์ด ์ฌ๋ผ์ง๋ ๋ฌธ์ ๊ฐ ์๋ค.
์์ฑํ ๊ธ์ด ์ฌ๋ผ์ง๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด ๋ก๊ทธ์์ ์ํ์์๋ ์์ ๋ต๋ณ ์์ฑ์ ๋ชปํ๊ฒ ๋ง๋ ๊ฒ์ด ์ข์ ๊ฒ์ด๋ค.
.question_detail.html
ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/resources/templates/
question_detail.html
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
<!-- (... ์๋ต ...) -->
<!-- ๋ต๋ณ ์์ฑ -->
<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<textarea sec:authorize="isAnonymous()" disabled th:field="*{content}" class="form-control" rows="10"></textarea>
<textarea sec:authorize="isAuthenticated()" th:field="*{content}" class="form-control" rows="10"></textarea>
<input type="submit" value="๋ต๋ณ๋ฑ๋ก" class="btn btn-primary my-2">
</form>
</div>
</html>
๋ก๊ทธ์ธ ์ํ๊ฐ ์๋ ๊ฒฝ์ฐ textarea
ํ๊ทธ์ disabled
์์ฑ์ ์ ์ฉํ์ฌ ์
๋ ฅ์ ๋ชปํ๊ฒ ๋ง๋ค์๋ค. sec:authorize="isAnonymous()"
, sec:authorize="isAuthenticated()"
์์ฑ์ ํ์ฌ ์ฌ์ฉ์์ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ฒดํฌํ๋ ์์ฑ์ด๋ค.
sec:authorize="isAnonymous()"
- ํ์ฌ ๋ก๊ทธ์์ ์ํsec:authorize="isAuthenticated()"
- ํ์ฌ ๋ก๊ทธ์ธ ์ํ