어쩌다보니 진도를 쭉 나가게 되었다. Lv2 는 주말에 대부분 구현해두어, 오늘은 어느정도의 리팩토링과 Lv6 까지 구현했다 보면 된다.
Lv3 은 Pwd 만 추가하는 거라 4 레벨부터 본격적이라고 생각하면 된다. 구현한 레벨이 많다 보니, 모든 내용을 한번에 담기는 힘들다. 내가 고민했던 포인트만 코멘트 남기겠다.
Lv 2. 유저 CRUD
- 유저를 생성, 조회, 수정, 삭제할 수 있습니다.
- 유저는 아래와 같은 필드를 가집니다.
- 유저명, 이메일, 작성일 , 수정일 필드
- 작성일, 수정일 필드는 JPA Auditing을 활용합니다.
- 연관관계 구현
- 일정은 이제 작성 유저명 필드 대신 유저 고유 식별자 필드를 가집니다
- 유저 Enity 같은 경우에는 일정 Entity 와 연관관계가 있기 때문에, 해당 부분을 일정 Entity 에 추가해주어야한다.
@ManyToOne
@JoinColumn(name="userId")
private User user;
public void setUser(User user) {
this.user = user;
}
- setUser를 통해서 주입 해주어야 하는 것도 잊지 않았다.
- 이때부터 RequestDto 와 ResponseDto 가 막 생겨나기 시작했다. 그래서 이 많은 Dto 를 어떻게 처리할까? 하다가 아래와 같은 이유로 Entity 별로 나누기로 했다.
// 왜 패키지를 Entity 별로 분리 했냐 ? -> Entity 를 기준으로 기능을 만들기 때문에
// Error 가 있어 수정해야할 일이 있으면 Entity request 와 response 를 동시에 확인해봐야함.
// 그래서 Entity 패키지로 묶어 두면 조금 더 관리하기 쉬울 거 같아서 Entity 를 기준으로 패키지 별로 분리함.
Lv 3. 회원가입 필수
- 유저에 비밀번호 필드를 추가합니다.
- 비밀번호 암호화는 도전 기능에서 수행합니다.
- 일당 해당 부분에서는 필드만 추가하라고 했기 때문에, 필드만 추가했다.
- 추가적으로 회원 가입 시, RequestDto 에 pwd 를 추가해두었다.
Lv 4. 로그인(인증)
- 조건
- 이메일과 비밀번호를 활용해 로그인 기능을 구현합니다.
- 회원가입, 로그인 요청은 인증 처리에서 제외합니다.
- 로그인은 세션과 필터를 잘 활용해야 된다.
- 먼저, LoginFilter 를 만들어야 된다.
package com.example.scheduleappserverjpa.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.HttpStatus;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.server.ResponseStatusException;
import java.io.IOException;
import java.net.http.HttpConnectTimeoutException;
public class LoginFilter implements Filter {
private static final String[] WHITE_LIST = {"/", "/users/signup", "/users/login"};
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// request 에서 url 가져오기.
String requestURL = httpServletRequest.getRequestURI();
// response 도 다운 캐스팅
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
// 화이트 리스트에 있지 않은 url 의 경우에는 로그인 여부를 확인해야한다.
if (!isWhiteList(requestURL)) {
// 만약 체크해야할 url 이라면,
HttpSession session = httpServletRequest.getSession(false);
// 세션이 없거나, 세션 키가 널이라면, 로그인 되지 않은 것.
if (session == null || session.getAttribute("loginUser") == null) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
// 화이트 리스트 안에 있는 값과 현재 url 을 비교하는 것.
public boolean isWhiteList(String string) {
return PatternMatchUtils.simpleMatch(WHITE_LIST, string);
}
}
- 간단하게 설명하면, WhiteList 에 있는 값들은 조건문을 들어가지 않을 것이고(로그인 여부 확인을 안 해도 됨), 없는 모든 url 들은 해당 조건문을 수행해야한다.
- 조건문에서는 아예 세션이 널이거나? loginUser 라는 세션이 없다면 ? 인증 에러가 뜨게 하는 것이다.
- 강의에서는 따로 common 이라는 곳에 interface 를 구현해 두었는데. 해당 부분을 보자.
public interface Const {
String LOGIN_USER = "loginUser";
}
- 이렇게 인터페이스에 따로 Login_user 를 만들어 둔 이유는 상수로 만들기 위함이다. enum 의 원리를 생각하면 조금 이해가 쉬울 것이다.
- 일단은 로그인, 로그아웃 까지만 구현했고, 인가에 대한 요구사항은 없어서 이후에 모든 과제를 끝낸 후 인가를 추가적으로 수정하기로 했다.
Lv 5. 다양한 예외처리 적용하기
- Validation을 활용해 다양한 예외처리를 적용해 봅니다.
- 정해진 예외처리 항목이 있는것이 아닌 프로젝트를 분석하고 예외사항을 지정해 봅니다.
- Ex) 할일 제목은 10글자 이내, 유저명은 4글자 이내
- @Pattern을 사용해서 회원 가입 Email 데이터 검증 등
- ReqeustDto 에다가 Valid 를 설정해주었고, 할일과 유저명에 대한 유효값 검증을 적절히 수정해주었다.
// https://jakarta.ee/specifications/bean-validation/3.0/jakarta-bean-validation-spec-3.0.html#builtinconstraints-size
// emil 검색시 나오는 정규표현식
@Pattern(regexp = "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}", message = "이메일 형식이 올바르지 않습니다.")
- 이메일은 이렇게 수정해주었다. 공식 문서에 나와 있는 표현식을 보고 적절히 수정해주었다.
@NotBlank(message = "이름은 필수값 입니다.")
@Size(min=2, max=4)
private String username;
@NotBlank(message = "제목은 필수값 입니다.")
@Size(max=10)
private String title;
- 할일과 유저명도 유구사항에 맞게 Vaild 를 넣어주었다.
- 추가적으로 커스텀한 에러들도 만들었다.
- 일단 커스텀 에러는 총 3가지로 데이터가 존재하지 않을 때, 인증이 되지 않을 때, 입력값이 이상할때 만들어 두었다.
- Handler 에서도 해당 에러를 아래와 같은 방법으로 나타나게 수정했다. 확실히 많은 정보가 담기니 훨 나은 거 같다 : >
[
"Message: Required request body is missing: public org.springframework.http.ResponseEntity<java.lang.String> com.example.scheduleappserverjpa.controller.UserController.delete(java.lang.Long,com.example.scheduleappserverjpa.dto.user.DeleteRequestDto)",
"ErrorType: class org.springframework.http.converter.HttpMessageNotReadableException",
"ErrorClass: org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor",
"ErrorMethod: readWithMessageConverters",
"HttpStatus: BAD_REQUEST"
]
- handler 에는 이런 에러 들을 담아냈다.
Lv 6. 비밀번호 암호화 도전
- Lv.3에서 추가한 비밀번호 필드에 들어가는 비밀번호를 암호화합니다.
- 암호화를 위한 PasswordEncoder를 직접 만들어 사용합니다.
- 과제에 주어진 코드를 이용해서 회원가입, 로그인 시 실행해야할 로직을 작성해주었다.
- 먼저 회원가입이다.
public SignUpResponseDto signUp(SignUpRequestDto dto) {
// pwd 암호화
String encode = passwordEncoder.encode(dto.getPwd());
// user Entity 생성
User user = new User(dto.getName(), dto.getEmail(), encode);
// Save Repository
User saved = userRepository.save(user);
return SignUpResponseDto.from(saved);
}
- 암호화한 pwd 를 Repository 에 넣어준다.
- 그런 후 Login 할 때 비교해주어야 한다.
public FindResponseDto login(LoginRequestDto dto) {
// 이메일에 맞는 유저 찾기
User findUser = userRepository.findByEmailOrElseThrow(dto.getEmail());
// 유저의 비밀번호를 가져와서 비교
boolean isMatch = passwordEncoder.matches(dto.getPwd(), findUser.getPwd());
if(!isMatch) {
throw new InvalidPasswordException("비밀번호가 틀렸습니다.");
}
return FindResponseDto.from(findUser);
}
- 나는 처음에 아래와 같은 방식을 생각했었다.
- 근데 알고보니,, 같은 String 값을 가진 pwd 라고 해도, 매번 암호화 할 때마다 다른 값으로 설정된다는 것이다.. !?
- 그래서 이런 방식이 아닌 2번의 방식으로 코드를 만들어야 됐다 : >
✅ 오늘의 회고
- 아직은 인가에 대해서 자세한 부분을 다루지 않았는데, 내일 인가에 대한 부분을 조금 더 수정해볼 생각이다.
- 로그인 되어 있는 세션을 이용하여 해당 세션의 아이디를 가진 유저가 수정할 수 있어야 하기 때문에 해당 부분은 좀 더 세밀하게 다루어야할 거 같다. 유저 뿐만 아니라, 일정도 이런 방향으로 수정해야 되기 때문에 ! 할 부분이 많다 : >,,, 이 부분을 댓글 CRUD 와 Paging 처리 이후에 할 까 생각도 든다.
'백엔드 부트캠프 > TIL' 카테고리의 다른 글
[내일배움캠프Spring-32일차] CH 3 일정 관리 앱 Develop Lv7~Lv8, Refactoring (1) | 2025.04.02 |
---|---|
[내일배움캠프Spring-31일차] 과제 트러블슈팅 (1) | 2025.04.01 |
[내일배움캠프Spring-29일차] CH 3 일정 관리 앱 Develop Lv0~Lv1 (0) | 2025.03.28 |
[내일배움캠프Spring-28일차] Spring DI/IoC (1) | 2025.03.27 |
[내일배움캠프Spring-27일차] 일정 관리 앱 개발 회고 (0) | 2025.03.26 |