Published on

포트원 다날 본인인증 api 연동하기

Authors

사용자 신원 확인 및 보안을 강화하기 위해, 다날의 본인인증을 포트원 API를 통해 구현할 수 있습니다.

이번 글에서는 Django에서 포트원v1 과 다날을 사용해 본인인증을 구현하는 방법을 간단하게 기록합니다.

1. View

로그인과 회원가입단에서 핸드폰만을 활용한 간단한 방식으로 구축했기 때문에,

아래와 같이 간단한 값으로 스키마를 설계합니다.

{
  "impUid": "string",
  "phone": "string"
}
class PortoneVerifierCreateView(CreateAPIView):
    """
    포트원 다날 본인인증
    ---
    """

    serializer_class = PortoneVerifierConfirmSerializer

비즈니스 로직으로 클라이언트의 IP 주소를 context에 추가하여, 해당 데이터를 나중에 본인인증 처리 시 사용할 수 있도록 처리 할 수 있습니다. 개인적으로.. 이 과정에서 보안 적인 요소를 적용하는 것이 좋다고 생각합니다.저는 해외 Ip 차단 로직을 추가했습니다. 검증과 함께 get_serializer_context 를 활용하는 것도 좋은 방법 인 것 같습니다.

2. Serializer 및 로직

PortoneVerifierConfirmSerializer는 본인인증에 필요한 정보를 받아들여, 포트원의 인증 정보를 확인하고 유저 데이터를 처리하는 과정입니다.

class PortoneVerifierConfirmSerializer(serializers.Serializer):

    imp_uid = serializers.CharField(write_only=True, label="포트원 서버인증요청 imp_uid")
    phone = serializers.CharField(write_only=True, label="폰 번호 01000000000")
    access_token = serializers.CharField(read_only=True)
    refresh_token = serializers.CharField(read_only=True)
    is_registered = serializers.BooleanField(read_only=True, label="가입완료 여부")

    @transaction.atomic
    def validate(self, attrs):
        request_user = self.context.get("request").user
        ip_address = self.context.get("ip_address")
        imp_uid = attrs["imp_uid"]
        phone = attrs["phone"]

        # portone
        certifications_info = get_certifications(imp_uid)

        ci = certifications_info.get("unique_key")
        di = certifications_info.get("unique_in_site")
        name = certifications_info.get("name")
        gender = certifications_info.get("gender")
        birthday = certifications_info.get("birthday")
        phone = certifications_info.get("phone", phone)
        kst = pytz.timezone(settings.TIME_ZONE)
        birth_date = datetime.strptime(birthday, "%Y-%m-%d")
        birth_date = kst.localize(birth_date)

        if ci and di:
            user_obj, _ = User.objects.get_or_create(ci=ci, di=di)
            self._validate_other_confirm(request_user, di)
            user_obj.name = name
            user_obj.gender = gender
            user_obj.birth_date = birth_date
            user_obj.phone = phone
            user_obj.save()
            refresh = RefreshToken.for_user(user_obj)
            attrs.update(
                {
                    "access_token": str(refresh.access_token),
                    "refresh_token": str(refresh),
                }
            )
            attrs["is_registered"] = user_obj.is_registered

        return attrs

validate 메서드에서 가장 중요한 부분은 포트원 인증 서버로부터 받은 imp_uid를 이용해 본인인증 정보를 조회하는 과정입니다.

인증 서버에서 받은 ci, di, name, gender, birthday 등의 정보를 바탕으로 새로운 유저를 생성하거나, 기존 유저 정보를 업데이트하게 됩니다.

JWT 토큰(access_tokenrefresh_token)을 생성하여 인증이 완료된 후 클라이언트에게 반환합니다.

2.1 portone 요청

portone(포트원) api token을 가져오는 부분은 생략합니다. (AWS의 secret manager를 이용했습니다.)

api token 을 이용해 access token 을 세팅하고, 인증된 자격으로 본인인증(confirm 과 유사) 확인 api 를 발송합니다.

imp_uid 는 클라이언트 사이드에서 생성되기 때문에, 동적으로 입력받은 값으로 처리합니다.


def get_access_token():
    url = "https://api.iamport.kr/users/getToken"
    portone_secrets = get_secret("portone")
    payload = {**portone_secrets}
    headers = {"Content-Type": "application/json"}
    response = requests.post(url, json=payload, headers=headers)
    response_json = response.json()
    if response.status_code == 200:
        return response_json.get("response").get("access_token")
    raise ValidationError(f"portone getToken api error : {response}")

def get_certifications(imp_uid: str):
    access_token = get_access_token()
    url = f"https://api.iamport.kr/certifications/{imp_uid}"
    headers = {"Authorization": access_token}
    response = requests.get(url, headers=headers)
    response_json = response.json()
    if response.status_code == 200:
        return response_json.get("response")
    raise ValidationError(f"portone certifications api error : {response}")

2.2 참고_검증 예시

이 메서드(_validate_other_confirm)는 로그인한 사용자가 인증된 사용자와 동일한지 확인하는 예시 입니다. 만약 다른 사용자의 본인인증 정보를 이용하려는 시도가 있을 경우, 이를 차단하고 에러를 반환합니다.

def _validate_other_confirm(self, request_user, confirmed_di: str):
    """
    로그인 된 유저의 di와 포트원 인증 후 반환된 di가 일치하는지 확인합니다.
    """
    if request_user and getattr(request_user, "is_authenticated"):
        if user_di := getattr(request_user, "di", None):
            if confirmed_di != user_di:
                raise serializers.ValidationError("타인의 본인인증 에러")

레퍼런스 링크

  • hongreat 블로그의 글을 봐주셔서 감사합니다!^^
  • 내용에 잘못된 부분이나 의문점이 있으시다면 댓글 부탁 & 환영 합니다~!
  • (하단의 버튼을 누르시면 댓글을 보거나 작성할 수 있습니다.)
Buy Me A Coffee