<Product(상품) 설계>
1) Entity 생성
package com.ecproject.onlinestore.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String details;
private int price;
private int quantity;
private LocalDateTime releaseTime;
private LocalDateTime updateTime;
}
2) MySql 데이터베이스 테이블 생성
3) MySql 관련 의존성 추가
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'
// application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/(데이터베이스명)
spring.datasource.username=(아이디)
spring.datasource.password=(비밀번호)
spring.jpa.show-sql = true
spring.jpa.database = mysql
4) ProductRepository 생성
package com.ecproject.onlinestore.repository;
import com.ecproject.onlinestore.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
<회원가입 기능>
1) Spring Security 의존성 추가
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
2) SecurityConfig 작성
- Spring Security의 기본 설정을 커스터마이징하여 애플리케이션의 보안 요구 사항에 맞게 조정
- 'WebSecurityConfigurerAdapter' 클래스를 상속받아 구현하는 방식은 더이상 쓰이지 않음
- 'SecurityFilterChain' 사용이 권장됨
package com.ecproject.onlinestore.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;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration // 클래스가 Spring IoC 컨테이너에 의해 Bean 정의의 소스로 사용됨을 나타냄
@EnableWebSecurity // Spring Security 설정을 활성화
public class SecurityConfig {
@Bean
// HttpSecurity 객체를 사용하여 HTTP 요청에 대한 보안 구성을 정의
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authz -> authz
.anyRequest().authenticated()) // 모든 요청에 대해 인증을 요구함
.formLogin(withDefaults()); // 기본 로그인 폼을 제공함
return http.build();
}
@Bean
// 비밀번호를 안전하게 저장하고 검증하는 메커니즘을 제공하는 인터페이스
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // BCrypt 해싱 함수를 사용하여 비밀번호를 인코딩
}
}
3) MemberRole enum 생성
- 각각의 멤버가 일반 유저인지 관리자인지 구분
package com.ecproject.onlinestore.entity;
public enum MemberRole {
USER, ADMIN // 각각의 멤버가 일반 유저인지 관리자인지 구분
}
4) DTO 생성
- DTO(Data Transfer Object): 계층 간 데이터 전송을 위해 사용되는 객체
- 클라이언트와 서버 간, 또는 서비스 계층과 데이터 액세스 계층 간의 데이터 교환을 위해 사용
- 'MemberFormDto': 사용자가 회원가입 양식에 입력하는 데이터를 담는 DTO
package com.ecproject.onlinestore.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MemberFormDto {
private String name;
private String email;
private String password;
private String address;
}
5) Member Entity 생성
- @Enumerated(EnumType.STRING): 열거형 값을 문자열로 데이터베이스에 저장, 열거형의 순서가 변경되더라도 데이터베이스에 영향 X
- createMember 메서드: MemberFormDto 객체와 비밀번호를 암호화하기 위한 PasswordEncoder를 받아 새로운 Member 인스턴스를 생성하고 반환
package com.ecproject.onlinestore.entity;
import com.ecproject.onlinestore.dto.MemberFormDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.crypto.password.PasswordEncoder;
@Entity
@Getter
@Setter
@ToString
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(unique = true) // 회원은 이메일을 통해 유일하게 구분
private String email;
private String password;
private String address;
@Enumerated(EnumType.STRING)
private Role role;
public static Member createMember(MemberFormDto memberFormDto,
PasswordEncoder passwordEncoder) {
Member member = new Member();
member.setName(memberFormDto.getName());
member.setEmail(memberFormDto.getEmail());
member.setAddress(memberFormDto.getAddress());
String password = passwordEncoder.encode(memberFormDto.getPassword());
member.setPassword(password);
member.setRole(Role.ADMIN);
return member;
}
}
6) MemberRepository 생성
package com.ecproject.onlinestore.repository;
import com.ecproject.onlinestore.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByEmail(String email); //회원 가입시 중복된 회원이 있는지 이메일로 검사
}
7) MemberService 생성
package com.ecproject.onlinestore.service;
import com.ecproject.onlinestore.entity.Member;
import com.ecproject.onlinestore.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
// 의존성 주입, 회원 정보에 대한 CRUD 연산을 데이터베이스에 위임
public Member saveMember(Member member) { // 새 회원 저장 메서드
validateDuplicateMember(member); // 먼저 중복 검사 실행
return memberRepository.save(member);
}
private void validateDuplicateMember(Member member) {
Member findMember = memberRepository.findByEmail(member.getEmail());
if (findMember != null) {
throw new IllegalStateException("이미 가입된 회원입니다.");
}
}
}
8) MemberController 생성
- 회원가입이 성공하면 메인 페이지로 리다이렉트
package com.ecproject.onlinestore.controller;
import com.ecproject.onlinestore.dto.MemberFormDto;
import com.ecproject.onlinestore.entity.Member;
import com.ecproject.onlinestore.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
// 의존성 주입
private final MemberService memberService;
private final PasswordEncoder passwordEncoder;
@GetMapping("/new")
public String memberForm(Model model){ // 회원 가입 양식 요청 메서드
model.addAttribute("memberFormDto", new MemberFormDto());
return "member/memberForm";
}
@PostMapping("/new")
public String memberForm(MemberFormDto memberFormDto){ // 회원 가입 양식 제출 메서드
Member member = Member.createMember(memberFormDto, passwordEncoder);
memberService.saveMember(member);
return "redirect:/";
}
}
9) MainController 생성
- 애플리케이션의 홈페이지 또는 메인 페이지를 사용자에게 보여주는 역할을 수행
- 사용자가 웹 브라우저에서 루트 URL을 방문하면, "main" 뷰가 렌더링되어 사용자에게 응답으로 전송
package com.ecproject.onlinestore.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MainController {
@GetMapping("/")
public String main() {
return "main";
}
}
10) MemberFormDto 유효성 검증 어노테이션 추가
package com.ecproject.onlinestore.dto;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
@Getter
@Setter
public class MemberFormDto {
@NotBlank(message = "이름은 필수 입력 값입니다.")
private String name;
@NotBlank(message = "이메일은 필수 입력 값입니다.")
@Email(message = "이메일 형식으로 입력해주세요.")
private String email;
@NotBlank(message = "비밀번호는 필수 입력 값입니다.")
@Length(min=4, max=10, message = "비밀번호는 4자 이상 10자 이하로 입력해주세요.")
private String password;
@NotEmpty(message = "주소는 필수 입력값입니다.")
private String address;
}
11) MemberController 기능 추가
- 회원 가입이 성공하면 메인 페이지로 리다이렉트
- 실패하면 다시 회원 가입 페이지로 돌아가 실패 이유 출력
- @Valid: MemberFormDto 객체에 설정된 검증 조건을 자동으로 검사
- BindingResult: 검증 과정에서 발생한 에러 정보를 담음
@Controller
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
// 의존성 주입
private final MemberService memberService;
private final PasswordEncoder passwordEncoder;
@GetMapping("/new") // 회원 가입 양식 요청 메서드
public String memberForm(Model model){
model.addAttribute("memberFormDto", new MemberFormDto());
return "member/memberForm";
}
@PostMapping("/new") // 회원 가입 양식 제출 메서드
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);
} catch (IllegalStateException e){
model.addAttribute("errorMessage", e.getMessage());
// 중복 가입 예외가 발생하면 에러 메시지를 뷰로 전달
return "member/memberForm";
}
return "redirect:/";
// 성공적으로 회원 가입이 완료되면 루트 경로(/)로 리다이렉트
}
}
12) thymeleaf 설정, memberForm.html 작성
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>회원가입</title>
</head>
<body>
<h1>회원가입</h1>
<form th:action="@{/members/new}" th:object="${memberFormDto}" method="post">
<p>이름: <input type="text" th:field="*{name}" /></p>
<p>이메일: <input type="email" th:field="*{email}" /></p>
<p>비밀번호: <input type="password" th:field="*{password}" /></p>
<p>주소: <input type="text" th:field="*{address}" /></p>
<p><button type="submit">가입하기</button></p>
</form>
</body>
</html>
13) SecurityConfig 수정
- 회원 가입이 성공하면 메인 페이지로 리다이렉트
@Configuration // 클래스가 Spring IoC 컨테이너에 의해 Bean 정의의 소스로 사용됨을 나타냄
@EnableWebSecurity // Spring Security 설정을 활성화
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // CSRF 보호 비활성화
.authorizeRequests(authz -> authz
.requestMatchers("/members/new").permitAll() // '/members/new' 경로에 인증되지 않은 접근 허용
.anyRequest().authenticated() // 그 외 모든 요청에 대해 인증 요구
)
.formLogin(withDefaults()); // 기본 로그인 폼 사용
return http.build();
}
@Bean
// 비밀번호를 안전하게 저장하고 검증하는 메커니즘을 제공하는 인터페이스
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // BCrypt 해싱 함수를 사용하여 비밀번호를 인코딩
}
}