2. API Key 검증 로직 구현

프라이마리모·2024년 12월 29일

REST API

목록 보기
3/4
post-thumbnail

이 시리즈에서는 JPA 예시 코드를 기반으로, 실제 운영 환경에서 각기 다른 도메인끼리의 통신을 가정한 RESTful API로 디벨롭한다.
* 기본 웹 프로젝트 세팅 완료된 상태에서 시작, 테스트는 POSTMAN 활용

디벨롭 단계

  • Interceptor 생성 및 적용
  • API Key 검증 로직 구현
  • JWT 발급, 인증/인가 로직 구현
    • 클라이언트 용 인증 로직 구현
    • 서버용 JWT 발급 로직 구현
    • 서버 인터셉터 내 검증 구현
      • JWT의 형식 검증
      • JWT의 서명(Signature) 검증
      • 클레임(예: iss, exp, scope) 검증.
  • OAuth 2.0 기반 인증/인가 로직 구현
    • 클라이언트 용 Access Token 발급 로직 구현
    • 서버 인터셉터 내 검증 구현
      • Access Token의 형식 검증
      • 인증 서버에 검증 요청(옵션, 원격 검증)
      • Access Token의 클레임(예: scope, exp) 검증

HTTP 인증(Authorization)은 ID/PW 인증 방식처럼 HTTP 인증으로 서버에 접근하는 클라이언트가 권한을 갖고 있는지를 확인한다.

HTTP 통신

HTTP 통신은 클라이언트-서버 간에 요청(Request)과 응답(Response)으로 HTML 문서 및 웹 리소스를 호출하며 발생한다. 클라이언트(ex: 웹브라우저)가 서버에 요청을 보내면, 서버는 요청한 리소스를 돌려준다.

HTTP 통신 시 보호된 서버 리소스를 접근하는 클라이언트에는 인증 정보를 확인한다. HTTP 인증 프레임워크는 요청 시 아래와 같은 인증 헤더를 사용한다.

Authorization: <type> <credentials>

대표적으로 응답 받는 형태는 다음과 같다.

  • 200 : OK, 통신 성공
  • 401 : Unauthorized, 인증 헤더 누락 등 잘못된 인증 정보
  • 403 : Forbidden, 정상적인 인증 정보이나 권한이 없는 접근

인증 정보는 인증 방식(Type)에 따라 달라진다.

Basic 인증

Basic 인증 방식은 말 그대로 가장 기본적인 인증 방식이다. 인증 정보로 사용자 ID와 비밀번호를 사용하는데, base64로 인코딩한 “사용자ID:비밀번호” 문자열을 Basic과 함께 인증 헤더에 입력한다.

Authorization: Basic base64({USERNAME}:{PASSWORD})

장점

  • 단순하고 구축이 간단하다.
  • 사용자 ID와 비밀번호 외에 로그인 페이지나 별도의 인증 정보를 요구하지 않는다.

단점

  • Base64는 쉽게 복호화할 수 있어, 단순 base64 인코딩된 사용자 ID와 비밀번호를 HTTP로 전달 시 요청 보안이 보장되지 않는다.
  • 보안을 위해 반드시 HTTPS, SSL/TLS로 통신해야한다.
  • 서버에 사용자 목록을 저장한다.
    • 요청한 리소스가 많거나 사용자가 많으면 목록에서 권한을 확인하는 시간이 길어진다.
    • 사용자가 많거나 사용자 변화가 잦은 서비스인 경우 서버에 부담이 가중된다.
  • 정교한 사용자 별 권한 설정이 까다롭다. 추가 기능 구현이 필요하다.

Bearer 인증

Bearer 인증 방식은 OAuth 2.0 프레임워크에서 사용하는 토큰 인증 방식이다. Bearer 와 토큰을 인증 헤더에 입력한다.

Authorization: Bearer <token>

OAuth 2.0 프레임워크

OAuth 프레임워크는 다양한 서비스 또는 서버 사이에 안전하게 데이터를 전송할 수 있게 해주며, 제 3자의 클라이언트에게 보호된 리소스를 제한적으로 접근하게 해준다.
OAuth 프레임워크에는 리소스 소유자, 클라이언트, 인증 서버, 리소스 서버가 있다.

  • 리소스 소유자 : 사용자
  • 클라이언트 : 사용자의 정보를 접근하는 제 3자의 서비스
  • 인증 서버 : 클라이언트의 접근을 관리하는 서버
  • 리소스 서버 : 리소스 소유자의 데이터를 관리하는 서버
  • 구동 순서
    • 리소스 소유자의 동의 확인
    • 인증 서버가 클라이언트에게 액세스 토큰 발급
    • 클라이언트는 액세스 토큰을 사용하여 리소스 서버에 보호된 데이터 호출

Bearer 토큰

Bearer 토큰은 OAuth 프레임워크에서 액세스 토큰으로 사용하는 토큰의 유형이다.

Bearer 토큰은 불투명(Opaque)한 문자열로, 토큰의 형태는 인증 서버에서 정의하기 나름이다. 16진수의 문자열이나 JSON Web Token(JWT)을 사용하기도 한다. 어떠한 형태를 사용하든, Bearer 토큰은 클라이언트가 해석할 수 없는 형태여야 하며 사용자의 정보를 전달하면 안된다. 또한 서버에서 클라이언트의 권한을 확인할 수 있는 메타데이터가 토큰에 인코딩되어 있어야 하며, 충분히 복잡한 알고리즘을 사용하여 발급해야 한다.

장점

  • 안전하고, 확장이 쉽다.
  • 쉽게 복호화할 수 없고, OAuth 프레임워크의 서버는 SSL/TLS를 필수로 사용한다.
  • 서버에서 토큰의 리소스 접근 권한과 유효기간을 쉽게 설정할 수 있다.
  • OAuth 프레임워크에서는 리소스 접근 제어를 정교하게 설정할 수 있다.
  • 토큰 자체에 메타데이터를 갖고 있어, 서버는 토큰 보관없이 발급만 하면 된다.
  • 여러 서비스 및 서버 간에 토큰 공유가 가능하다.

단점

  • 토큰이 외부에 노출되면 다른 서비스도 토큰으로 바로 리소스에 접근할 수 있다.
    • 하지만 서버가 OAuth 프레임워크에 정의된 보안 장치를 잘 구축하면 문제가 없다.
    • 노출이 발견되면 해당 토큰의 권한을 철회할 수 있다.

API Key 검증 로직 구현

우선 인터셉터에서 간단하게 헤더에 있는 API key 값을 검증하는 로직만 구현하였다. 여기서 사용 될 토큰 값과 관련된 로직은 다음 포스팅에서 구현한다.

@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

    // 컨트롤러 호출 전에 실행
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 예: 인증 확인
        String authToken = request.getHeader("Authorization");
        if (authToken == null || !authToken.startsWith("Bearer ")) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 Unauthorized
            response.getWriter().write("Unauthorized");
            return false; // 요청 처리 중단
        }

        String token = authToken.substring(7); // "Bearer " 이후의 토큰 부분

        try {
            // 토큰 검증 로직
            if (isValidToken(token)) {
                return true; // 검증 성공 시 요청 계속 진행
            } else {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 Forbidden
                response.getWriter().write("Invalid token");
                return false;
            }
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // 500 Error
            response.getWriter().write("Error validating token");
            return false;
        }
    }
    
    ...
    
    // 토큰 유효성 검증 메서드
    private boolean isValidToken(String token) {
        try {
            if (token.equals("J3iHLt7...")) {	//보안키로 포스팅에는 전체 기재하지 않음
                return true;
            }
            return true; // 실제 검증 로직 추가 후 결과 리턴
        } catch (Exception e) {
            return false;
        }
    }
}

통신 결과

profile
개발공부 요약노트

0개의 댓글