SRT 개발이 완료됐으니, KTX 예매 기능을 구현하자.
SRT 로그인 기능 구현했던 글을 참조하여 빠르게 개발해보자.
Session 받기
KTX 세션은 https://www.letskorail.com/ 에서 받는다. - callback 함수들은 ui와 연동하면서 필요하므로, 일단 주석처리하자.
class KTX:
def __init__(self): #, error_callback, try_callback):
# self.error_callback = error_callback
# self.try_callback = try_callback
self.session = requests.Session()
self.session.get("https://www.letskorail.com/")
로그인하기
KTX 로그인 페이지로 이동해서 로그인을 해보자.
아이디랑 패스워드가 안보이고, encUserId와 encUserPwd가 보인다...
RSA 알고리즘으로 id/pw를 암호화를 한 번 하고 보내는 건데, 가능하면 암호화를 피하고 싶다...암호화가 들어가는 순간 좀 복잡해진다.
encUserId 위에 보면 UserId, UserPwd가 보이는데 암호화되지 않은 값을 전달해도 로그인이 될 수 있으니...희망을 갖고 암호화를 하는 코드를 좀 살펴보자.
로그인 폼이 제출되는 submit target을 찾아보면 Login() javascript 함수로 연결된다.
이 함수에서 암호화 부분을 찾아보자.
// 이중보안 2015.05.07 ljy
document.form2.encUserId.value = '';
document.form2.encUserPwd.value = '';
try {
var rsa = new RSAKey();
rsa.setPublic('8ac8f33129dceca9449e8ca2d6d8a1888e7c62bd95f234415a07f8c89b66fbe300a2d4314123cf049b797e9a2521886c00f618c97b0d4643ee180eca92c173ccd3a920bc5f45b6da83ef2fa5a8b38066a10d94486dfe24cf76f507c6d4ecbff5d7dc66a97378d497db31e1fe062a978e3f929d0b3356d4a8ed9947d0827e0315', '10001');
// 사용자ID와 비밀번호를 RSA로 암호화한다.
var e_txtMemberNo = rsa.encrypt(txtMemberNo);
document.form2.encUserId.value = e_txtMemberNo;
if (($('.keySec').is(":checked"))) {
document.form2.useKeySecFlg.value = 'Y';
var encData = nshc.encrypted();
encData = encodeURIComponent(encData);
document.form2.encUserPwd.value = encData;
} else {
document.form2.useKeySec.value = 'N';
var e_txtPwd = rsa.encrypt(txtPwd);
document.form2.encUserPwd.value = e_txtPwd;
}
} catch(err) {
//alert(err);
document.form2.UserId.value = txtMemberNo;
document.form2.UserPwd.value = txtPwd;
}
2중 보안을 이유로 RSA 암호화를 하고 있고...
try 안에서 에러가 발생하면 catch에서 암호화 되지 않은 계정을 보내고 있는 것을 볼 수 있다..!
개발자 도구에서 항상 암호화를 안하도록 javascript 코드를 바꾸고 테스트해보니 암호화를 안해도 로그인이 잘된다~~(싱글벙글)
로그인을 위해 요청하는 Form data를 정리하면 다음과 같다.
- URL : https://www.letskorail.com/korail/com/loginAction.do
- Method : POST
- FormData
- selInputFlg : 로그인 타입(2 - 멤버십 번호, 5 - 이메일, 4 - 휴대폰)
- radIngrDvCd : 고정 - "2"
- hidMemberFlg : 고정 - "1"
- txtDv : 비밀번호 길이 관련 변수 (1 - 4자리 비밀번호, 2 - 비밀번호가 8자리 이상, 나머지 경우는 불가능)
- UserId : 비밀번호
- UserPwd : 아이디
- acsURI : 고정 - "https://www.letskorail.com:443/ebizsso/sso/acs"
- providerName : 고정 - "Ebiz Sso"
- forwardingURI : 고정 - "/ebizsso/sso/sp/service_proc.jsp"
- RelayState : 고정 - "/ebizsso/sso/sp/service_front.jsp"
- IPType : 고정 - "Ebiz Sso Identity Provider"
- 나머지 변수들 : 빈 값
KTX는 SRT에 비해 분석이 좀 까다로웠는데, 로그인 페이지의 Login() 함수를 참고하여 분석했다.
동일한 body data를 만들고 post 요청을 보내도 정상 처리가 안되는데, 이건 Request Header 값을 안넣어줘서 그렇다.
SRT는 이 작업을 안해줘도 문제가 없었는데, 코레일에서는 헤더값을 보고 정상적인 브라우저 요청인지를 검토하는 것 같다.
앞으로 다른 요청을 보낼 때도 헤더가 필요할 것 같으니, 아예 헤더용 함수를 만들자.
def get_req_headers(self):
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Length': '460',
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': str(self.session.cookies),
'Host': 'www.letskorail.com',
'Origin': 'https://www.letskorail.com',
'Pragma': 'no-cache',
'Referer': 'https://www.letskorail.com/korail/com/login.do',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
}
return headers
이 헤더를 포함한 POST 요청을 보내면 아래와 같은 응답이 돌아온다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ko" xml:lang="ko">
<head>
<script type="text/javascript" src="/js/jquery.ui/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="/com/selectKorailCoMessage.do"></script>
<script language="JavaScript">
window.onload = function(){
localStorage.removeItem("pc25100En");
$.ajax({
url : "/korail/com/mypage/preset/preset_list_json.do;jsessionid=mVZalxGB4P908cKJwTrRdaN8Kp1rfPrRxZzeNUgkcVoUzTRunJmD7FZl1lIya5Ng.kr014_servlet_engine4",
type : "POST",
data : {
hidSetDv : '02',
hidRegSqno : '0'
},
success: function(data) {
var obj = JSON.parse(data);
if (obj.strResult == "SUCC")
{
localStorage.setItem("pc25100En", data);
}
onSubmit();
},
error:function(jqXHR, textStatus, error) {
onSubmit();
}
});
};
function onSubmit() {
document.form1.action = pub3+"/korail/com/loginProc.do";
document.form1.submit();
}
</script>
</head>
<body>
<form name="form1" method="post">
<input type="hidden" name="ret_url" value="/"/>
<input type="hidden" name="strWebPwdCphdAt" value="Y"/>
</form>
</body>
<html>
ajax 안에 있는 URL로 POST 요청을 보내면 로그인이 완료되는 것처럼 보인다.
ajax로 메세지를 보내면 아래와 같은 응답을 받을 수 있다.
{
"strResult" : "SUCC",
"pc25100En" : {
"en_len" : 0,
"h_result" : null,
"h_msg_txt" : "정상적으로 처리되었습니다.",
"entity" : [ ],
"h_msg_cd" : "IRS100002",
"h_grd_cnt" : "0000"
}
}
*** 참고로, 테스트해본 결과 ajax 안의 request는 로그인 정보가 틀려도 SUCC라고 뜬다 ㅋㅋ
이렇게 완성된 login 메서드는 아래와 같다..!
def login(self, login_type='', login_id='', login_pwd=''):
login_url = "https://www.letskorail.com/korail/com/loginAction.do"
# 아이디 검증
if login_type == '2': # 회원번호 로그인
if len(login_id) != 10:
self.error_callback('KTX 로그인 실패', f"회원번호는 10자리 숫자입니다")
return False
elif login_type == '4': # 휴대전화번호 로그인
login_id = login_id.replace('-', '')
if len(login_id) == 10:
login_id = login_id[0:3] + '-' + login_id[3:6] + '-' + login_id[6:]
elif len(login_id) == 11:
login_id = login_id[0:3] + '-' + login_id[3:7] + '-' + login_id[7:]
else:
self.error_callback('KTX 로그인 실패', f"휴대전화번호 입력 오류")
return False
elif login_type == '5': # 이메일 로그인
if '@' not in login_id:
self.error_callback('KTX 로그인 실패', f"이메일 입력 오류")
return False
else:
self.error_callback('KTX 로그인 실패', f"로그인 타입 코드 오류 - {login_type}")
return False
# 비밀번호 길이 검증
if len(login_pwd) == 4:
txtDv = "1"
elif len(login_pwd) >= 4:
txtDv = "2"
else:
self.error_callback('KTX 로그인 실패', f"비밀번호는 4자리 또는 8자리 이상 필수 [비밀번호 길이 : {len(login_pwd)}]")
return False
body = {
"txtBookCnt": "",
"txtIvntDt": "",
"txtTotCnt": "",
"selValues": "",
"selInputFlg": login_type,
"radIngrDvCd": "2",
"ret_url": "",
"hidMemberFlg": "1",
"txtHaeRang": "",
"hidEmailAdr": "",
"txtDv": txtDv,
"useKeySec": "",
"UserId": login_id,
"UserPwd": login_pwd,
"encUserId": "",
"encUserPwd": "",
"keyname": "",
"useKeySecFlg": "",
"acsURI": "https://www.letskorail.com:443/ebizsso/sso/acs",
"providerName": "Ebiz Sso",
"forwardingURI": "/ebizsso/sso/sp/service_proc.jsp",
"RelayState": "/ebizsso/sso/sp/service_front.jsp",
"IPType": "Ebiz Sso Identity Provider"
}
try:
res = self.session.post(login_url, data=body, headers=self.get_req_headers())
except Exception as e:
self.error_callback('KTX 로그인 요청 실패', f"HTTP 로그인 요청에 실패했습니다 - \n{e}")
return False
if 'preset_list_json.do' not in res.text:
self.error_callback('KTX 로그인 요청 실패', f"아이디 혹은 패스워드가 틀렸습니다")
return False
body = {
'hidSetDv': '02',
'hidRegSqno': '0'
}
try:
res = self.session.post(
f"https://www.letskorail.com/korail/com/mypage/preset/preset_list_json.do;jsessionid={self.session.cookies.get('JSESSIONID')}",
data=body, headers=self.get_req_headers())
except Exception as e:
self.error_callback('KTX 로그인 요청 실패', f"HTTP 로그인 확인 요청에 실패했습니다 - \n{e}")
return False
if 'SUCC' not in res.text:
self.error_callback('KTX 로그인 요청 실패', f"로그인 결과 확인에 실패했습니다")
return False
return self.is_logged_in()
로그인 여부 확인은 SRT와 비슷하게 만들면 된다.
def is_logged_in(self):
try:
res = self.session.get(f"https://www.letskorail.com/index.jsp")
except Exception as e:
self.error_callback('KTX 로그인 여부 확인 실패', f"HTTP 요청에 실패했습니다 - \n{e}")
return False
try:
if '로그아웃' in res.text:
return True
except Exception as e:
self.error_callback('KTX 로그인 여부 확인 실패', f"로그인 여부 확인에 실패했습니다 - \n{e}")
return False
풀 코드는 아래 깃허브를 참고하세용
https://www.github.com/dhgwag/train_reservation
GitHub - dhgwag/train_reservation
Contribute to dhgwag/train_reservation development by creating an account on GitHub.
github.com
'프로젝트 > SRT&KTX 매진표 예매' 카테고리의 다른 글
SRT&KTX 기차표 매크로 예매 - (10) KTX 승차권 조회 (0) | 2024.01.15 |
---|---|
SRT&KTX 기차표 매크로 예매 - (9) KTX 역 리스트 조회 (0) | 2024.01.15 |
SRT&KTX 기차표 매크로 예매 - (7) 실행파일 만들기 (PyInstaller) (0) | 2024.01.12 |
SRT&KTX 기차표 매크로 예매 - (6) 아이디/비밀번호 저장하기 (0) | 2024.01.12 |
SRT&KTX 기차표 매크로 예매 - (5) UI 만들기 (PyQt5) (0) | 2024.01.12 |