스프링 숙련주차 팀과제 담당부분(회원,인증) 고민or 어려웠던점 and 회고
1. 프로필 수정 페이지 요청시 유저 pk값 받아야 하나?
처음에 프로필 수정 페이지 요청시 PathVariable로 유저 id 까지 받는 방식으로 하고 서비스에서 컨트롤한테 받은 id로
User테이블에서 검색한 유저랑 현재 로그인중인 유저 정보로 User테이블랑 유저랑 같은지 체크까지 하는 작업을 했는데
일반 여러 사이트(인프런,코드잇,네이버등등)확인해보니 프로필 수정시 PathVariable로 유저 id안보내고 오직 현재 로그인중인 유저 정보로 User테이블에서 유저 정보 가져오기만한다.
이렇게 하면 클라쪽에서도 오류 받을일 없고 서버쪽도 db에서 일일이 유저정보 가져오고 체크하는 작업 안해도 되서 서로 편하고 좋다. 그래서 이미 일일이 다 체크하는 작업을 지우고 깔끔하게 현재 로그인중 유저 정보로 User테이블에서 유저 정보 가져오는거로 변경했다!
2. JWT 로그아웃 어떻게 할까?
먼저 JWT 시큐리티 방식으로 로그인을 구현했는데 과제에 로그아웃 기능을 추가해야한다 나와서 이생각이 들었다.
"Jwt는 서버에서 토큰의 정보를 저장하지 않고,토큰 정보 필요한 api요청시 토큰 검사후 시큐리티컨텍스트에 넣고 요청한 api작업이 끝나면 자동으로 시큐리티 컨텍스트에서 사라지는 방식인데 로그아웃이 존재하나??! "
그래서 이 로그아웃을 알아보니 리프레시 토큰을 이용해서 로그아웃시 리프레시 토큰을 블랙리스트로 등록한다.
여기서 리프레시 토큰을 블랙리스트로 등록한다는거는 서버에서 토큰을 관리한다는건데 그러면 세션방식이나 같지 않나 생각 들었지만 그래도 엑세스 토큰을 등록하는게 아닌 리프레시 토큰만 관리해서 안전성을 따지면 괜찮다 생각들었다.
이제 리프레시 토큰을 Redis한다 했는데 지금 상황에서 Redis를 하는건 너무 크고 오래 걸리니 일단 일반 DB인 블랙리스트 테이블을 만드는거로 결정했다!
그리고 이제 유저가 로그아웃 하면 로그인때 발급 받았던 리프레시 토큰을 블랙리스트 테이블에 등록 한다.
3. 프로필 수정시 PUT,PATCH
(실제 naver에서 일하고 있는분한테 물어보니 변경할때 put,patch메소드 이용안하고 post로 한다해서 어떤 메소드로 해야하나 정해진건 없다)
일단 변경 로직 부분은 프론트쪽이랑 정해야 하는게 많아서 복잡하다.
(1) put
일단 사용자가 프로필 수정 요청할시 서버에서는 해당 사용자 정보 객체 찾은뒤 비밀번호 제외 나머지 필드 담긴 정보를 클라한테 다 넘겨줘서 사용자 화면에는 변경전 정보가 form에 다 적혀 있도록 출력하게 해준다.
사이트 마다 다르지만 대부분 원하는 form 정보 변경후 모든 form 정보를 서버에 한번에 보내는 방식으로 해서 나도 그 방식으로 했다.
만약 이름,이메일,소개 필드 있는데 이름만 변경한다 할때 변경후 요청 보내면 requestbody에 (변경할이름,이전이메일,이전소개) 이렇게 모든 필드 정보들이 담겨있는데 서버에서는 일단 해야일 일이
1)클라한테 받은 request의 필드 값들이 일단 현재 db에 있는 값이랑 동일한지 체크(어떤 필드들이 변경하는건지)
2)동일하지 않은 필드가 있으면 그 필드는 변경한다는건데 변경값이 다른유저값이랑 중복으로 있는지 체크(엔티티 필드에 유니크 설정했으니)
3) 2번의 예외 발생 안하면 db에 변경내용 저장
이렇게 하는데 1번의 경우처럼 request에 모든 각각의 필드들 변경 안하는건지 따로 체크 해야하는게 문제다.(필드의 갯수가 많아지면 힘들듯)
*만약 1번을 안하고 바로 모든 필드 2번을 바로 하면 유니크가 걸려있어서 변경 안하려하는 필드도 오류 일어나서.
(2) patch
클라한테 이전의 정보 다 보내주지만 프론트쪽은 form에 이전의 정보가 흐릿하게 보이도록하고 변경저장 버튼 누르면 서버한테 변경한 필드만 보내주도록 한다면
서버는 requestdto에 있는 필드랑 비교하면서 null체크를 해야한다.(mapstruct 라이브러리나 null체크 로직 직접 생성등)
null체크하고 위에 pat의 서버에서 해야하는일 2번부터 하면 된다.
4. 프로필 수정 로직 클라한테 상태코드,메세지 반환은 정상적으로 하는데 DB적용 안되는 경우
코드가 오류 없이 다 동작하고 클라한테까지 200상태코드,유저정보 변경 완료 메세지 까지 보내는데 확인해보니 DB에는 변경하는 값이 적용이 안되고 있었다.
그래서 더티체킹쪽 문제 의심이 들었는데 확인해도 왜 안되는거지? 전에 했던 개인과제랑 같은데 생각 했는데 더 고민해보다 발견한게 있었다!!
이렇게 변경할 유저 자체를 매개변수로 받아온 상태라 메소드 안에서 작업해도 영속성 컨텍스트에 해당 유저가 없어서 DB를 아예 사용 안하고 있었던거다.. 그래서 해결 방법을
(1) 매개변수로 받아온 유저 객체로 db에서 해당 유저 객체 불러오기
(2) 더티체킹 방식 안하고 그냥 직접 save로 변경사항 저장하기
이렇게 두가지 방법이 있는데 (1)번 방법은 이미 매개변수로 변경적용할 유저 받아 왔는데 또 DB에서 이 유저를 찾아 받아오기엔 좀 그렇고 코드도 더 길어지니 (2)번 방법으로 했다.
@Transactional
public ProfileResponseDto updateProfile(ProfileRequestDto requestDto, User user) {
//변경할 필드가 무엇인지 체크 하고 변경할 필드만 DB중복 체크
HashMap<String, String> map = requestDto.fieldChangeCheck(user);
for (Map.Entry<String, String> entry : map.entrySet()) {
switch (entry.getKey()){
case "username" -> nameCheck(entry.getValue());
case "email" -> emailCheck(entry.getValue());
}
}
user.profileUpdate(requestDto);
//유저를 매개변수로 받아와서 더티체킹이 불가라 직접 일부로 save방식 선택
userRepository.save(user);
return new ProfileResponseDto(user);
}
5. 엑세스 토큰 만료시 리프레시 토큰으로 엑세스 토큰 재발급
리프레시 토큰 기능을 넣은건 이번이 처음이라 잘 몰랐는데 액세스 토큰이 만료일시 리프레시 토큰으로 액세스 재발급 받을때 직접 클라가 재발급 받는 url요청을 하도록 했다.
근데 생각해보니 이건 인증 필요한 api요청시 마다 액세스 토큰 유효 검사하는데 거기서 오류 나면 바로 자동으로 리프레시 토큰 확인하고 엑세스 토큰 발급해야 정상 아닌가 생각이 들었다....
이 방법이 아닌 경우 프론트쪽에서 어떻게든 해서 해결 할 수 있지만 보통의 방법이면 서버가 자동으로 다 해줘야 하는게 맞다 들었다.
시간이 모잘라서 이부분은 직접 코드 해결은 못했지만 해결 방법은
인가필터쪽에서 엑세스 토큰 유효성 검사 오류 나면 리프레시 토큰 발급 로직이 실행되도록 연결시키면 되는것같다!
6. 로그아웃은 시큐리티 필터에서 안하고 디스패처서블릿에서 할때 api url을 /logout 으로 하면
일단 리프레시 토큰을 이용해서 로그아웃시 리프레시 토큰만 블랙리스트 걸도록 DB에 해당 유저 리프레시 토큰 추가 방법으로 했다.
그래서 필터에서 수행말고 디스패처서블릿에서 수행하도록 했고 url주소를 /logout 로 했는데 갑자기 오류가 난다. 그래서 한번 /logout/page 로 변경하고 해보니 이때는 아무이상 없이 수행하고...
알고보니 url주소를 딸랑 /logout 로하면 스프링 시큐리티 내부의 코드로 인해 시큐리티 내부 로그아웃 코드가 실행 하는거였다.
공식 문서 https://docs.spring.io/spring-security/reference/servlet/authentication/logout.html 보면 ↓ 적혀있다.
By default, Spring Security stands up a /logout endpoint, so no additional code is necessary.
그러므로 url 주소 정할때도 스프링자체 내부 구현된 코드 url인가 확인 잘 해야한다!
회고
이번 프로젝트에서 저는 회원관련쪽(가입,로그인,회원정보수정,인증)쪽을 담당 했는데 저번 개인과제의 사항과 많이 비슷하지만 저번과 다르게 리프레시 토큰 방식,로그아웃,회원정보 수정을 추가 했습니다.
리프레시 토큰은 액세스 토큰이 만료일시 리프레시 토큰을 통해 액세스 토큰을 재발급 받도록 하는건데 코드 구현 작업 다 마치고 나서 왜 이렇게 했지? 생각든 부분이 있습니다.
일단 처음이라 액세스 재발급 받을때 직접 클라가 재발급 받는 url요청을 하도록 했는데 생각해보면 액세스 토큰 인증 필요한 api요청시 액세스 토큰 만료면
자동으로 리프레시 토큰 검사후 액세스 토큰 재발급 하는게 보통의 경우 아닌가? 생각 들어서 다음에도 리프레시 토큰 방식 한다면 이렇게 해볼겁니다!
그리고 로그아웃은 처음에 ""Jwt는 서버에서 액세스 토큰의 정보를 저장하지 않고,액세스 토큰 정보 필요한 api요청시 토큰 검사후 시큐리티컨텍스트에 넣고 요청한 api작업이 끝나면 자동으로 시큐리티 컨텍스트에서 사라지는 방식인데 로그아웃이 존재하나??! " 생각이 들었는데 그래도 안전을 위해 리프레시 토큰만 로그아웃시 블랙리스트로 등록 하자해서 DB에 리프레시 토큰 정보 등록하는 방식으로 했습니다. 그리고 이부분을 알아보니 Redis라는 것을 사용한다는데 시간이 부족해서 일반 DB테이블을 이용했습니다.
이렇게 리프레시 토큰,로그아웃을 처음 도전해 보니 예외처리 해야 하는 경우가 좀 있어서 복잡했지만 JWT랑 시큐리티에 대해 다시 복습해서 저번 개인과제때보다 더욱 이해가 잘 됐고,회원 정보 수정쪽에서 변경시 왜 DB에 적용 안되나 문제 일어났는데 영속성 컨텍스트의 더티체킹쪽도 다시 복습하고 해결해서 이번엔 여러 가지 복습 위주를 한 것 같습니다.
예외로 저희팀은 애초에 다른팀과 다르게 저 포함 3명 이였는데 한분은 아프셔서 참여 못 하시고 2명에서 작업해서 선택이나 도전사항은 못하고 필수쪽만 했던게 약간 아쉽긴 했습니다.
나중 더 알아보거나 피드백?
1.기능 별로 패키지 폴더 따로 나누기
2. post맨 기능별로 창을 추가해서 하기.
3. 깃 플로우에 대해
4. 복합키에 대해
5. 회원가입시 이메일 인증을 하도록 하기