[til] 토큰 기반 인증/인가 방식 수정 및 문제해결

GuruneLee·2023년 1월 1일
0

Let's Study 공부해요~

목록 보기
24/36
post-thumbnail

문제

cleo-admin 은 JWT기반 인증 방식을 사용한다. 다만, 기존에 webapp에서 토큰을 localStorage에서 관리하고, backend에서도 토큰의 만료기간을 1년으로 하는 등 제대로 관리하지 않았었다. 인가(Authorization) 방식 또한 로그인 시 받아온 siteType을 localStorage에서 관리하고 로그아웃 시 삭제하는 방식으로 관리되었다.

그러나, JWT의 accessToken기간을 짧게 하여 발행된 토큰의 사용가능 기간을 최대한 줄이는 식으로 사용해야 안전하며, 클라이언트 측에서 JS로 접근할 수 있게되면 XSS 공격에 노출될 위험이 있기 때문에 JS로 접근할수 없도록 HttpOnly 쿠키에 담아 관리하는 방식이 안전하다.

따라서, cleo-admin 웹의 인증/인가 방식을 업데이트 하기로 결정하였다.

구현 및 트러블 슈팅

jwt 를 다루는 것이 처음이었고, 웹앱을 개발하는 것도 처음, vue를 개발하는것도 처음이었기 때문에 많은 시행착오가 있었다.

1) JWT 쿠키를 요청과 응답에 담기**

가장 먼저, 서버측에서 response의 헤더에 'Set-Cookie'로 담겨온 access/refresh 토큰이 브라우저에 저장되지 않았다. Http Response에 Set-Cookie에 담겨 날아온 쿠키는 당연히 브라우저의 쿠키 스토리지에 저장되어야 했는데 그렇지 않아 서치를 해보았다. MDN을 보자 (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials)
`
The Access-Control-Allow-Credentials response header ***tells browsers whether to expose the response to the frontend JavaScript code when the request's credentials mode (Request.credentials) is include.

When a request's credentials mode (Request.credentials) is include, browsers will only expose the response to the frontend JavaScript code if the Access-Control-Allow-Credentials value is true.***

Credentials are cookies, authorization headers, or TLS client certificates.
`

정리하면, "응답이 Acess-Controll-Allow-Credintials 헤더를 담고 있어야 Cookie정보을 확인할 수 있다. 그리고 이런 응답을 받기 위해선 요청이 credential 모드여야 한다" 라는 말이다.
내가 할 일은 axios요청을 credential모드로 날려주면 된다. 이는, webapp에서 axios 요청에 {withCredential: true} 설정을 넣어 해결하였다. 모든 요청에 토큰을 담아 보낼 것 이므로, axios 전역 객체에 다음과 같이 추가해주었다.

const instance = axios.create();
instance.defaults.withCredentials = true

credential 모드 설정 이후 한 가지 더 해줘야 한다. cleo-admin webapp은 s3를 사용해 호스팅 하고 있었고, 이는 front 소스의 출처와 api서버의 출처가 다르다는 것을 의미한다. 따라서, 쿠키를 정상적으로 보내기 위해선 SameSite 설정을 풀어주어야 했다.

일단 쉽게 가자는 판단으로 SameSite=none으로 설정하여 쿠키를 발행하였다.
( SameSite=none 쿠키를 크롬에서 사용하기 위해선 쿠키가 Secure해야 했고, 내가 짧은 지식으로 Secure쿠키는 Https에서만 사용가능하다. 개발 환경이 localhost 여서 안되지 않을까 걱정했는데, 다행히 localhost에선 https가 아니어도 secure이 사용 가능하다고 한다. MDN을 보자)

2) 토큰 만료시 로그인 해제**

webapp에서 가진 JWT가 만료되면 당연히 로그아웃이 되어 서비스를 이용할 수 없게 해야한다. api에선 당연히 막혀있었고, webapp에서 해야하는 것은 서비스를 이용할 수 없음을 사용자에게 알리고 재 로그인을 하라고 안내하는 것 이다.

처음 생각은 이러했다
"vue router로 페이지가 이동될 때 마다 토큰 validation 요청을 보내서, invalid하면 로그인 페이지로 리다이렉트하자"

하지만, cleo-admin 웹은 SPA (Single Page Application) 이며, 라우팅을 하지 않고 데이터만 가져오거나 컴포넌트만 갈아끼우는 동작에선 vue router에 등록한 인증확인 로직이 실행되지 않는다.

그래서 다시 생각한 것이 api 요청을 보냈을 때, 자동으로 validation을 해주니 axios interceptor를 이용하여 invalid error를 잡아 리다이렉트 하는 방식이었다.

물론, 그냥 JWT가 만료되면 바로 로그아웃 시키는게 베스트지만, 우리 서비스가 websocket을 사용하는것도 아니고, 만료 시간을 webapp에서 저장해두는 것은 오버헤들라고 생각했기 때문에 api요청마다 validation을 하는것이 가장 현실적일 것이라고 생각하였다.

여기서 또 문제가 발생했는데, cleo-admin 서비스는 예외마다 코드를 정해두지 않고 에러종류를 정확시 '메시지'로만 파악할 수 있었다 (심지어, webapp에서 String을 잘못 보내준 것도 서버에서 정의된 enum이 없다는 이유로 500에러로 준다). 다음과 같이 에러 메시지 스트링에 'JWT expired'라는 서브스트링이 포함되어 있는지 확인할 수 밖에 없었다.

axios.interceptor.response.use(
    response => {
 
    },
    error => {
        if (error.response.data.message.includes('JWT Expired') {
            localStorage.clear();
            authAPI.logout();
            router.push('/login');
        }
    }
)

항상 개발 할때는 예외처리를 염두 하자.

Trouble Shooting

이런 에러 메시지가 뜬다.
Access to XMLHttpRequest at 'https://dev-cubecleo-api.crscube.io/api/v1/login' from origin 'https://dev-cubecleo.crscube.io' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

cors 정책을 위반했다는 건데, 이게 항상 뜨는게 아니라 규칙적인 주기로 나타난다.

다음 이미지는 로컬에서 데브 서버로 10초에 한 번씩 요청을 보내고, 그 중 cors에러가 나타났을때 timestamp를 기록한 로그다. 20분 동안 기록하였으며, 5분 간격으로 약 1분동안 에러 응답이 지속되는 규칙적인 모습을 보였다.

'local서버에서는 잘된다 + 에러가 주기적으로 생긴다' 라는 큐를 보고 'AWS의 dev 서버 인스턴스에 문제가 있나' 라는 생각을 하였다.

devops팀에 문의해보니 서버가 5,6분 간격으로 죽었다 살았다를 반복하는 것을 확인할 수 있었고, 이내 수정되었다.

etc

처음 해본 것 투성이었지만, 백엔드 개발자 분과 함께 잘 마무리 해낸거 같아 뿌듯하다.

profile
Today, I Shoveled AGAIN....

0개의 댓글