Safariであらゆるページに自動ログインする方法
なにゆえに、金融機関のWebサイトは、あれほどまでに自動ログインを阻害する作りなのか?なにゆえに、シンプルなIDとパスワード方式ではダメなのか?中でも、ゆうちょダイレクトは難攻不落である。
- お客さま番号の3分割入力。
- ログインパスワードの別ページ入力。
- 初回のアクセスについては、2段階の合い言葉の要求。
なにゆえに、そこまで面倒な手続きを要求するのか?その手続きによって、果たして、どれほどセキュリティが向上しているのか?*1IDを3分割しようが、パスワードを別のページで入力しようが、根本的には二つの値の一致を確認する方式に変わりはなく、ただ、ただ、毎回の面倒な手続きが負担になっている。いまどき自動ログインができないなんて、不便過ぎる...。しかも、お客さま番号は13桁もある。覚えられん。その不便さに苛立って、IDを付箋に書き込んでディスプレイの枠に貼り付けてしまい、逆にセキュリティが低下する状況になっているのではないか?
この不便さをどうにか改善するツールはどこかにないだろうか。あの1Passwordでさえも、Safari5以降ではゆうちょダイレクトの自動ログインには対応できていないらしい。以前作ったAutomatorとGUIスクリプティングを利用した自動ログインワークフローも、いつの頃からか入力値が設定とは違う値で入力されるようになってしまい、使い物にならない状態。真剣に、オレオレ自動ログインスクリプトを作ってみることにした。
方針
- 最初に考えたのは、クリップボードにID・パスワードを設定して、目指すテキストフィールドにペーストする方法。
- しかし、クリップボードにパスワードも書き込むことになり、セキュリティ上あまりよろしくない気もする。
- クリップボード履歴アプリが、常時クリップボードの変化を監視して、パスワードを設定しようものならそれが履歴に相当な期間残ってしまうのだ...。
- やはり、ブラウザの中のことはブラウザ用のスクリプト言語であるJavaScriptに任せた方が良さそう。
- 但し、ブックマークレットにしてしまうと、ブックマークの中にパスワードが含まれることになり、やはりセキュリティ上よろしくない気がする。
- そこで、AppleScriptからdo JavaScriptを利用して実行し、セキュリティはAppleScript側で確保することにするつもり。
要素の詳細を表示する方法
- 最近のSafariには、表示中のWebページの一部をピンポイントで指定して、そのソースを素早く調べる方法が用意されている。
- その方法は、右クリック(あるいは二本指クリック、あるいはcontrol-クリック)して、コンテクストメニューの要素の詳細を表示するだけ。
- 構造化されたリスト表示で、対象となるHTMLソース部分が選択された状態になる。
- 例えば、ゆうちょダイレクトにログイン時の最初の入力フィールドのソースは以下。
<input type="text" name="okyakusamaBangou1" maxlength="4" size="7" tabindex="1" value="" onkeyup="JavaScript:FocusMove(); return false" onfocus="JavaScript:FocusChangeOnMouse(0); return false" class="imedisabled">
要素の詳細を表示する機能は非常に便利なので、有効にしておくと幸せになる。
- Safari >> 環境設定... >> 詳細 >> 「メニューバーに"開発"メニューを表示」をチェック入り で有効になる。
JavaScriptで操作してみる
- 要素の詳細が分かると、俄然、JavaScriptで操作しやすい状況になる。
- 入力フォームにはname属性、あるいはid属性が設定されているはずなので、その属性を利用して操作対象を指定するのだ。
tell application "Safari"
activate
--ゆうちょダイレクトを開く
delay 1
make new document with properties {URL:"https://direct.jp-bank.japanpost.jp/direct_login.html?link_id=ycDctLgn"} --お客さま番号入力
delay 3
do JavaScript "document.getElementsByName('okyakusamaBangou1')[0].value = 1234" in document 1
do JavaScript "document.getElementsByName('okyakusamaBangou2')[0].value = 5678" in document 1
do JavaScript "document.getElementsByName('okyakusamaBangou3')[0].value = 12345" in document 1
do JavaScript "document.getElementsByName('U010103')[0].click();" in document 1
--ログインパスワード入力
delay 3
do JavaScript "document.getElementsByName('loginPassword')[0].value = PASSWORD" in document 1
do JavaScript "document.getElementsByName('U010302')[0].click()" in document 1
end tell
AppleScriptを一般化する
- 上記コードは、コードの中に「お客さま番号」「ログインパスーワード」が埋め込まれていて、セキュリティ上問題がある。
- また、Rubyで学んだ、美しいコードはたった数行のメソッドの組み合わせとなる、という原則にも明らかに違反する。
- 「お客さま番号」「ログインパスワード」はプロパティとして実行時に保存し、機能別にハンドラを細分化してみた。
- プロパティはスクリプトファイルに保存されるが、その内容は容易には確認できないと思う。
- 自分が見た限りは、意味不明な内容だった。
- 但し、達人が見れば分かってしまうかも...。
(* ゆうちょダイレクト *)
property customer_number : ""
property login_password : ""
property login_url : "https://direct.jp-bank.japanpost.jp/direct_login.html?link_id=ycDctLgn"
if customer_number = "" then my save_customer_number() if login_password = "" then my save_login_password()
my yucho_direct_login_page() my customer_number_page() my login_password_page()
on save_customer_number() activate
display dialog "お客さま番号を入力してください。" default answer ""
set customer_number to result's text returned
end save_customer_number
on save_login_password() activate
display dialog "ログインパスワードを入力してください。" default answer "" with hidden answer
set login_password to result's text returned
end save_login_password
--ログインページを開く
on yucho_direct_login_page() tell application "Safari"
activate
delay 1
make new document with properties {URL:login_url} end tell
end yucho_direct_login_page
--ログイン(お客さま番号入力)
on customer_number_page() tell application "Safari"
activate
delay 3
do JavaScript "document.getElementsByName('okyakusamaBangou1')[0].value = " & customer_number's items 1 thru 4 in document 1
do JavaScript "document.getElementsByName('okyakusamaBangou2')[0].value = " & customer_number's items 5 thru 8 in document 1
do JavaScript "document.getElementsByName('okyakusamaBangou3')[0].value = " & customer_number's items 9 thru 13 in document 1
do JavaScript "document.getElementsByName('U010103')[0].click();" in document 1
end tell
end customer_number_page
--ログイン(ログインパスワード入力)
on login_password_page() tell application "Safari"
activate
delay 3
do JavaScript "document.getElementsByName('loginPassword')[0].value = " & quoted form of login_password in document 1
do JavaScript "document.getElementsByName('U010302')[0].click()" in document 1
end tell
end login_password_page
重複を排除する
- さらに、Railsで学んだ徹底したDRY(重複を排除する)の原則にしたがって、修正していく。
- do javascript以下の長ったらしい呪文は、set_name()、click_name()というハンドラにして、シンプルに実行できるようにしてみた。
- また、AppleScriptの実行時に管理者権限を求めて、セキュリティの向上を計った。
(* ゆうちょダイレクト *)
property customer_number : ""
property login_password : ""
property login_url : "https://direct.jp-bank.japanpost.jp/direct_login.html?link_id=ycDctLgn"
my init() my yucho_direct_login_page() my customer_number_page() my login_password_page()
--ログインページを開く
on yucho_direct_login_page() tell application "Safari"
activate
delay 1
make new document with properties {URL:login_url} end tell
end yucho_direct_login_page
--ログイン(お客さま番号入力)
on customer_number_page() delay 3
my set_name("okyakusamaBangou1", customer_number's items 1 thru 4 as text) my set_name("okyakusamaBangou2", customer_number's items 5 thru 8 as text) my set_name("okyakusamaBangou3", customer_number's items 9 thru 13 as text) my click_name("U010103") end customer_number_page
--ログイン(ログインパスワード入力)
on login_password_page() delay 3
my set_name("loginPassword", login_password) my click_name("U010302") end login_password_page
on init() do shell script "" with administrator privileges
if customer_number = "" then my save_customer_number() if login_password = "" then my save_login_password() end init
on save_customer_number() activate
display dialog "お客さま番号を入力してください。" default answer ""
set customer_number to result's text returned
end save_customer_number
on save_login_password() activate
display dialog "ログインパスワードを入力してください。" default answer "" with hidden answer
set login_password to result's text returned
end save_login_password
on set_name(k, v) tell application "Safari"
do JavaScript "document.getElementsByName(" & quoted form of k & ")[0].value = " & quoted form of v in document 1
end tell
end set_name
on click_name(k) tell application "Safari"
do JavaScript "document.getElementsByName(" & quoted form of k & ")[0].click()" in document 1
end tell
end click_name
自動ブラウズ機能も追加
- さらに、ログイン後、最初にやることはたいてい決まっている。自分の場合は、入出金明細の確認である。自動で最新の入出金明細を表示するようにしてみた。
- また、管理者認証をキャンセル時に「お客さま番号」「ログインパスワード」を再設定する機能も追加した。
- ページごとにハンドラに分割するやり方は煩わしいので、やめた。
- その他のサイトにも活用しやすいように、一般化をさらに進める。
(* ゆうちょダイレクト *)
property user_ID : ""
property login_password : ""
property login_url : "https://direct.jp-bank.japanpost.jp/direct_login.html?link_id=ycDctLgn"
property word_dict : {user_ID:"お客さま番号", login_password:"ログインパスワード"}
my init() my open_login_url()
delay 3 --ログイン(お客さま番号入力)
my set_name("okyakusamaBangou1", user_ID's items 1 thru 4 as text) my set_name("okyakusamaBangou2", user_ID's items 5 thru 8 as text) my set_name("okyakusamaBangou3", user_ID's items 9 thru 13 as text) my click_name("U010103")
delay 3 --ログイン(ログインパスワード入力)
my set_name("loginPassword", login_password) my click_name("U010302")
delay 3 --メインメニュー
my click_name("U030110") --入出金明細照会ボタンを押す
delay 3 --入出金明細
my click_id("nb") --ラジオボタンの通帳未記入分を選択する
my click_name("U070102")
delay 3 --入出金明細(通帳未記入分)
my scroll_bottom() --ページ末尾へスクロール
--初期化(ログイン情報を保存する)
on init() try
do shell script "" with administrator privileges
if user_ID = "" then my save_user_ID() if login_password = "" then my save_login_password() on error --管理者認証をキャンセルした時に「お客さま番号」「ログインパスワード」を再設定する
my save_user_ID() my save_login_password() store script me in file (path to me) replacing yes
error number -128 --強制終了する
end try
end init
--user_IDを保存しておく
on save_user_ID() activate
display dialog word_dict's user_ID & "を入力してください。" default answer user_ID
set user_ID to result's text returned
end save_user_ID
--login_passwordを保存しておく
on save_login_password() activate
display dialog word_dict's login_password & "を入力してください。" default answer "" with hidden answer
set login_password to result's text returned
end save_login_password
--ログインページを開く
on open_login_url() tell application "Safari"
activate
delay 1
make new document with properties {URL:login_url} end tell
end open_login_url
--name属性kの値にvを設定する
on set_name(k, v) tell application "Safari"
do JavaScript "document.getElementsByName(" & quoted form of k & ")[0].value = " & quoted form of v in document 1
end tell
end set_name
--name属性kをクリックする
on click_name(k) tell application "Safari"
do JavaScript "document.getElementsByName(" & quoted form of k & ")[0].click()" in document 1
end tell
end click_name
--id属性kをクリックする
on click_id(k) tell application "Safari"
do JavaScript "document.getElementById(" & quoted form of k & ").click()" in document 1
end tell
end click_id
--ページ末尾までスクロールする
on scroll_bottom() tell application "Safari"
do JavaScript "window.scrollBy(0,document.height)" in document 1
end tell
end scroll_bottom
その他のサイト対応
- 上記のスクリプトは、プロパティとそれに続く上部のコードを変更するだけで、様々なサイトに対応できる。
- on init() 以下のコードの変更は一切不要である。
例1:三井住友銀行 one'sダイレクト
(* one'sダイレクト *)
property user_ID : ""
property login_password : ""
property login_url : "https://direct.smbc.co.jp/aib/aibgsjsw5001.jsp"
property word_dict : {user_ID:"契約者番号", login_password:"第一暗証"}
my init() my open_login_url()
delay 3 --ログインページ
my set_name("USRID1", user_ID's items 1 thru 5 as text) my set_name("USRID2", user_ID's items 6 thru 10 as text) my set_name("PASSWORD", login_password) my click_name("bLogon.y") --ログインボタン
delay 5 --重要なお知らせ
my click_name("imgNext.y") --次へボタン
--on init() 以下変更なし
例2:新生パワーダイレクト
(* 新生パワーダイレクト *)
property user_ID : ""
property login_password : ""
property login_url : "http://redesign.shinseibank.com/index_jpn.htm"
property word_dict : {user_ID:"3桁の店番号と7桁の口座番号", login_password:"4桁の暗証番号とパワーダイレクトパスワード"}
my init() my open_login_url()
delay 3 --ログインページ
my set_name("fldUserID", user_ID) --[1]3桁の店番号と 7桁の口座番号
my set_name("fldUserNumId", login_password's items 1 thru 4 as text) --[3]4桁の暗証番号
my set_name("fldUserPass", login_password's items 5 thru -1 as text) --[4]パワーダイレクトパスワード
my click_name("Login") --ログインボタン
--on init() 以下変更なし
所感
*1:初回アクセスの合い言葉の要求は、認めよう。初回だけなので我慢する。