Skip to content

OAuth 개념 정리와 로그인 방법

김영현 edited this page Nov 23, 2024 · 1 revision

1. OAuth의 개념

  • OAuthOpen Authorization 의 약자로, 주로 사용자 정보를 보유한 서비스 (Google, kakao) 에서 인증과 권한을 위임하는 프로토콜입니다.
  • OAuth 는 애플리케이션이 사용자 비밀번호나 민감한 정보를 요청하지 않고, 사용자로부터 특정 권한을 위임받아 작업을 수행할 수 있도록 합니다.

2. OAuth 역할

image

Resource Owner (자원 소유자)

  • 자원에 대한 접근 권한을 가진 사용자입니다.
  • 예) Google Drive에 파일을 가진 실제 사용자

Client (클라이언트)

  • 자원 소유자를 대신해서 자원 서버에 접근하고자 하는 애플리케이션입니다.
  • 예) 클라이언트 앱은 Google calender에 접근하여 일정을 등록할 수 있는 웹 애플리케이션일 수 있습니다.

Resource Server (자원 서버)

  • 우리가 제어하고자하는 실제 자원, 데이터를 가지고 제공하는 서버입니다.
  • 예) Google Drive의 파일 저장 서버나, Google calender 서버가 이에 해당합니다.

Authorization Server (인증 서버)

  • 사용자의 신원을 확인하고 클라이언트가 자원에 접근할 수 있는 권한을 부여하는 서버입니다.
    • Access Token을 주는 곳이라고 생각하면 좋을 것 같습니다.
  • 보통 자원을 제공하는 서비스 제공자가 직접 운영합니다.

Access Token (접근 토큰)

  • 클라이언트가 자원 서버에 접근할 수 있도록 권한을 부여 받으면 최종적으로 Authorization Server가 발급해주는 토큰입니다.
  • 해당 토큰을 통해 클라이언트는 제한적으로 자원에 접근할 수 있습니다.

OAuth의 절차 - 1. 등록

image

Client 서비스에서 OAuth를 제공하기 위해서는 우선 Resource Server 에 그에 대한 사전 승인을 받아야합니다. 이를 “register” 한다고 합니다.


image

Resoure server 마다 OAuth에 대한 승인 절차는 다르겠지만, 공통적으로 부여해주는 항목이 꼭 있습니다.

Client ID

  • 말그대로 Client를 구별하기 위해 있는 아이디입니다. 노출되어도 크게 상관없습니다.

Client Secret

  • Client 식별에 대한 비밀번호입니다. 절대 노출이 되어서는 안됩니다.
  • Resource Owner로 부터 OAuth 인증을 허가하는데 중요한 역할을 합니다.

Authorized redirect URls

  • 권한을 부여하는 과정에서 Authorized code를 전달받을 주소입니다.
  • 만약 OAuth 요청을 해당 주소를 통해 하지않고 다른 경로에 한다면, 요청을 무시당하게됩니다.
  • Client Secret 와 함께 Resource Owner로 부터 OAuth 인증을 허가하는데 중요한 역할을 합니다.

OAuth의 절차 - 2. Resource Owner의 승인

image

앞서 ClientResource server에 등록을 했기에,

ClientResource Server에는 각각 그에 관련된 정보가 저장되어있습니다.


image

승인 과정을 알아보기전,

만약 Resource Server에 존재하는[ A, B, C, D ]의 기능 중 [B, C] 의 기능만을 사용하고 싶다면 그 인증 범위 또한 지정을 해줄 수 있습니다.

이를 scope라고 합니다. 클라이언트가 접근할 수 있는 데이터와 권한의 범위를 라고 생각해주시면 될 것 같습니다.


이제 Resource Owner의 승인 과정을 알아보겠습니다.

image

만약, Resource OwnerClient 에 있는 외부 서비스를 이용해야된다면,

위와 같이 Client 서비스는 OAuth 로그인 화면을 보여주게 됩니다.


image

해당 버튼들은 이러한 url로 이동하도록 설정되어있습니다.

여기에 client_id, scope, redirect_uri 가 명시되어있는 것을 볼 수 있습니다.

register 과정에서 보았던 것들인 것 같군요.


image

Resource Owner가 해당 버튼을 누른 후 일어나는 일은 Resource Server에 로그인 여부를 확인하는 것 입니다. 만일 로그인이 되어있지않으면 먼저 로그인을 하도록 합니다.


image

로그인에 성공을 한다면 이제 url에 있는 client_id, redirect_uri 와 기존 clientregister 과정에서 생성되었던 client_id, redirect_uri 을 비교합니다.

만일 둘 중 하나라도 다르다면 Resource Server는 해당 요청을 거절하게 됩니다.


image

client_idredirect_uri를 확인하고 일치한다면, 이제 Resource Owner에게 Client의 요청에 동의하는지에 대한 문구를 보여주게 됩니다.


image

Resource Owner가 자신의 정보를 특정 서비스에 제공하는 데 동의하면, Resource Server는 이 동의에 따라 Resource Owner의 허용 정보(사용자 ID 및 허용 범위인 scope)를 저장합니다. 여기서 scope는 클라이언트가 접근할 수 있는 데이터와 권한의 범위를 말합니다.

OAuth의 절차 - 3. Resource Server의 승인

앞서 Resource Owner의 승인을 받았으니, 이제는 Resource Server의 승인이 필요한 상황입니다.

image

Resource Server의 승인 절차는 다음과 같습니다. (상단의 그림과 함께 참고해 주세요!)

  1. Resource Owner가 서비스 제공에 동의하면, Resource ServerAuthorization Code라는 임시 인증 코드를 생성합니다.
  2. 생성된 Authorization Code가 포함된 URLHTTP 리다이렉트 방식으로 Resource Owner를 자동으로 Client에 연결시킵니다.
  3. 이를 통해 ClientAuthorization Code를 전달받을 수 있습니다.

image

이후 Client는 받은 Authorization Code와 함께 기존 인증 정보를 사용하여 Resource ServerResource Owner의 최종 인증을 요청합니다.

image

모든 인증 정보가 확인되면, Resource Server는 최종적으로 Client에게 Access Token을 발급합니다.

발급된 Access TokenClient에 저장되며, Client는 필요할 때마다 이 Access Token을 사용하여 Resource Server에 요청을 보내고 리소스에 접근할 수 있습니다.

image

Resource Server는 이 Access Token을 통해 user id가 1인 사용자가 b와 c 권한에 대한 접근을 요청하고 있음을 확인할 수 있습니다.

카카오 로그인 순서

image

  • 클라이언트가 서비스 서버로 카카오 로그인 요청을 하고 서비스 서버는 클라이언트가 카카오 로그인을 수행할 수 있게 카카오 서버로 요청을 보낸다.
@Get('kakao')
  kakaoLogin(@Res({passthrough: true}) res: Response) {
    const clientId = this.configService.get<string>('CLIENT_ID');
    const redirectUrl = this.configService.get<string>('REDIRECT_URL');
    const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUrl}&response_type=code`;

    res.redirect(kakaoAuthUrl);
    return;
  }
  
// 클라이언트
const KakaoLogin = () => {
  const handleKakaoLogin = async () => {
    try {
      window.location.href = 'http://localhost:3000/api/login/kakao';
    } catch (error) {
      console.error('카카오 로그인 에러:', error);
    }
  };

  return {
    handleKakaoLogin
  };
};

image

  • 쿼리 파라미터에서 필수로 필요한 요소들을 전부 추가했습니다. https://kauth.kakao.com/oauth/authorize 에 쿼리 파라미터를 추가하여 인가코드를 받습니다.
HTTP/1.1 302 Found
Content-Length: 0
Location: ${REDIRECT_URI}?code=${AUTHORIZE_CODE}
  • 받은 인가코드를 가지고 다시 카카오 인증/인가 처리를 진행합니다.
// 클라이언트 
export const KakaoCallback = ()=>{
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();

  useEffect(() => {
    const code = searchParams.get('code');

    if (code) {
      axios.get(`http://localhost:3000/api/login/kakao/callback?code=${code}`, {
        withCredentials: true
      })
        .then(response => {
          console.log('로그인 성공:', response.data);
          navigate('/');
        })
        .catch(error => {
          console.error('카카오 로그인 콜백 에러:', error);
          navigate('/login');
        });
    }
  }, [searchParams, navigate]);

  return (
    <div>카카오 로그인 처리중...</div>
  );
}

//서버
@Get('kakao/callback')
  async kakaoCallback(@Query('code') code: string) {
    await this.loginUserUsecase.login(code);
    return;
  }
  • 해당 서버로 코드를 가지고 다시 요청을 보내 진행합니다.

image

image

curl -v -X POST "https://kauth.kakao.com/oauth/token" \
    -H "Content-Type: application/x-www-form-urlencoded;charset=utf-8" \
    -d "grant_type=authorization_code" \
    -d "client_id=${REST_API_KEY}" \
    --data-urlencode "redirect_uri=${REDIRECT_URI}" \
    -d "code=${AUTHORIZE_CODE}"
  • 서버에 요청을 보내고 서버에서는 해당 카카오 인증서버로 요청을 보낸다.
const clientId = this.configService.get<string>('CLIENT_ID');
    const redirectUrl = this.configService.get<string>('REDIRECT_URL');
    const response = await axios.post('https://kauth.kakao.com/oauth/token', new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: clientId,
        redirect_uri: redirectUrl,
        code: code,
      }).toString(),
      {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
      },
    );
data: {
    access_token: '97Uy6f52V3wWEAyiGPib56b6mMfS7dcJAAAAAQoqJY8AAAGTQo3TRJgXPJRhmZ-F',
    token_type: 'bearer',
    refresh_token: 'SnoMWJdzi2arGm0Cqq6W6rdjmdvYYM2IAAAAAgoqJY8AAAGTQo3TQpgXPJRhmZ-F',
    expires_in: 21599,
    scope: 'account_email',
    refresh_token_expires_in: 5183999
  }
  • 이 응답이 나온다.
  • 해당 발급받은 토큰을 통해 이제 카카오 정보를 얻어와야 한다.

image

image

curl -v -G GET "https://kapi.kakao.com/v2/user/me" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

이 형식으로 카카오에 요청을 보내면 유저의 정보를 얻을 수 있다.

const userResponse = await axios.get('https://kapi.kakao.com/v2/user/me', {
      headers: {
        Authorization: `Bearer ${response.data.access_token}`,
      },
    });
{
  id: 3798636860,
  connected_at: '2024-11-19T03:30:15Z',
  kakao_account: {
    has_email: true,
    email_needs_agreement: false,
    is_email_valid: true,
    is_email_verified: true,
    email: '[email protected]'
  }
}
  • 해당 형식으로 OAuth 로그인을 통한 카카오 유저 로그인과 유저 정보를 얻어올 수 있다.

마무리

OAuth는 다양한 서비스 간 안전한 인증과 권한 부여를 가능하게 하며, 특히 사용자의 민감한 정보를 보호하는 데 중요한 역할을 합니다.

이번 글을 통해 OAuth의 기본적인 흐름을 이해하고, 실제 인증 과정에서 어떤 일이 일어나는지 감을 잡으셨기를 바랍니다. 이후에는 Refresh Token, 토큰 만료와 갱신 등 더 깊은 OAuth 개념을 공부해 보시면 더욱 좋을 것 같습니다. :)

MafiaCamp

📔소개
🎯프로젝트 규칙
💻프로젝트 기획
🍀기술 스택
📚그룹 회고
🌈개발 일지
🍀문제 해결 경험
🔧트러블 슈팅
Clone this wiki locally