AngularJS の $http サービスのXSRF対策について調べた
AngularJS の $http サービスのXSRF対策について調べた
AngularJS の $http サービスにはXSRF(CSRF)の対策の機能が実装されています。
(AngularJSでは、cross-site request forgeries
の略を XSRF
と呼んでいる場面が多くみられるので、ここではXSRFで統一します。)
AngularJS側の動作
XHRが実行された際に $http
サービスは、Cookieに XSRF-TOKEN
があれば、 X-XSRF-TOKEN
という名前でHTTPヘッダーにセットします。※ この名前は変更することができます。
サーバ側の対応
送られてきたCookieに含まれる XSRF-TOKEN
の値と HTTPヘッダーのX-XSRF-TOKEN
の値が合っていることを確認します。
自分のドメイン上で実行されるJavaScript以外はCookieを読み込むことが出来ないため、 サーバーはXHRが自分のドメイン上で実行されて送られてきた事を保証することが出来ます。
実装して確認してみる
簡単な実装を行い動作を確認してみます。 テキストボックスに入力した値をXHRでサーバにリクエストし、Tokenの検証が成功したら入力した値を返し、テキストボックスの直下に値を表示させます。 構成としては、sinatraで画面(html+AngularJS)をレンダリングし、そのレンダリングした画面でXHRが実行するようにします。
XHRでメッセージをリクエストし、レスポンスで同じメッセージが返ってくれば成功です。
require 'sinatra' require 'sinatra/reloader' require 'sinatra/cookies' require 'json' require 'active_support' require 'active_support/core_ext' # 画面をレンダリングする get '/' do # Tokenを生成 token = SecureRandom.base64(32) response.set_cookie( 'XSRF-TOKEN', value: token, # jsからcookieを読み取る必要があるので httponly を off にする httponly: false, secure: false ) erb :index end # Tokenの検証に成功したら、入力した文字をそのまま返す post '/add_message' do if same_xsrf_token?(cookies['XSRF-TOKEN']) { message: request_params['message'] }.to_json else status 403 { error: 'CSRF検証に失敗しました' }.to_json end end private def request_params request_body = request.body.read return {} if request_body.blank? JSON.parse(request_body) end def same_xsrf_token?(token) return false if token.blank? http_headers = request.env.select { |k, v| k.start_with?('HTTP_') } token == http_headers['HTTP_X_XSRF_TOKEN'] end
フロント側
<!DOCTYPE html> <html ng-app='App'> <head> <meta charset="utf-8"> <title>angularjs xsrf</title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script> <script src="js/controller.js"></script> </head> <body ng-controller="AppController"> <input type="text" ng-model="message"> <input type="button" value="送信" ng-click="send_message()"> <h1>{{receive_message}}</h1> </body>
var app = angular.module('App', []); app.controller('AppController', ['$scope', '$http', function($scope, $http){ $scope.send_message = function(){ $http({ method: 'POST', url: 'add_message', data: { message: $scope.message } }) .success(function(data, status, headers, config){ $scope.receive_message = data.message; }) .error(function(data, status, headers, config){ $scope.receive_message = data.error; }); } }]);
動作させてみる
画面で確認
- 無事リクエストしたメッセージが取得できていることを確認しました。
- ちなみに検証に失敗するとこうなります。
リクエストの内容を確認する
POST /add_message HTTP/1.1 Host: localhost:4567 Connection: keep-alive Content-Length: 20 Accept: application/json, text/plain, */* Origin: http://localhost:4567 X-XSRF-TOKEN: VGqbpCECjRRXF6XJFwD4IJA0zxzxdobSr4mnMJieyvI= User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36 Content-Type: application/json;charset=UTF-8 Referer: http://localhost:4567/ Accept-Encoding: gzip, deflate, br Accept-Language: ja,en-US;q=0.9,en;q=0.8 Cookie: XSRF-TOKEN=VGqbpCECjRRXF6XJFwD4IJA0zxzxdobSr4mnMJieyvI=
Cookie XSRF-TOKEN
に保存されているのと同じ値が、 X-XSRF-TOKEN
が付与されていることを確認