Microsoft MVP성태의 닷넷 이야기
웹: 24. 네이버는 어떻게 로그인 처리를 할까요? [링크 복사], [링크+제목 복사]
조회: 35522
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

네이버는 어떻게 로그인 처리를 할까요?

마침, 질문하신 분이 계셔서 한번 알아보기로 했습니다. ^^

naver 소켓 로그인
; https://www.sysnet.pe.kr/3/0/986

위의 글에 보면, Naver 로그인 시에 https로 정보가 날아가기 때문에 어떤 식의 HTTP 통신이 발생하는 지 알 수 없다는 것인데요. 하지만 실제로 살펴본 결과, 네이버는 부분적으로 HTTPS 통신과 GZIP 압축을 곁들이기 때문에 패킷을 가로채는 도구로는 알 수 없는 바이너리 데이터가 나타난 것이었습니다.

gzip 압축의 경우에는, IE 설정을 통해서 강제로 gzip 지원을 하지 않는다고 표시를 하면 이후의 통신에서 웹 서버 측에서도 gzip 압축을 사용하지 않기 때문에 패킷 모니터링 도구로 보는 것이 가능합니다. 이를 위해 아래와 같이 HTTP 1.1 통신 설정을 해제해 주면 됩니다.

naver_how_to_login_1.png

물론, 이렇게 했어도 HTTPS 통신의 경우에는 패킷 모니터링 도구로는 답이 없습니다. 하지만, Fiddler의 도움을 받으면 그런 경우에도 문제 없이 패킷 내용을 볼 수 있습니다.

참고로, 이 글에서는 기왕에 fiddler를 사용하는 것이니, HTTP 1.1 해제 옵션은 사용하지 않겠습니다.




자, 이제 네이버를 IE에서 방문한 다음 '로그인' 버튼을 누르는 순간부터 발생하는 HTTP 통신을 캡쳐해 보겠습니다.

naver_how_to_login_2.png

재미있군요. ^^ 곧바로 로그인 처리를 하는 웹 페이지로 폼 데이터를 전송하지 않고 static.nid.naver.com으로부터 keys_js.nhn 파일을 가져오고 있습니다. 내용을 한번 볼까요?

naver_how_to_login_3.png

보시는 것처럼, keys_js.nhn 요청에 대한 응답은 gzip으로 압축되어 있어서 기본적으로 볼 수 없는데요. Fiddler 도구에서는 이를 위해 "Response is encoded and may need to be decoded before inspection. Click here to transform." 영역을 사용자가 누르는 시점에 다음과 같이 정상적으로 압축 해제된 결과를 확인할 수 있도록 해주고 있습니다.

==== 요청 ====
GET http://static.nid.naver.com/enclogin/keys_js.nhn HTTP/1.1
Accept: application/javascript, */*;q=0.8
Referer: http://static.nid.naver.com/login.nhn?svc=me&url=http%3A%2F%2Fwww.naver.com
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: static.nid.naver.com
Connection: Keep-Alive
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0

==== 응답 ====
HTTP/1.1 200 OK
Server: nginx/0.8.54
Date: Mon, 15 Aug 2011 03:01:54 GMT
Content-Type: text/html
Connection: keep-alive
X-Powered-By: PHP/5.1.4
Expires: -1
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate
Content-Length: 1551

        sessionkey = 'gOS...[생략]...N0';
        keyname = '1...[생략]...42';
        evalue = '5DF...[생략]...8D0DC5413';
        nvalue = '010001';
    $('encnm').value = '1...[생략]...42';
    if (!sessionkey || !keyname || !evalue || !nvalue) {
    //if (true) {
        if ($('enctp').value == 3) {
                    showErrorDiv2(4);
        } else {
                    showErrorDiv(4);
        }
        $('frmNIDLogin').action="https://nid.naver.com/nidlogin.relogin";
    } else {
        if ($('enctp').value == 1) {
            var rsa = new RSAKey();
            rsa.setPublic(evalue, nvalue);
            $('encpw').value = rsa.encrypt(getLenChar(sessionkey) + sessionkey
                    + getLenChar($('uid').value) + $('uid').value
                    + getLenChar($('upw').value) + $('upw').value);
            $('upw').value = "";
            $('uid').value = "";
            $('frmNIDLogin').submit();
        } else if ($('enctp').value == 2) {
            keySetting('gO...[생략]...001');
            loginClick();
        } else if ($('enctp').value == 3) {
            document.CKKeyPro.E2EInitEx("naver", sessionkey, evalue,nvalue, $('id').value);
            $('encpw').value = document.CKKeyPro.GetEncData("","frmNIDLogin", "pw");
            $('id').value = "";
            $('pw').value = "";
            $('frmNIDLogin').submit();
        }
    }

보시는 것처럼, RSA 암호화에 필요한 값들을 네이버는 '로그인' 버튼이 눌리는 시점에 서버로부터 가져와서 처리를 하고 있습니다. 그리고 나서 enctp 값에 따라 if 문 처리를 하고 있는데, GET 요청에 보면 "nid_enctp=1" 값으로 미뤄볼 때 결국 다음과 같은 if 문이 실행되는 것을 짐작할 수 있습니다.

var rsa = new RSAKey();
rsa.setPublic(evalue, nvalue);
$('encpw').value = rsa.encrypt(getLenChar(sessionkey) + sessionkey
        + getLenChar($('uid').value) + $('uid').value
        + getLenChar($('upw').value) + $('upw').value);
$('upw').value = "";
$('uid').value = "";
$('frmNIDLogin').submit();

보시면, 원래의 HTTP Form 필드에서 upw/uid 값은 빈 문자열("")로 초기화 하고 모든 값을 encpw 필드에 rsa.encrypt로 암호화 해서 넣고 있습니다. 아하~~~ 매우 바람직한 방법으로 보입니다. 패킷을 가로채는 악의적인 사용자로 하여금 암호뿐만 아니라 아이디 조차도 알 수 없게 만들어 버린 것입니다.

이렇게 해서 암호화된 encpw 값이 전송되는 부분은 Fiddler의 "/nidlogin.login"에서 찾아볼 수 있습니다.

==== 요청 ====
POST https://nid.naver.com/nidlogin.login HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://static.nid.naver.com/login.nhn?svc=me&url=http%3A%2F%2Fwww.naver.com
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: nid.naver.com
Content-Length: 365
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0

enctp=1&encpw=...[생략]...&encnm=100005842&svctype=0&url=http%3A%2F%2Fwww.naver.com&enc_url=http%253A%252F%252Fwww.naver.com&postDataKey=&saveID=0&nvme=0&smart_level=1&id=&pw=

==== 응답 ====
HTTP/1.1 302 Found
Date: Mon, 15 Aug 2011 03:01:55 GMT
Server: Apache
Set-Cookie: nid_enctp=1; expires=Sun, 13-Nov-2011 03:01:55 GMT; path=/; domain=.nid.naver.com;
Set-Cookie: nid_inf=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_pwa=expired; expires=Wed, 02-Jun-1999 00:00:00 GMT; path=/; domain=.naver.com;
Set-Cookie: adsession=expired; expires=Wed, 02-Jun-1999 00:00:00 GMT; path=/; domain=.naver.com;
Set-Cookie: NID_DH_UI=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: NID_AUT=...[생략]...=; path=/; domain=.naver.com;
Set-Cookie: NID_MATCH_M=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_me=...[생략]...; expires=Sun, 11-May-2014 03:01:55 GMT; path=/; domain=.nid.naver.com;
Set-Cookie: NID_SES=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_sid=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.naver.com;
Location: http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]...
Content-Length: 27
Connection: close
Content-Type: text/html

<html><body></body></html>

보시는 것처럼, 폼 데이터를 POST 방식으로 "https://nid.naver.com/nidlogin.login" 페이지로 전송하고 있는데, 개인적으로 여기서 약간 이해가 안되는 부분이 있습니다. 어차피 HTTPS 통신을 사용할 거면 굳이 폼 데이터를 RSA 암호화 할 필요는 없었는데,,, 혹시 MITM(Man-in-the-middle Attack) 공격에 대한 대비책인지는 모르겠습니다.

어쨌든, 결과를 살펴보면 전형적인 쿠키 인증 절차로 보입니다. keys_js.nhn 자바 스크립트 파일에서 처리된 encpw 필드 값이 nidlogin.login으로 전달되고, 그 웹 페이지에서는 (아마도) RSA 개인키를 통해 encpw 필드 값을 복호화해서 사용자가 입력한 계정/암호 값을 기반으로 인증을 할 것입니다. 성공하면, 위의 응답에서 본 것처럼 인증과 관련된 각종 쿠키값을 보내줄 텐데 이후의 HTTP 통신은 이렇게 설정된 쿠키값들을 기반으로 이뤄질 것입니다.

나머지는 그다지 중요한 것 같진 않지만 마저 살펴보면, 위의 응답이 "HTTP/1.1 302 Found"이므로 "Location" 헤더로 설정된 "http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]..." 페이지로 웹 브라우저는 재방문을 해야 합니다.

==== 요청 ====
GET http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]... HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Connection: Keep-Alive
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Pragma: no-cache
Accept-Encoding: gzip, deflate
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0
Cache-Control: no-cache
Host: static.nid.naver.com

==== 응답 ====
HTTP/1.1 302
Server: nginx/0.8.54
Date: Mon, 15 Aug 2011 04:13:35 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.1.4
Location: http://mstatic.nid.blog.me/sso/cross-domain.nhn?domain_idx=0&sid=...[생략]...
Cache-Control: no-store, no-cache, must-revalidate

0

경로에 sso라는 이름이 포함된 것으로 보아, Single-sign-on을 위해 또 다른 웹 페이지를 방문하고 있는 것 같습니다. 위의 응답도 역시 302 redirection이므로 연쇄적으로 또 다른 SSO 경로를 방문하는데, 위의 결과 이외에도 네이버는 다음과 같이 2개의 웹 페이지를 더 방문합니다.

  • http://mstatic.nid.me2day.net/sso/cross-domain.nhn?domain_idx=1&sid=...[생략]...
  • http://static.nid.naver.com/login/sso/finalize.nhn?url=http%3A%2F%2Fwww.naver.com&sid=...[생략]...

정리해 보면, 네이버는 로그인 한번에 인증과 함께 4개의 SSO를 함께 처리하고 있습니다. 그리고 최종적으로 마지막의 finalize.nhn까지 와서야 비로소 www.naver.com으로 이동하도록 302 코드를 반환하는 것으로 끝이 납니다.




살펴보니, 제법 네이버의 로그인 체계가 마음에 듭니다. 혹시나, 자신의 웹 사이트에 로그인을 구현해야 하는 분들이 있다면 위의 과정을 모방해서 구현하는 것도 좋은 선택이 될 수 있겠습니다. ^^

이것으로, 대강의 분석이 끝났으니 다시 처음 질문했던 상황으로 돌아가 볼까요? 문제는 Socket 프로그래밍으로 이것을 어떻게 해야 하느냐 입니다. 음... 간단한 문제가 아니군요. ^^ 시간 관계상 제가 직접 구현할 수는 없지만 대략 다음과 같은 순으로 진행해야 합니다.

  1. 사용자로부터, 로그인 아이디/암호를 입력받는다.
  2. http://static.nid.naver.com/enclogin/keys_js.nhn에 요청해서 암호화 관련 키들을 담고 있는 js 파일을 받아서 파싱한 후 sessionkey, evalue, nvalue 값을 얻는다.
  3. 자바 스크립트로 되어 있는 RSA 코드를 C/C++로 마이그레이션하고, rsa.encrypt 메서드와 동일한 동작으로 암호화해서 encpw 값을 구함.
  4. ssl 소켓 통신을 이용하여 https://nid.naver.com/nidlogin.login으로 각종 폼 데이터를 전송하고 반환받은 쿠키값들을 보관

제 역할은 이것으로 끝난 것 같습니다. ^^ 혹시나 위의 구현을 하시는 분이 있다면 블로그를 통해 공개하시는 것도 좋을 것 같습니다. ^^

(참고로, 저더러 네이버 로그인 처리를 구현하라고 한다면? 차라리 웹 브라우저 컨트롤을 하나 내장해서 로그인 완료과정까지 마친 다음 최종 쿠키값만을 별도로 얻도록 구성할 것 같습니다. 만약, 제가 시간이 많은 상황에 놓여 있다면 위에서 설명한 데로 구현을 바꿀 것이고, 더욱 많은 상황에 놓인다면 ssl 프로토콜 문서에 따라 직접 구현할 것입니다. ^^)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]


donaricano-btn



[최초 등록일: ]
[최종 수정일: 5/11/2021

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 쓴 사람
 



2012-09-13 12시40분
[김영대] 정말 감사드립니다. 잘 배우겠습니다
[손님]
2012-09-18 01시11분
[백유미] 좋은 글 잘 읽었습니다.
[손님]

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12752정성태8/4/202143개발 환경 구성: 592. JetBrains의 IDE(예를 들어, PyCharm)에서 Visual Studio 키보드 매핑 적용
12751정성태8/4/202155개발 환경 구성: 591. Windows 10 WSL2 환경에서 docker-compose 빌드하는 방법
12750정성태8/3/202181디버깅 기술: 181. windbg - 콜 스택의 "Call Site" 오프셋 값이 가리키는 위치
12749정성태8/2/202172개발 환경 구성: 590. Visual Studio 2017부터 단위 테스트에 DataRow 특성 지원
12748정성태8/2/202131개발 환경 구성: 589. Azure Active Directory - tenant의 관리자(admin) 계정 로그인 방법
12747정성태8/1/202145오류 유형: 748. 오류 기록 - MICROSOFT GRAPH – HOW TO IMPLEMENT IAUTHENTICATIONPROVIDER파일 다운로드1
12746정성태7/31/202199개발 환경 구성: 588. 네트워크 장비 환경을 시뮬레이션하는 Packet Tracer 프로그램 소개
12745정성태7/31/202128개발 환경 구성: 587. Azure Active Directory - tenant의 관리자 계정 로그인 방법
12744정성태7/30/202133개발 환경 구성: 586. Azure Active Directory에 연결된 App 목록을 확인하는 방법?
12743정성태7/30/202153.NET Framework: 1083. Azure Active Directory - 외부 Token Cache 저장소를 사용하는 방법파일 다운로드1
12742정성태7/30/202137개발 환경 구성: 585. Azure AD 인증을 위한 사용자 인증 유형
12741정성태7/29/202186.NET Framework: 1082. Azure Active Directory - Microsoft Graph API 호출 방법파일 다운로드1
12740정성태7/29/202155오류 유형: 747. SharePoint - InvalidOperationException 0x80131509
12739정성태7/28/202164오류 유형: 746. Azure Active Directory - IDW10106: The 'ClientId' option must be provided.
12738정성태7/28/202166오류 유형: 745. Azure Active Directory - Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).
12737정성태7/28/202169오류 유형: 744. Azure Active Directory - The resource principal named api://...[client_id]... was not found in the tenant
12736정성태7/28/202150오류 유형: 743. Active Azure Directory에서 "API permissions"의 권한 설정이 "Not granted for ..."로 나오는 문제
12735정성태7/27/202186.NET Framework: 1081. C# - Azure AD 인증을 지원하는 데스크톱 애플리케이션 예제(Windows Forms)파일 다운로드1
12734정성태7/26/202199스크립트: 20. 특정 단어로 시작하거나/끝나는 문자열을 포함/제외하는 정규 표현식 - Look-around
12733정성태7/23/2021124.NET Framework: 1081. Self-Contained/SingleFile 유형의 .NET Core/5+ 실행 파일을 임베딩한다면?파일 다운로드2
12732정성태7/23/202165오류 유형: 742. SharePoint - The super user account utilized by the cache is not configured.
12731정성태7/23/202183개발 환경 구성: 584. Add Internal URLs 화면에서 "Save" 버튼이 비활성화 된 경우
12730정성태7/23/202198개발 환경 구성: 583. Visual Studio Code - Go 코드에서 입력을 받는 경우
12729정성태7/22/202196.NET Framework: 1080. xUnit 단위 테스트에 메서드/클래스 수준의 문맥 제공 - Fixture
12728정성태7/22/202179.NET Framework: 1079. MSTestv2 단위 테스트에 메서드/클래스/어셈블리 수준의 문맥 제공
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...