본문 바로가기

backend/CICD

JWT 인증을 nginx에서 처리하기 위한 방법 lua

목적

JWT 인증을 nginx에서 처리하기 위해서 lua 스크립트를 사용했습니다.
현재 프로젝트에서 MSA로 설계했습니다.
이 경우 서버 마다의 JWT 인증을 어떻게 처리할지 고민이였습니다.

nginx는 JWT 파서를 오픈소스로 제공하지 않기 때문에 다른 방법을 사용해야 했습니다.
그래서 konglua 같은 스크립트 방식, 또한 SPRING GATEWAY API를 찾아보았습니다.
저희 프로젝트 규모에 맞고, 목적(JWT 인증처리만)에 맞고,
이미 nginx를 통해 리버스 프록시를 사용하고 있기 때문에, LUA를 사용하고자 했습니다.
(특히 nginx plus에 대한 정보가 많이 부족했습니다.)

아래는 설정 방법입니다.

Lua 스크립트


local function verify_jwt()
    local args = ngx.req.get_headers()
    local token = args["Authorization"]

    if not token then
        ngx.status = ngx.HTTP_UNAUTHORIZED
        ngx.say("Missing Authorization header")
        return ngx.exit(ngx.HTTP_UNAUTHORIZED)
    end

    token = token:match("Bearer%s+(.+)")
    if not token then
        ngx.status = ngx.HTTP_UNAUTHORIZED
        ngx.say("Invalid Authorization header")
        return ngx.exit(ngx.HTTP_UNAUTHORIZED)
    end

    local jwt_obj = jwt:verify("OurKey", token)
    if not jwt_obj.verified then
        ngx.status = ngx.HTTP_UNAUTHORIZED
        ngx.say("Invalid token")
        return ngx.exit(ngx.HTTP_UNAUTHORIZED)
    end

    ngx.req.set_header("X-User-ID", jwt_obj.payload.sub)
end

return verify_jwt

프론트엔드 코드
javascript

import axios from 'axios';

const response = await axios.get('http://our-backend-domain/api/protected', {
  headers: {
    Authorization: `Bearer ${token}`, // JWT를 Authorization 헤더에 포함
  },
});

// 요청 인터셉터 설정
const apiClient = axios.create({
  baseURL: 'http://your-backend-domain',
});

apiClient.interceptors.request.use(
  config => {
    const token = localStorage.getItem('accessToken');
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// 응답 인터셉터 설정
apiClient.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      const refreshToken = localStorage.getItem('refreshToken');
      if (refreshToken) {
        try {
          const response = await axios.post('http://your-backend-domain/api/refresh-token', {
            token: refreshToken,
          });
          const newAccessToken = response.data.accessToken;
          localStorage.setItem('accessToken', newAccessToken);
          apiClient.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
          return apiClient(originalRequest);
        } catch (e) {
          console.error('Refresh token expired, please log in again', e);
          // 로그아웃 처리 또는 사용자에게 재로그인 요청
        }
      }
    }
    return Promise.reject(error);
  }
);

export default apiClient;

위 경우 401이 나온다면 갱신하는 프로세스를 추가했습니다.

nginx.conf
nginx

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  example.com www.example.com;

        location / {
            proxy_pass http://172.28.0.2:80;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /api {
            #access_by_lua_file /etc/nginx/lua/verify_jwt.lua;
            proxy_pass http://172.28.0.5:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        listen [::]:443 ssl ipv6only=on; # managed by Certbot
        listen 443 ssl; # managed by Certbot
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    }

    server {
        listen 80;
        listen [::]:80;
        return 404; # managed by Certbot
    }
}

현재는 배포가 user밖에 안 되어 있기 때문에 주석처리로 확인해보지 않았습니다. 이후 해당 부분을 확인해도 제대로 동작하는지 확인해보고자 합니다.

확인 완료했고 모두 정상 동작합니다 !

'backend > CICD' 카테고리의 다른 글

Mixed Content 에러 해결  (0) 2024.08.01
CORS 문제  (1) 2024.07.31
배포자동화 CI/CD AWS EC2, Git Action, Docker  (0) 2023.05.17