JavaでCookie実装

Cookieとは

  • 主に、セッション管理、トラッキング目的で使用される。Webアプリにログインして、ブラウザを閉じて再度同じページにアクセスしてもログイン状態が保持されているのはこのため
  • Cookieはブラウザ (メモリ) に保存される。
  • Cookieの受け渡しは、Webサーバーのサーブレットの機能により行われる。
  • Cookieに はそれ自体に有効期限を設定できる。

ブラウザでのCookieの確認

Edgeの場合

DevTool>アプリケーションタブ>サイドバーの『Cookie』

もしくは

ブラウザの設定> Cookieとサイトのアクセス許可> すべてのCookieとサイトデータを表示する

ブラウザの設定から確認できるものは現在ブラウザに保持されているすべてのCookieで、DevToolで確認できるものは、現在のページのCookie。

ファーストパーティークッキーとサードパーティークッキー

DevTool に表示されるDomainカラムがアクセスしたURLと同一のものはファーストパーティークッキー、その他のドメインのものはサードパーティークッキーと呼称される。

アプリケーションによって、ファーストパーティークッキーしか許容しない設定はよくあるので、Domain、Pathの設定は特に重要な意味を持つ。

Cookieの受け渡しがうまくできない場合はまずこの2つの設定を見直すとよい。

Cookieとキャッシュの違い

Cookieはセッション管理、トラッキング目的で使用されるのに対し、キャッシュは、一度表示したページのコンテンツを記憶することでページングパフォーマンス に寄与するもの。

Cookieの実装

Cookieの設定・取得・送信

■Cookieの設定

画面処理中もしくは、APIが画面にレスポンスを返すタイミングで設定できる。Cookieはブラウザに設定される。

画面で設定する場合は、例えばAngularではngx-cookie-serviceというような、Cookieを扱うためのパッケージが用意されているのでそちらを利用する。

APIレスポンスにCookieの設定を乗せる場合は、HttpServletResponseにCookieを設定したうえでレスポンスを返却することで、ブラウザが勝手に認識しCookieを設定してくれる。

画面(javascript)でCookieを設定する方法

前述のパッケージをインポートすれば特に苦しむことなく実装できる。

package.json

"dependencies": {
    "ngx-cookie-service": "13.0.0"
        …

app.module.ts

@NgModule({
    providers: [
        CookieService
    ],
    …

compName.service.ts

export class Cws1Service {
    constructor(
    ) {}
    private cookieService: CookieService
    init() {
        this.cookieService.set('cookieName', cookieValue, { path: '/' });   //{}内はオプション
    }

APIがレスポンスを返すタイミングでCookieを設定する方法

デフォルトでDIされているHttpServletResponseを引数に呼び出してCookieを設定する。

Sample.java

public class Sample {
    // コンストラクタ
    public Sapmle(HttpServletResponse httpServletResponse) {
        this.httpServletResponse = httpServletResponse;
    }

    public void sample() {
        Cookie cookie = new Cookie("key", "value");
        this.httpServletResponse.addCookie(cookie);
    }
}

Cookieの取得

すでにブラウザに設定されているCookieは、画面からでもAPIからでも値を取得することができる。これがCookieを使用する大きな利点の1つ。

画面から取得する場合は、設定時と同様に、専用のパッケージのメソッドを使用する。

APIで取得する場合は、Cookieはリクエストに乗って送られてくる。取得する際は、HttpServletRequestから取得することができる。

画面(Javascript)で取得する方法

設定時同様パッケージのメソッドで取得できる。

compName.service.ts

export class Cws1Service {
    constructor(
    ) {}
    private cookieService: CookieService
    init() {
        const cookieValue = this.cookieService.get('cookieName');
    }

APIリクエスト送信タイミングで取得する方法

ブラウザに設定されているCookieは、画面からAPIへのリクエスト送信時、HttpServletRequestのパラメータとして送られてくるため、これを取得する。

Sample.java

public class Sample {
    // コンストラクタ
    public Sapmle(HttpServletRequest httpServletRequest) {
        this.httpServletRequest = httpServletRequest;
    }

    public void sample() {
        Cookie[] cookies = this.httpServletRequest.getCookies();  // cookieは配列として取得される
        for (Cookie cookie : cookies) {
            String cookieName = cookie.getName();
            if(String.equals(cookieName, "key")) {
                system.out.println(cookie.getValue);
            }
        }
    }
}

Cookieの送信

ブラウザに設定されているCookieは、特に何もしなくても、サーブレットのリクエストに乗って送信される。

画面でもAPIでも、送信については、意識する必要は無く、実装も不要。

誤解しないようにしたいのは、例えばブラウザに設定したCookieを、APIを介して別のクライアントのブラウザに送信するような事例の場合、何もしなくても別クライアントのブラウザに同じCookieが設定されるわけではないということ。

このような例では、ここまで話したような、ブラウザにCookieを設定する実装を、別ブラウザ側がレスポンスを受け取るタイミングにも実装されていなければ、そのような受け渡しは実現しない。

Appendix

CookieとSet-Cookieの違い

Cookieを設定する本質的な仕組みは、ヘッダーに”Cookie”、”Set-Cookie”を設定することである。

ここまでパッケージを使用してCookieの設定を行ってきたが、これらはこのヘッダーの設定を、パッケージを通して扱いやすくしたものに過ぎない。

Cookieはブラウザが保持しているCookieであり、Set-Cookieは、Cookieをブラウザに設定せよという指示となる。

つまり、Cookieを持つブラウザがAPIへリクエストを送信する際、ヘッダーには”Cookie”フィールドが存在する。

一方で、前述の、HttpServletResponseにCookieを設定する方法、APIから画面にレスポンスを返す際のレスポンスヘッダーには”Set-Cookie”フィールドが設定されていることがわかる。

  • Set-Cookie : サーバー→クライアントに送信するときに付与するフィールド。
  • Cookie : クライアント→サーバーに送信するときに付与するフィールド。

原始的に、これらのフィールドをヘッダーに設定することでも、Cookieの設定をすることができる。

httpServletResponse.addHeader("Set-Cookie", "cookieSampleName=cookieVal; Domain=xxx.com; Path=/; Secure; HttpOnly")

尚、同じkey名を持つCookie、Set-Cookieを複数設定した場合は後勝ちとなり、値は上書きされる。

Cookieに設定されている情報

Cookieは以下のように複数の情報がセットされる。

cookieSample=aiueo; Domain=prudential.com; Path=/; Secure; HttpOnly

このうち、 cookieSample=aiueoのみがCookieのオリジナルのKey Value のセットであり、その他のDomainやPathなどは、Cookieが元々オプションとして持っているキーである。

そのため、JavaでCookie オブジェクトを新規作成する際は、以下のように1つしか Key Valueの組み合わせを独自にセットすることはできず、その他のオプションは メソッドで付与する作りになっている。

Cookie cookie = new Cookie("cookieSample", "aiueo");
cookie.setDomain ("sample.com"));
cookie.setPath("/"));

Set-Cookieの引継ぎ

多くは無いが、例えば以下のような構成で通信するケースを考える。

画面⇔API⇔外部API

外部APIから画面にCookieを設定したい場合は、外部APIからのレスポンスヘッダーに”Set-Cookie”が設定されてくる。

しかし、APIでRestTemplateで通信していたりすると、ヘッダーが書き換えられて”Set-Cookie”が消滅し、画面に意図したCookieが設定されない場合がある。

このようなケースは、外部APIからのレスポンスヘッダーにある”Set-Cookie”を、明示的にAPIでも引き継ぐ処理を追加する必要がある。

HttpHeaders responseHeader = response.getHeaders(); // responseは外部APIからのレスポンス
responseHeader.forEach((key, values) -> {   // headerは配列なのでforeachで取得
    if (StringUtils.equals(key, HttpHeaders.SET_COOKIE)) {  // headerのフィールドkeyがSet-Cookieだった場合は…①
        for (String value : values) {
            httpServletResponse.addHeader(HttpHeaders.SET_COOKIE, value);   //改めてサーブレットレスポンスのヘッダーにSet-Cookieとして値を設定しなおす…②
        }
    }
});

②とき、httpServletResponse.addCookieとしたいが、①で渡されてくるのはSet-Cookieフィールドの、以下のようにkey、valueに分かれていない文字列のままとなっている。

cookieSample=aiueo; Domain=prudential.com; Path=/; Secure; HttpOnly

そのため、Cookieオブジェクトとして作り直そうとすると、この文字列を手加工しなくてはならないため、Set-Cookieの文字列として、そのままヘッダーに設定した方がシンプルになる。

インシデント

APIでCookieが取得できない

APIのHttpServletRequestにCookieが見つからないケースのチェックポイント。

画面側の処理を、ブラウザの開発者コンソールで確認する。

  • アプリケーションタブ:Cookieが設定されているかを確認する
  • ネットワークタブ:対象のリクエストのヘッダーのCookieフィールドを確認する

上記問題ない場合は、アプリケーションタブのCookieに設定されているDomain、Pathを、他のCookieと見比べてみる。

特に業務アプリでは、ファーストパーティークッキーしか許容しないものも多いため、ここに疑わしい設定がある場合は、修正を試みる。

コメント

タイトルとURLをコピーしました