이번 과제는 저번 주에 JDBC로 구현했던 일정관리 앱을 JSP로 구현하고, 로그인 필터, 암호화 등의 요구사항이 추가되었다.
튜터님께서 기술이 불편하면 새로운 기술이 나타난다고 하신 말씀이 생각난다.
실제로 JDBC을 이용하다가 JPA를 이용해보니 코드가 훨씬 간결해지고 편하게 짤 수 있었다.
하지만 지금도 계속 새로운 기술이 개발되고 있겠지..
과제하면서 제일 구현하기 어려웠던 요구사항은 4. 로그인(인증) 이다. 지금은 어느정도 이해가 되긴하지만 chain 이용에 대해서는 아직.. 더 공부해야할 것 같다.
API 명세
기능 | Method | URL | request | response | 상태코드 | 설명 |
일정 생성 | POST | /todos | [JSON] ∙title ∙contents |
TodoResponseDto | 200 CREATED | 작성일, 수정일 👉🏻JPA Auditing Session에 있는 유저 정보로 일정 작성 |
전체 일정 조회 | GET | /todos | [Param] ∙modifiedAt ∙userId ∙page ∙ size |
<List> TodoPageResponseDto | 200 OK | 일정 페이징 조회 : 일정의 수정일을 기준으로 DESC |
특정 일정 조회 | GET | /todos/{todoId} | TodoPageResponseDto | 200 OK | 일정 ID로 조회 | |
특정 일정 수정 | PATCH | /todos/{todoId} | [JSON] ∙title ∙contents |
TodoResponseDto | 200 OK | 일정 ID로 수정 |
특정 일정 삭제 | DELETE | /todos/{todoId} | 204 No Content | 일정 ID로 삭제 | ||
유저 생성 (회원가입) | POST | /users/signup | [JSON] ∙userName ∙password |
SingUpResponseDto | 200 CREATED | 작성일, 수정일 👉🏻JPA Auditing |
전체 유저 조회 | GET | /users | <List> UserResponseDto | 200 OK | 회원 가입한 모든 유저 조회 Bcrypt 해시암호 |
|
특정 유저 조회 | GET | /users/{userId} | <List> UserResponseDto | 200 OK | 유저 ID로 조회 | |
특정 유저 수정 | PATCH | /users/{userId} | [JSON] - userName |
UserResponseDto | 200 OK | 유저 Id로 수정 |
특정 유저 삭제 | DELETE | /users/{userId} | 204 No Content | 유저 Id로 삭제 | ||
로그인 | POST | /login | [JSON] - userName |
LoginResponseDto | 200 OK | 이메일과 비밀번호로 로그인 → 성공시 Session에 저장 |
로그아웃 | POST | /logout | 204 No Content | |||
댓글 작성 | POST | /todos/{todoId}/comment | [Text] ∙Content |
CommentresponseDto | 200 CREATED | 작성일, 수정일 👉🏻JPA Auditing Session에 저장된 UserId |
전체 댓글 조회 | GET | /todos/{todoId}/comment | <List> CommentresponseDto | 200 OK | 일정 Id에 등록된 댓글 전체 조회 | |
내가 작성한 댓글 조회 | GET | /todos/mycomments | <List> MyCommentresponseDto |
200 OK | 내가 작성한 모든 댓글 조회 | |
내가 작성한 댓글 수정 | PATCH | /todos/mycomments/{commentId} | [Text] ∙Content |
<List> MyCommentresponseDto | 200 OK | 내가 작성한 댓글을 댓글Id로 조회하여 수정 |
내가 작성한 댓글 삭제 | DELETE | /todos/mycomments/{commentId} | 204 No Content | 내가 작성한 댓글을 댓글Id로 조회하여 삭제 |
ERD
로그인한 유저 정보를 세션에 저장 , Filter 인증
LoginFilter.java
@Slf4j
public class LoginFilter implements Filter {
// 회원가입, 로그인 요청은 인증 처리에서 제외
private static final String[] WHITE_LIST = {"/users/signup", "/login", "/logout"};
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// request를 쉽게 사용하기 위해 down casting
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
HttpServletResponse httpResponse = (HttpServletResponse) response;
log.info("로그인 필터 로직 실행");
// 로그인을 체크해야하는지 검사
// WHITE_LIST에 포함된 경우 true -> !true = false
if(!isWhiteList((requestURI))) { // WHITE_LIST에 포함되어 있지 않다면
// 로그인 확인 (로그인 하면 session에 값이 저장되어 있다는 가정)
// 세션이 존재하면 가져온다. 세션이 없으면 session = null
HttpSession session = httpRequest.getSession(false);
// 로그인하지 않은 사용자인 경우
if (session == null || session.getAttribute("USER_ID") == null) {
throw new RuntimeException("로그인 해주세요. ");
}
}
chain.doFilter(request, response);
}
public boolean isWhiteList(String requestURI) {
//WHITE_LIST로 만들어놨던 URL에 포함되어 있지 않다면 false 반환
return PatternMatchUtils.simpleMatch(WHITE_LIST, requestURI);
}
}
WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean loginFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
// Filter 등록 (로그인 필터)
filterRegistrationBean.setFilter(new LoginFilter());
// Filter 순서 결정
filterRegistrationBean.setOrder(1);
// 전체 URL에 Filter 적용
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
여기서 Filter는 로그인 필터 하나만을 사용해서 우선순위 의미가 없지만.. 만약 필터를 추가한다면 우선 순위를 결정해 주면된다.
💥 trouble 1. chain.doFilter 사용
if (!isWhiteList(requestURI)) { // WHITE_LIST에 포함되어 있지 않다면
// 로그인 확인 (로그인 하면 session에 값이 저장되어 있다는 가정)
// 세션이 존재하면 가져온다. 세션이 없으면 session = null
HttpSession session = httpRequest.getSession(false);
// 로그인하지 않은 사용자인 경우
if (session == null || session.getAttribute("USER_ID") == null) {
throw new RuntimeException("로그인 해주세요.");
}
}
log.info("로그인 성공1"); // doFilter 전
chain.doFilter(request, response);
log.info("로그인 성공2"); // doFilter 후 루프를 돌고옴
WHITE_LIST에 속하는 /logout URL에 GET 요청을 했더니 로그인 성공 log가 찍혔다.
Filter Chain은 위와 같이 동작한다. 루프를 돈다고 생각하면 된다.
chain.doFilter(request, response) 전의 코드를 필터 우선 순위대로 실행한 후, 루프를 돌았다면 chain.doFilter(request, response) 후의 코드를 다시 루프 도는 것이다.
따라서, Filter chain을 사용할 때에는 구조와 순위를 잘 생각해서 사용할 것!
💥 trouble 2. 세션에 저장된 로그인 유저 인증
일정 CRUD, 댓글 CRUD, 유저 수정, 삭제 등을 하려면 Login Filter를 거쳐 로그인을 한 상태여야지 url 요청이 가능하게 설계하였다.
그리고 세션에 저장된 유저 정보로 일정이나 댓글을 생성할 수 있게 API를 구현하려고 했다. (param이나 body로 유저 id를 안 넣어도 됨)
동작 순서를 정리해보면,
회원가입 → 로그인 → 일정 생성 URL post 요청 → userId = 세션에 저장된 유저 식별자 → DB에 일정 저장
와 같이 동작한다.
그런데, url 요청이 들어올 때 로그인한 유저 정보를 어떻게 저장해야하는지 몰랐다.
찾아보니 세션 값을 가져올 수 있는 @SessionAttribute("Key") annotaion이 있다는 것을 알았다.
id 변수에 annotation을 적용하고, requestDto와 함께 Service에 파라미터로 전달해주었다.
@RestController
@RequestMapping("/todos")
@RequiredArgsConstructor
public class TodoController {
private final TodoService todoService;
private static final String USER_ID = "USER_ID";
// 일정 생성
@PostMapping
public ResponseEntity<TodoResponseDto> save(@SessionAttribute(USER_ID) Long userId, @RequestBody TodoRequestDto requestDto) {
TodoResponseDto todoResponseDto = todoService.save( //service 요청
requestDto.getTitle(),
requestDto.getContents(),
userId
);
return new ResponseEntity<>(todoResponseDto, HttpStatus.CREATED);
}
}
@Service
@RequiredArgsConstructor
public class TodoService {
private final TodoRepository todoRepository;
private final UserRepository userRepository; // Todo N : User 1 연관 관계
private final CommentRepository commentRepository;
// 일정 생성
public TodoResponseDto save(String title, String contents, Long userId) {
User findUser = userRepository.findByUserIdOrElseThrow(userId); //세션에 저장된 로그인 유저ID
Todo todo = new Todo(title, contents);
todo.setUser(findUser);
Todo savedTodo = todoRepository.save(todo); // Repository에 저장
return new TodoResponseDto(savedTodo);
}
}
결과 )
일정 등록할 때 유저 정보를 body에 넣지 않아도 세션에 저장된 유저 ID로 저장되는 걸 알 수 있다.
일정 등록뿐만 아니라 유저 수정, 작성 댓글 조회 등도 이와 같이 @SessionAttribute("Key") 을 활용하면 된다.
'TIL' 카테고리의 다른 글
29일차) (0) | 2024.12.13 |
---|---|
28일차) 스프링 컨테이너, 빈, 싱글톤 (0) | 2024.12.11 |
Spring - 일정 관리 앱 만들기 과제 트러블 슈팅 (0) | 2024.12.10 |
19일차) 객체간의 결합도와 다형성, 인터페이스 (2) | 2024.11.29 |
Java 키오스크 과제 - 챌린지 Lv1 트러블 슈팅 (1) | 2024.11.28 |
댓글