カテゴリー
【Rxjs基礎講座】rxjs#ajaxでCookie付きのログイン認証を行う
※ 当ページには【広告/PR】を含む場合があります。
2020/02/27
2022/10/05
Nodejs利用してWebスクレイピングをajaxに行おうとする場合には、
Promise
fromPromise
そこまでせずとも、rxjs単体でajaxな操作が使える
今回はこの
ajax
前準備(ビルド環境等)
以前の記事:
実際にお手元のPCで動かしてみたい方は、そちらの方からお試しください。
xmlhttprequestのインストール
クロスドメインやCookieを正しく操作する場合には、
$ yarn add xmlhttprequest
でインストールされます。
rxjs/ajaxオペレーターの使い方の基本
まずは本題に入る前に、ajaxメソッドの基本的な使い方をおさらいします。
以前の記事・
$ tree -I node_modules
.
├── dist
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── index.ts
├── package.json
├── tsconfig.json
└── yarn.lock
ここから、ソースコード用に
src
ajax.ts
$ mkdir src && touch src/ajax.ts
$ tree -I node_modules
.
├── dist
│ ├── index.d.ts
│ ├── index.js
│ └── index.js.map
├── index.ts
├── package.json
├── src
│ └── ajax.ts
├── tsconfig.json
└── yarn.lock
ソースコードファイルを追加したら、
tsconfig.json
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"declaration": true,
"sourceMap": true,
"outDir": "./dist",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true
},
"include": [
"./index.ts",
"./src/*" // 👈 Add it here
],
"compileOnSave": false
}
そして、
ajax.ts
import { ajax } from 'rxjs/ajax';
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
export const ajax$ = ajax({
createXHR: () => new XMLHttpRequest(),
url: 'https://www.google.com/',
method: 'GET',
responseType: 'text'
});
次にルートの
index.ts
import { Observable } from 'rxjs';
import { ajax$ } from './src/ajax';
ajax$.subscribe(
res => console.log(res),
err => console.log(err)
);
それでは、ビルドしてプログラムを走らせてみます。
$ tsc && babel-node dist/index.js
AjaxResponse {
originalEvent: undefined,
xhr: {
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4,
readyState: 4,
...中略
</body></html>\"
}
レスポンスは
AjaxResponse
ajaxメソッドの基本的な利用方法はこんな感じです。
rxjs/ajaxによるCookieによるログイン機能の実装
ここからは本題のajaxでCookie認証をどう突破させるのかを順を追ってやっていきます。
ログイン機能をもつウェブサイトへajaxでスクレイピングをするには、少し工夫が必要になります。
また、今回の方法を用いれば、JWTによるログイン認証を行う際にも同じように応用が効くものと思います。
テストサイト(の候補)
※2022-06-24時点での内容です。
以前であれば、いろんなテストに使える
Web Scraper Testing Ground
ということで簡単にCookieログイン試験を行えるよう、以下のデモサイトを利用させて頂くことで代替します。
ユーザー名(
demo-admin
demo9999
typescriptによるログインメソッドの実装
src
login.ts
$ touch src/login.ts
この
login.ts
import { Observable, map } from 'rxjs';
import { ajax } from 'rxjs/ajax';
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
export function login$(): Observable<any> {
const usr = 'admin';
const pwd = ''; // 👈 It's wrong on purpose...
// const pwd = '12345';
const playgroud = `http://testing-ground.scraping.pro/login?mode=login`;
const xhr = () => {
const _xhr = new XMLHttpRequest();
return _xhr;
};
const login$ = ajax({
createXHR: xhr,
url: playgroud,
crossDomain: true,
withCredentials: true,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
usr: `${usr}`,
pwd: `${pwd}`,
},
responseType: 'text',
timeout: 3000,
async: false // 👈 Important to recieve cookie!
}).pipe(
map(res => res.response)
);
return login$;
}
そして、
index.ts
import { Observable } from 'rxjs';
import { login$ } from './src/login';
login$().subscribe(
res => console.log(res),
err => console.log(err),
);
再度ビルドし、プログラムを走らせてみます。
$ tsc && babel-node dist/index.js
<!DOCTYPE html>
#......中略
<div id="case_login">
<h3 class='error'>ACCESS DENIED!</h3><a href='login'><< GO BACK</a></div>
<br/><br/><br/>
</div>
</body>
</html>
応答したhtmlの内容にもあるように、
ACCESS DENIED!
では、パスワードを再度正しいものに設定します。
上の
login.ts
// const pwd = ''; // 👈 It's wrong on purpose...
const pwd = '12345';
と、正しいパスワードに差し替えます。
プログラムを再度走らせると、
$ tsc && babel-node dist/index.js
<!DOCTYPE html>
#....中略
<div id="case_login">
<h3 class='success'>REDIRECTING...</h3><a href='login'><< GO BACK</a></div>
<br/><br/><br/>
</div>
</body>
</html>
今度は、
REDIRECTING...
ただし、ajaxはログイン成功後のリダイレクト先までは面倒を見てくれませんので、手動でリダイレクト先に遷移させる必要があります。
Cookieを受け取る
リダイレクトするためには、発行されたCookieを受けとる必要があります。
発行されたCookieを受け取るために、先ほどの
login.ts
//......中略
export function login$(): Observable<any> {
const usr = 'admin';
const pwd = '12345';
const playgroud = `http://testing-ground.scraping.pro/login?mode=login`;
const xhr = () => {
const _xhr = new XMLHttpRequest();
return _xhr;
};
const login$ = ajax({
createXHR: xhr,
url: playgroud,
crossDomain: true,
withCredentials: true,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
usr: `${usr}`,
pwd: `${pwd}`,
},
responseType: 'text',
timeout: 3000,
async: false // 👈 Important to recieve cookie!
}).pipe(
map(res => {
const cookie: any = res.xhr.getResponseHeader('Set-Cookie');
const token: string = cookie[0];
return token;
})
);
return login$;
}
Cookieはサーバー側から返ってきたレスポンスヘッダの中に付与されております。
ここでのポイントして、レスポンスインスタンスから
xhr.getResponseHeader('Set-Cookie')
この補正したソースコードでは、
login$
再び一旦プログラムを、走らせてこれを確認してみましょう。
$ tsc && babel-node dist/index.js
tdsess=TEST_DRIVE_SESSION
と言うことで、
tdsess=TEST_DRIVE_SESSION
サーバー-クライアント間の同期処理
Cookieを付けてAjaxにリダイレクトさせる前に、クッキーを受け取る際のサラッと重要な設定の解説をしておきます。
通常ブラウザは実に様々なHTTP処理をバックグラウンドで行っており、
今回利用させてもらっているテストサイトでは、上のソースコード内のように、ajaxメソッドの設定に
async:false
Cookie送信とログインを試す
さて、ようやく本題に戻ります。
ログインページから発行されたcookieを使って、リダイレクトページへ手動で遷移させてみましょう。
src
redirect.ts
$ touch src/redirect.ts
そして、以下の内容で編集します。
import { Observable, map } from 'rxjs';
import { ajax } from 'rxjs/ajax';
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
export function redirect$(_cookie: string): Observable<any> {
const playgroud = `http://testing-ground.scraping.pro/login?mode=welcome`;
const xhr = () => {
const _xhr = new XMLHttpRequest();
_xhr.setDisableHeaderCheck(true); // important for redirect to send cookie!
return _xhr;
};
const enter_page$ = ajax({
createXHR: xhr,
url: playgroud,
crossDomain: true,
withCredentials: true,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': _cookie
},
responseType: 'text',
timeout: 3000
}).pipe(
map(res => res.response)
);
return enter_page$;
}
次に、
index.ts
import { Observable } from 'rxjs';
import { concatMap } from 'rxjs/operators';
import { login$ } from './src/login';
import { redirect$ } from './src/redirect';
login$().pipe(
concatMap(cookie => redirect$(cookie))
).subscribe(
res => console.log(res),
err => console.log(err)
);
これは、
login$
concatMap
redirect$
concatMapに関しては、以前のブログで解説しましたので、ご興味がありましたら、
では、実行してみます。
$ tsc && babel-node dist/index.js
<!DOCTYPE html>
#.......中略
<div id="case_login">
<h3 class='success'>WELCOME :)</h3><a href='login'><< GO BACK</a></div>
<br/><br/><br/>
</div>
</body>
</html>
無事に
WELCOME :)
setDisableHeaderCheckメソッド
var forbiddenRequestHeaders = [
"accept-charset",
"accept-encoding",
"access-control-request-headers",
"access-control-request-method",
"connection",
"content-length",
"content-transfer-encoding",
"cookie",
"cookie2",
"date",
"expect",
"host",
"keep-alive",
"origin",
"referer",
"te",
"trailer",
"transfer-encoding",
"upgrade",
"via"
];
その中には
cookie
そこで救済措置として、
setDisableHeaderCheck
//...
const xhr = () => {
const _xhr = new XMLHttpRequest();
_xhr.setDisableHeaderCheck(true); // important for redirect to send cookie!
return _xhr;
};
//...
まとめ
今回は、rxjsのビルドイン
ajax
ajax
@angular/common/http
あとはrxjsにはcache操作のビルドイン機能があればより実用性のあるhttp処理が可能になります。
ですが、そこまでの複雑な処理を実現しようとおもうとpuppeteerのようなヘッドレスブラウザを操作するライブラリを用いたほうが良いと思います。
pupperteerを導入してみたときの記事は以下を参考ください。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー