
코딩애플 강의를 통해 배운 Spring Boot & JPA를 정리한 글입니다.
2025년 3월 11일
소개
part2-1 : 상품 추가기능 1 (Map 자료형)
part2-2 : 상품 추가기능 2 & Navbar 만들기
part2-3 : 상세페이지 만들기 1 (Optional)
part2-4 : 상세페이지 만들기 2 & 예외처리
part2-5 : REST API의 예외처리 방법
part2-6 : Service 레이어로 분리하려면
part2-7 : 수정기능 1
part2-8 : 수정기능 2 (숙제)
part2-9 : 삭제기능 1 (AJAX, query string)
part2-10 : 삭제기능 2 (AJAX 추가 내용)
part2-11 : Session, JWT, OAuth 개념 설명
part2-12 : Spring Security 설치와 셋팅, Hashing
part2-13 : 가입기능 만들기
part2-14 : 로그인 기능 1
part2-15 : 로그인 기능 2
part2-16 : 로그인 기능 3 (유저정보 커스터마이징, CSRF)
참고 자료
Part 2 : Part 2
// 만드는방법
Map<String, String> test = new HashMap<>();
test.put("name", "kim");
<!-- wrtie.html -->
<form action="/add" method="POST">
<input name="title" />
<input name="price" />
<button type="submit">버튼</button>
</form>
// controller
@PostMapping("/add")
String addPost (@RequestParam Map formData) {
System.out.println(formData.get("title"));
System.out.println(formData.get("price"));
return "redirect:/list";
}
or
@PostMapping("/add")
String addPost (String title, String price) {
System.out.println(title);
System.out.println(price);
return "redirect:/list";
}
@PostMapping("/add")
String addPost (String title, Integer price) {
Item item = new Item();
item.setTitle(title);
item.setPrice(price);
itemRepository.save(item);
return "write.html";
}
or
@PostMapping("/add")
String addPost (@ModelAttribute Item item) {
itemRepository.save(item);
return "redirect:/list";
}
<!-- nav.html -->
<div class="nav" th:fragment="navbar">
<a class="logo">SpringMall</a>
<a>List</a>
<a>Write</a>
</div>
<!-- write.html -->
<div th:replace="~{ nav.html::navbar }"></div>
@GetMapping("/detail/{id}")
String detail () {
Optional<Item> result = itemRepository.findById(1L);
return "detail.html";
}
@GetMapping("/detail/{id}")
String detail (@PathVariable Long id, Model model) {
Optional<Item> result = itemRepository.findById(id);
if (result.isPresent()) {
model.addAttribute("items", result.get());
}
else {
return "redirect:/list";
}
return "detail.html";
}
<a th:href="@{'/detail/' + ${items.id}}">링크</a>
ResponseEntity<String > detail (@PathVariable Long id, Model model) {
try {
}
catch (Exception e) {
return ResponseEntity.status(400).body("니잘못임");
}
}
ResponseEntity.status(HttpStatus.NOT_FOUND).body("니잘못임");
throw new Exception(); 가 나오면 퍼블릭파일 호출@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handler(){
return ResponseEntity.status(400).body("에러남!");
}
}
// Service.java
@Service
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
public void saveItem(String title, Integer price) {
Item item = new Item();
item.setTitle(title);
item.setPrice(price);
itemRepository.save(item);
}
}
// Controller
private final ItemService itemService;
@PostMapping("/add")
String addPost (String title, Integer price) {
itemService.saveItem(title, price);
return "redirect:/list";
}
@GetMapping("/edit/{id}")
String edit (@PathVariable Long id, Model model) {
Optional<Item> result = itemRepository.findById(id);
if (result.isPresent()) {
model.addAttribute("items", result.get());
}
else {
return "redirect:/list";
}
return "edit.html";
}
@PostMapping("/edit")
String editPost (String title, Integer price, Long id) {
Item item = new Item();
item.setId(id);
item.setTitle(title);
item.setPrice(price);
itemRepository.save(item);
return "redirect:/list";
}
<form action="/edit" method="POST">
<input name="id" th:value="${items.id}" style="display:none;" />
<input name="title" th:value="${items.title}" />
<input name="price" th:value="${items.price}" />
<button type="submit">버튼</button>
</form>
document.querySelectorAll(".btn")[0].addEventListener("click", function () {
fetch("/test1", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "kim", price: 123 }),
});
});
or;
// 이건 Get
document.querySelectorAll(".btn")[0].addEventListener("click", function () {
fetch("/test1?title=kim&price=123");
});
@PostMapping("/test1")
String test1 (@RequestBody Map<String, Object> body) {
System.out.println(body);
return "redirect:/list";
}
@DeleteMapping("/item")
ResponseEntity<String> deleteItem(@RequestParam Long id) {
itemRepository.deleteById(id);
return ResponseEntity.status(200).body("삭제완료");
}
<span
th:onclick="fetch('/item?id=[[${items.id}]]', { method : 'DELETE' } )
.then(r => r.text())
.then(()=>{
location.reload();
})
"
>🗑️s</span
>
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'
// SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 다른사이트에서 api요청하는것 그게 csrf공격
http.csrf((csrf -> csrf.disable()));
http.authorizeHttpRequests((authorize) ->
// /**은 아무 url이라는 뜻, permitAll()은 항상 허용하겠다 라는뜻 즉 모든 url 로그인 검사를 끔
authorize.requestMatchers("/**").permitAll()
);
return http.build();
}
}
new BCryptPasswordEncoder().encode("문자")
@Bean
PasswordEncoder 함수() {
return new BCryptPasswordEncoder();
}
// 폼으로 로그인 한다라는 뜻
// SecurityConfig.java
http.formLogin((formLogin) -> formLogin.loginPage("/login")
.defaultSuccessUrl("/")
.failureUrl("/fail")
)
// MyUserDetailsService.java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// DB에서 username을 가진 유저를 찾아와서
// return new User(유저아이디, 비번, 권한) 해주세요
}
}
// interface파일
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findAllByUsername(String username);
}
<!-- 에러가 난 경우 보여줌 -->
<div th:if="${param.error}">아이디 비번틀림</div>
// MyUserDetailsService.java
public class MyUserDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// DB에서 username을 가진 유저를 찾아와서
// return new User(유저아이디, 비번, 권한) 해주세요
var result = memberRepository.findAllByUsername(username);
if(result.isEmpty()) {
throw new UsernameNotFoundException("그런 아이디 없음");
}
var user = result.get();
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("일반유저"));
return new User(user.getUsername(), user.getPassword(),authorities);
}
}
// controller.java
public String myPage(Authentication auth) {
System.out.println(auth);
System.out.println(auth.getName()); //아이디출력가능
System.out.println(auth.isAuthenticated()); //로그인여부 검사가능
System.out.println(auth.getAuthorities().contains(new SimpleGrantedAuthority("일반유저")));
return "mypage.html";
}
// SecurityFilterChain
http.logout( logout -> logout.logoutUrl("/logout") );
<span sec:authentication="principal"></span>
<span sec:authentication="principal.username"></span>
<span sec:authentication="principal.authorities"></span>
server.servlet.session.timeout=5s
server.servlet.session.cookie.max-age=5s
// SecurityConfig
@Bean
public CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
http.csrf(csrf -> csrf.csrfTokenRepository(csrfTokenRepository())
.ignoringRequestMatchers("/login")
)