2024. 3. 11. 15:20ใSpring/[2024] Spring Boot
https://hyejin.tistory.com/1295
๊ฐ๋จํ Spring Boot Restful API Sample Project ๋ง๋ค์ด๋ณด๊ธฐ #1. Spring Security ์ค์ + CustomFilter ๋ง๋ค๊ธฐ
โ ์ํ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๊ธฐ๋ก ๊ฒฐ์ ํ ์ด์ -> ์๊ฐ์ด ์กฐ๊ธ ์ฌ์ ๋ก์ด ์์ฆ, ๊ณง ๊ฐ๋ฐ ๋ค์ด๊ฐ๊ธฐ ์ ์ ๊ฐ๋จํ๊ฒ ์ํ ํ๋ก์ ํธ ๋ง๋ค๋ฉด์ ๊ฐ ์ก๊ณ ? ์๊ณ ์ถ์ด์! ๊ทธ๋ฆฌ๊ณ ๋ง๋ ๊น์ ์ค๋๋ง์ ํฐ์คํ ๋ฆฌ
hyejin.tistory.com
-> ์ด๋ฒ ๊ธ์ ํ์ ๊ฐ์ ๋ก์ง์ ๊ตฌํํ๋ ๊ธ๋ก, ์ํ๋ฆฌํฐ ์ค์ ๊ด๋ จ ๋ถ๋ถ์ ๋ฐ์ ๋งํฌ ํด๋ฆญํด์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค :)
๐ : ์๊ฐํด๋ณด๋ฉด ์คํ๋ง ์ํ๋ฆฌํฐ ์ค์ ๋ฐ ๋ก๊ทธ์ธ ๊ตฌํํ๊ธฐ ์ ์ ํ์๊ฐ์ ๋ก์ง์ ๋ง๋ค์์ด์ผ ํ๋ค.. ์๋
ํ์ ๊ฐ์ ์ ํด์ผ ํ์ ์ ๋ณด ์ ์ฅํ๊ณ , ๋ก๊ทธ์ธํ๋๊น..! ์ด์ฐ๋ณด๋ฉด ๋น์ฐํ๊ฑด๋ฐ ๋๋ ์คํ๋ง ์ค์ ํ ์๊ฐ๋ฟ์ด์๋ค ใ ใ ใ ใ ํ๋์ ๊ฝํ๋ฉด ์ด๋ ๊ฒ ๋๋ค.;;; ์ํผ ๊ทธ๋์ ์ด์ ์์ผ ํ์ ๊ฐ์ ๊ด๋ จ ๊ธ์ ์์ฑํ๋ค..
Member
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseEntity
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email; // email์ id๋ก ์ฌ์ฉํ ๊ฒ์!
private String password;
private String name;
@Enumerated(EnumType.STRING)
private MemberLevel memberLevel;
@Enumerated(EnumType.STRING)
private MemberStatus memberStatus;
@Builder
public Member(String email, String password, String name, MemberLevel memberLevel, MemberStatus memberStatus)
{
this.email = email;
this.password = password;
this.name = name;
this.memberLevel = memberLevel;
this.memberStatus = memberStatus;
}
}
-> Member ๋ ์ํฐํฐ๋ก id, email, password, name ๊ธฐ๋ณธ ์ ๋ณด์ memberLevel, memberStatus ๊ฐ์ enum ์ผ๋ก ๊ฐ๋๋ค.
memberLevel ์ ์ฐ์ ์๊ฐ์ผ๋ก ๊ด๋ฆฌ์๋, ์ฌ์ฉ์๋ ๋ฅผ ๊ตฌ๋ถํ ๊ฒ์ด๊ณ ,
memberStatus ์๋ ์ฌ์ฉ์ค์ธ์ง, ์ฌ์ฉ ์ค์ง๋ ์ฌ์ฉ์์ธ์ง ๊ตฌ๋ถํ ๊ฒ์ด๋ค.
MemberLevel
@Getter
@RequiredArgsConstructor
public enum MemberLevel
{
USER("์ผ๋ฐ ์ฌ์ฉ์"),
ADMIN("๊ด๋ฆฌ์");
private final String text;
}
MemberStatus
@Getter
@RequiredArgsConstructor
public enum MemberStatus
{
USE("์ฌ์ฉ ์ค"),
HOLD("์ค์ง");
private final String text;
}
BaseEntity
@Getter
@MappedSuperclass // ๊ณตํต ๋งตํ ์ ๋ณด๊ฐ ํ์ํ ๋ ์ฌ์ฉ, ๋ถ๋ชจ ํด๋์ค์์ ์ ์ธํ๊ณ ์์ฑ๋ง ์์๋ฐ๊ณ ์ถ์ ๋ ์ฌ์ฉ
@EntityListeners(AutoCloseable.class) // JPA Entity์ ์ด๋ฒคํธ ๋ฐ์ํ ๋ ๊ด๋ จ ์ฝ๋ ์คํ
public abstract class BaseEntity
{
@CreatedDate // ์์ฑ ์ผ์๋ฅผ ๊ด๋ฆฌํ๋ ํ๋, ํ์ฌ ๋ ์ง ์ฃผ์
@Column(updatable = false) // ์์ฑ์ผ์์ ๋ํ ํ๋์ด๊ธฐ ๋๋ฌธ์ ์์ ๋ถ๊ฐํ๋๋ก ์ค์
private LocalDateTime registerDate;
@LastModifiedDate // ์กฐํํ Entity ๊ฐ ๋ณ๊ฒฝํ ๋ ์๊ฐ ์๋ ์ ์ฅ
private LocalDateTime modifiedDate;
}
-> ๊ทธ๋ฆฌ๊ณ BaseEntity์ด๋ค. BaseEntity๋ ์ถ์ํด๋์ค๋ก ๋ง๋ค๊ณ , ์ด๋ฅผ ์์๋ฐ์ ์ํฐํฐ๋ค์ ๋ํ ๋ฑ๋ก์ผ๊ณผ ์์ ์ผ์ ์ถ๊ฐํ๋ค. Member๋ ๊ฐ์ ํ๋ฉด ๊ฐ์ ์ผ, ์์ ํ๋ฉด ์์ ์ผ์ ๊ฐ์ด ์ ์ฅํด์ฃผ๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ์์ ์ถ๊ฐํ๋ค.
์ด๋ ๋ฑ๋ก์ผ์ ์์ฑ์ผ์์ ๋ํ ํ๋๋๊น ์์ ๋ถ๊ฐํ๊ฒ ๋ง๋ค์๋ค.
AuthController
@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController
{
private final AuthService authService;
@PostMapping("/signUp")
public ApiResponse<SignResponse> signUp(@RequestBody SignUpCreateRequest request)
{
return ApiResponse.ok(authService.signUp(request));
}
}
-> /auth/signUp post ์์ฒญ์ผ๋ก ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ request body์ ๋ด์ ํธ์ถํ๋ฉด, ํ์ ๊ฐ์ ํ ์ ํด์ง ApiResponse์ ๋ง๊ฒ ๋ฆฌํดํด์ค๋ค.
SignUpCreateRequest
@Getter
@Setter
@NoArgsConstructor
public class SignUpCreateRequest
{
private String email;
private String password;
private String name;
private MemberLevel memberLevel;
private MemberStatus memberStatus;
@Builder
public SignUpCreateRequest(String email, String password, String name)
{
this.email = email;
this.password = password;
this.name = name;
this.memberLevel = USER;
this.memberStatus = USE;
}
public static Member toEntity(SignUpCreateRequest request)
{
return Member.builder()
.name(request.getName())
.email(request.getEmail())
.password(request.getPassword())
.memberLevel(USER)
.memberStatus(USE)
.build();
}
}
-> ํ์ ๊ฐ์
ํ ๋, email, password, name ๋ฑ์ ๋ฐ๋๋ค๊ณ ์๊ฐํ๊ณ ,
MemberLevel, MemberStatus๋ ์์ฑํ ๋ ์๋์ผ๋ก USER, USE๋ก ์ง์ ๋๋ค.
ApiResponse
@Getter
public class ApiResponse<T>
{
private int code;
private HttpStatus status;
private String message;
private T data;
public ApiResponse(HttpStatus status, String message, T data)
{
this.code = status.value();
this.status = status;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> of(HttpStatus status, String message, T data)
{
return new ApiResponse<>(status, message, data);
}
public static <T> ApiResponse<T> of(HttpStatus status, T data)
{
return of(status, status.name(), data);
}
public static <T> ApiResponse<T> ok(T data)
{
return of(HttpStatus.OK, data);
}
}
-> ๋ด๊ฐ ๋ง๋ Restful api response ํ์์ ๋ง๋ค์๋ค.
code์๋ ์ํ๊ฐ, status์๋ ์๋ต ์ํ, message์๋ ์๋ต ๋ฉ์์ง, data์ ์ด์ ๋ฆฌํด๋ ๊ฐ์ ๋ฐ์ ๊ฒ์ด๋ค.
AuthService
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService
{
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
public SignResponse signUp(SignUpCreateRequest request)
{
Optional<Member> byEmail = memberRepository.findByEmail(request.getEmail());
if (byEmail.isPresent()) {
throw new AlreadyExistsException();
}
String rawPassword = request.getPassword();
String encryptedPassword = passwordEncoder.encode(rawPassword);
request.setPassword(encryptedPassword);
Member member = SignUpCreateRequest.toEntity(request);
Member saveMember = memberRepository.save(member);
return new SignResponse(saveMember.getEmail());
}
}
-> ํ์ ๊ฐ์
์๋น์ค ๋ก์ง์ด๋ค.
๋จผ์ request์์ ๋ฐ์ email ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ MemberRepository์์ ํด๋น email์ ๊ฐ์ง ํ์์ด ์๋์ง ์กฐํํ๊ณ , ๋ง์ฝ ์๋ค๋ฉด ์์ธ๋ฅผ ๋์ง๋ค. (์ค๋ณต ์ด๋ฉ์ผ ์๋จ!)
์ด์ ์ด๋ฉ์ผ์ด ์ค๋ณต๋์ง ์๋๋ค๋ฉด ๋ฐ์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํํด์ db์ ์ ์ฅํ๋ค.
๊ทธ๋ฆฌ๊ณ returnํ ๋๋ email ์ ๋ณด๋ง ๋ฆฌํดํด์ค๋ค.
BaseException
@Getter
public abstract class BaseException extends RuntimeException
{
public BaseException(String message)
{
super(message);
}
public BaseException(String message, Throwable cause)
{
super(message, cause);
}
public abstract HttpStatus getStatus();
}
-> BaseException๋ RuntimeException์ ์์๋ฐ์ ์ถ์ ํด๋์ค๋ก ์ปค์คํ ํ ์์ธ๋ฅผ ๋ง๋ค๊ธฐ ์ํ ํด๋์ค๋ผ๊ณ ํ ์ ์๋ค.
AlreadyExistsException
public class AlreadyExistsException extends BaseException
{
private static final String MESSAGE = "์ด๋ฏธ ๊ฐ์
๋ ์ด๋ฉ์ผ์
๋๋ค.";
public AlreadyExistsException()
{
super(MESSAGE);
}
@Override
public HttpStatus getStatus()
{
return null;
}
}
-> ๊ทธ๋ฆฌ๊ณ BaseException ์ ์์๋ฐ์์ ์ด๋ฏธ ๊ฐ์ ๋ ์ด๋ฉ์ผ์ ๋ํ ์์ธ๋ฅผ ์ปค์คํ ํด์ ๋ง๋ค์ด์คฌ๋ค.
SignResponse
@Getter
@Setter
public class SignResponse
{
private String email;
public SignResponse(String email)
{
this.email = email;
}
}
-> ๊ฐ๋จํ๊ฒ ์ฐ์ ํ์๊ฐ์ ํ ๋ฆฌํดํ๋ vo์๋ email๋ง ๋ด๊ธด๋ค. (์ฌ๊ธฐ๋ ์์ ๋ ์ง, ์ด๊ฑด ์์ง ๋ณด๋ฅ)
MemberRepository
@Repository
public interface MemberRepository extends JpaRepository<Member, Long>
{
Optional<Member> findByEmail(String email);
}
-> JpaRepository๋ฅผ ์์๋ฐ์ ๋ง๋ค๊ณ , email์ ํตํด ํ์ ์ ๋ณด๋ฅผ ์กฐํํ ์ ์๊ฒ ์ถ๊ฐํ๋ค.
ํ์ ๊ฐ์ ์ ๊ฐ๋จํ๋ค..!
์ํ ํ๋ก์ ํธ๋๊น ์ฐ์ ์ด๋ ๊ฒ ๋ง๋ค์๊ณ , ๋ง๋ ํ์ ๊ฐ์ ์ด ์ ์ ๋์ํ๋์ง ํ์ธํด๋ณด๋ฉด
### ํ์ ๊ฐ์
POST http://localhost:8080/auth/signUp
Content-Type: application/json
{
"name" : "test",
"email" : "test@gmail.com",
"password": "1231"
}

์ด๋ ๊ฒ ์ฐ๋ฆฌ๊ฐ ์ง์ ํ apiResponse์ ๋ง๊ฒ ์๋ต์ด ๋ด๋ ค์จ๋ค. ๐
๐ฉโ๐ป : ํ์ ๊ฐ์ ์ ์ฐ์ ๊ฐ๋จํ๊ฒ ์ด๋ฐ์์ผ๋ก ๋ง๋ค์๋ค !
์ด์ ์ด ๋ค์์๋ jwt ๋ฅผ ์ด์ฉํด์ ์ธ์ฆ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๊ตฌํํด๋ณผ ์์ ์ด๋ค.