본문 바로가기
포스코x코딩온

[포스코x코딩온] 풀스택 부트캠프 4차 프로젝트 -4

by 김선지 2024. 3. 13.

알고보니까 지금까지 구현한 jwt는 내가 좀 이상하게 만든 거였다.

 

oauth2.0이 jwt를 지원해준다고 해서 이거 썼는데 이건 구글, 카카오 로그인 같은데서 쓰는 용도로 만들어진 프로토콜이고 나는 그냥 커스텀으로 만들어서 써야 했다.

어쩐지 오류 투성이더라...

그리고 chatGPT도 거짓말 투성이다. 걸러들어서 다행이지. 신포도 할 뻔했다.

 

 access token은 oauth에서, refresh token은 jjwt에서 만든 것까지는 눈치 못채고 , 이게 당연한 거라고 생각했었는데 (oAuth에서는 당연하다는 것 같다.)

저거 구현할 수 있는 기발한 방법을 찾고자 hasRole 함수를 썼는데, role 자체를 전부 인식을 못해서 403에러가 떴다. 물론 오류코드는 아무것도 안 알려줬다.

그래서 조금 이상하게 생각했는데, jwt는 제대로 작동되서 그냥 넘어갈까 생각했다. 

하지만 이게 진짜 궁금해서 강사님께 물어보니까 이거 연동이 안되어있는거라는 말을 들었다.

그래서 검증할 겸, 로그인 시 토큰 발급에 대한 제한을 풀어서 유저 DB에 없는 아이디로도 접속해봤는데 접속이 가능했다. 즉 로그인을 해도 DB와 연동되어있는 UserDetails에 추가가 되지 않았던 거다.

진짜 검증만 했던거다.

지금 와서 생각해보니까 조금 석연찮은 부분이 많긴 헀다.

구글링이나 공식문서에서 정보를 찾으려고 했는데, 내가 한 방식으로 구현한 사람이 단 한명도 없었다.

 

뭐 덕분에 OAuth2.0 조금 공부해서 다행인가? 싶기도 하다. 확실히 그냥 토큰이 유효한지만 검증하면 유저 정보를 가져오거나 새로 만든다고 하면 이게 맞는 것 같기는 하다.

 

이상하다는 사실을 이해하고 바꾸는데까지 이틀정도 걸린 것 같다.

jwt는 기본으로 제공해주는 게 아니기 때문에 따로 설정을 좀 많이 해줘야 한다. UserDetails나 UserDetailsService같은 것들 말이다. 그리고 당연히 oauth.jwt같은 설정은 안해주고 build한다.

다만 커스텀 filter를 하나 만들어줘서 UsernamePasswordAuthenticationFilter가 적용되기 전에 먼저 필터를 적용시켜줘야 한다.

 

 

이거 하면서 오류들과 굉장히 많이 마주쳤다. 일단 제일 큰건 Bean 순환참조 오류와 nullPointerException이었다.

일단 nullPointerException같은 경우는 스크린샷을 찍지는 않았지만 찾아보니까 implement한 CustomUsersDetails.java 파일에는 어노테이션이 없다는 걸 알았다. 즉. 직접 컨스트럭터로 구현해야 한다는 뜻.

다만 이거 해도 nullPointerException이 또 떴다.

이후는 필드 @Autowired와 생성자 @Autowired로 해결할 수 있었다. 필드같은 경우는 좀 늦게 생기지만 생성자의 경우에는 빌드될 때 바로 생기기 때문에 null pointer Exception을 막을 수 있었다. 물론 spring application이 인식하지 못하는 경우 (annotation이 명시되지 않은 경우) 도 마찬가지다.

 

다음은 순환참조 오류였다.

이건 참 익숙한데... entity 양방향 참조의 경우도 이거때문에 stackOverFlow가 등장했던 추억이 있다.

각설하고 이유만 말해보자면 다음에 있었다.

 

아래 사진은 @Configuration에 있는 SecurityFilterChain의 일부다.

여기서 jwtUtilsService는 이 파일에서 autowired된 것인데, 이 서비스 안에 여기 Configure 파일에서 정의된 Bcrypt 생성자를 이용한다. 그래서 순환참조가 되기 때문에 오류가 났던 것이다.

 

그러면 어떻게 해결하나... 했더니 @Configuration 어노테이션이 두개 있어도 된다고 한다.

예전에 두개 했더니 오류뜨길래 하나만 가능한 줄 알았는데 그때는 SecurityFilterChain이 두개 있어서 그랬던 것 같다.

어쨌건 Bean을 쪼개니까 순환 참조 오류도 발생하지 않았다. 

다행이다 다행이야.

 

 

아이디어는 이분을 참고했다.

https://velog.io/@platinouss/Spring-Circular-References-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0

 

 

 

일단 이런 식으로 오류를 해결했기 때문에 jwt를 검증하는 로직이 두개가 필요하게 되었다. refresh와 access. 하나만 있으면 refresh 그대로 넣어도 access처럼 동작하게 된다.

물론 key pair를 다르게 해버리면 되긴 하지만 오히려 그렇게 하는게 더 낭비가 아닌가 하는 생각이 들었다.

뭐... 이건 role으로 해도 되고 다르게 해도 되지만 나는 issuer를 다르게 넣어서 access의 경우 access에해당하는 issuer가 아닐 경우 throw 하고, refresh의 경우 반대로 했다.

이렇게 구현에 성공한 것 같다.

그리고 key를 하드코딩한 게 아니라 spring에서 관리하게 만들어놨는데, 덕분인지 안좋은 건지 모르겠지만 서버가 재시작 될때마다 key pair가 바뀌어서 이전 토큰으로 검증이 안된다. 서명이 바뀌니까.

이런 식으로 만약에 토큰이 대량 탈취되었을때 막는 건가 싶다. 그냥 서버한번 죽였다가 키면 전부 강제 로그아웃이니까.