LoginSignup
5

More than 5 years have passed since last update.

CompojureのCSRF対策トークンのanti-forgery-token作成

Last updated at Posted at 2015-05-18

前回Docker Composureを使って作成したCompojureプロジェクトに、curlからPOSTするとInvalid anti-forgery tokenが発生してしまいました。デフォルトでCSRF対策用トークンを追加しないとPOSTはできない仕様になっています。開発中はanti-forgeryを無効にした方が簡単です。いろいろ調べるとStackOverflowにあった方法でうまくいきました。

Invalid anti-forgery token

以下のようにPOST用ハンドラをhandler.cljに作成してcurlでPOSTするとInvalid anti-forgery tokenが発生します。

$ curl -X POST -d "name=masato" localhost:3000/greeting
<h1>Invalid anti-forgery token</h1>

StackOverflowでも議論されていました。以下のサイトを参考に作業していきます。

プロジェクト

前回と同じようにDocker Composeでプロジェクトを管理します。

$ cd ~/clojure_apps
$ tree .
.
├── Dockerfile
├── cookies
├── docker-compose.yml
└── m2

Dockerホストの作業ユーザーと同じuidのユーザーをDockerコンテナにも作成します。MavenのjarファイルはVOLUMEに指定してDockerホストにマップしてコンテナ間で共有します。

~/clojure_apps/Dockerfile
FROM clojure
MAINTAINER Masato Shimizu <ma6ato@gmail.com>

WORKDIR /usr/src/app

RUN apt-get update && apt-get install sudo net-tools && \
  rm -rf /var/lib/apt/lists/*

RUN adduser --disabled-password --gecos '' --uid 1000 docker && \
  adduser docker sudo && \
  echo 'docker ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
  mkdir /home/docker/.m2 && \
  chown -R docker:docker /usr/src/app /home/docker/.m2

VOLUME /home/docker/.m2
USER docker
RUN lein

ENTRYPOINT ["lein"]

ローカルにClojureのイメージをビルドします。

$ docker build -t clojure .

docker-compose.ymlのworking_dirディレクティブはCompojureプロジェクトを作成する前はコメントアウトしておきます。

~/clojure_apps/docker-compose.yml
lein: &defaults
  image: clojure
  volumes:
    - .:/usr/src/app
    - ./m2:/home/docker/.m2
#  working_dir: /usr/src/app/hello-world
  ports:
    - "3000:3000"
    - "3449:3449"
cljslein:
  <<: *defaults
  ports:
    - "9000:9000"

lein newでcompojureのプロジェクトを作成します。

$ cd ~/clojure_apps
$ docker-compose run --rm --service-ports lein new compojure hello-world

作成したCompojureプロジェクトをworking_dirディレクティブに指定します。

~/clojure_apps/docker-compose.yml
lein: &defaults
  image: clojure
  volumes:
    - .:/usr/src/app
    - ./m2:/home/docker/.m2
  working_dir: /usr/src/app/hello-world
  ports:
    - "3000:3000"
    - "3449:3449"
cljslein:
  <<: *defaults
  ports:
    - "9000:9000"

project.cljファイルにはJSON作成のcheshireを依存関係に追加します。

~/clojure_apps/hello-world/project.clj
(defproject hello-world "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [compojure "1.3.1"]
                 [ring/ring-defaults "0.1.2"]
                 [cheshire "5.4.0"]]
  :plugins [[lein-ring "0.8.13"]]
  :ring {:handler hello-world.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring-mock "0.1.5"]]}})

GET "/"ハンドラでCSRFトークンを出力するように変更します。トークンはRing-Anti-Forgery*anti-forgery-token*を評価します。動的なvarのスペシャル変数です。セッションに保存しているcookie-storeのキーはランダムで適当に作成しました。

またJSONでレスポンスを返すためにcheshireのgenerate-stringを使います。

~/clojure_apps/hello-world/src/hello_world/handler.clj
(ns hello-world.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [ring.middleware.session :refer [wrap-session]]
            [ring.middleware.anti-forgery :refer :all]
            [ring.middleware.session.cookie :refer (cookie-store)]
            [cheshire.core :refer :all]))

(defn greeting-handler [request]
  (let [name (get-in request [:params :name])]
    (str "Hi, " name)))

(defroutes app-routes
  (GET "/" [] (generate-string {:csrf-token
                                *anti-forgery-token*}))
  (POST "/greeting" [] greeting-handler)
  (route/not-found "Not Found"))

(def app
  (-> app-routes
   (wrap-defaults site-defaults)
   (wrap-session {:cookie-attrs {:max-age 3600}
                  :store (cookie-store {:key "ahY9poQuaghahc7I"})})))

Ringサーバーをheadlessで起動します。

$ cd ~/clojure_apps
$ docker-compose run --rm --service-ports lein ring server-headless
...
SelectChannelConnector@0.0.0.0:3000
Started server on port 3000

最初に用意したハンドラを実行してCSRFトークンを出力します。

$ curl -X GET --cookie-jar cookies "http://localhost:3000/"
{"csrf-token":"FxyHMRjb5I9wwxskEo2h2uXhtU/CNUo38xLDTa/2fJp7QhZ/Wo7hi4zRbey9yUZgRKfe3y1uS66K8+kA"}

JSONで取得したcsfr-tokenをHTTPヘッダのX-CSRF-Tokenに指定してcurlからPOSTすると成功します。

$ curl -X POST  \
  --cookie cookies  \
  -F "name=masato"  \
  -H "X-CSRF-Token: FxyHMRjb5I9wwxskEo2h2uXhtU/CNUo38xLDTa/2fJp7QhZ/Wo7hi4zRbey9yUZgRKfe3y1uS66K8+kA" \
  "http://localhost:3000/greeting"
Hi, masato

anti-forgeryを無効にする場合

CSRF対策を無効にする場合は、site-defaultsのanti-forgeryキーの値をfalseにして起動します。

~/clojure_apps/hello-world/src/hello_world/handler.clj
...
(def app (wrap-defaults app-routes (assoc-in site-defaults [:security :anti-forgery] false)))

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5