Spring BootでJavaScript/CSSライブラリにフィンガープリントをつける (2)

2016/03/23
2021/02/25

Spring Bootで静的コンテンツにフィンガープリントをつける,
Spring BootでJavaScript/CSSライブラリにフィンガープリントをつけるの続き。

前回の内容だと、外部ライブラリに対してGitコミットハッシュを固定のバージョンとして付与できるが、これだと例えば/lib/というリソースに対して/バージョン/lib/というパスが生成される。

この場合、/lib/**は認証対象外、というようなSpring Securityの設定をしているケースではバージョン付与後のパスが認証対象となってしまい、ログイン画面でリソースが読み込めない状態になってしまう。
このエントリでは、これに対処する方法について説明する。

分析

このパターンに対処するには、前回のエントリで使った addFixedVersionStrategy() の実装を追っていくと答えが見えてくる。

addFixedVersionStrategy()の内部ではaddVersionStrategy()を呼び出しており、そこではFixedVersionStrategyPrefixVersionPathStrategyというクラスが使われている。
これらを置き換えるような独自のストラテジを設定すればよさそう。

FixedVersionStrategyは、バージョンの識別に、コンストラクタで与えられたバージョンを固定値として使う、というストラテジ。
さらに、具体的なパスの中にバージョンを埋め込んだり抽出したりするロジックはPrefixVersionPathStrategyに委ねられている。

PrefixVersionPathStrategyは、与えられたバージョンをprefixとしてパスの先頭につけるというストラテジ。

実装

まずFixedVersionStrategyの代替として
PrefixAndFixedVersionStrategyというものを作る。

これは、FixedVersionStrategyがパラメータとしてバージョンだけを受け取っていたのに対して、先頭につける文字列も与えられるようにするもの。

こんな感じ。

public class PrefixAndFixedVersionStrategy extends AbstractVersionStrategy {
    private final String version;

    public PrefixAndFixedVersionStrategy(String prefix, String version) {
        super(new MiddleVersionPathStrategy(prefix, version));
        this.version = version;
    }

    @Override
    public String getResourceVersion(Resource resource) {
        return this.version;
    }
}

上記のコンストラクタでsuper()に渡しているのは、
パスをハンドリングするストラテジ(VersionPathStrategy)のインスタンスで、PrefixVersionPathStrategyの代替となるもので、この後で実装する。
パスのハンドリングについてMiddleVersionPathStrategyを使い、これに対してprefixを渡せる、というのがFixedVersionStrategyとの違い。

次に、VersionPathStrategyの実装としてMiddleVersionPathStrategyと名付けたクラスを以下のように実装する。

public class MiddleVersionPathStrategy implements VersionPathStrategy {
    private final String prefix;
    private final String version;

    public MiddleVersionPathStrategy(String prefix, String version) {
        this.prefix = prefix;
        this.version = version;
    }

    @Override
    public String extractVersion(String requestPath) {
        if (requestPath.startsWith(this.prefix)) {
            String prefixRemoved = requestPath.substring(this.prefix.length());
            if (prefixRemoved.startsWith(this.version)) {
                return this.version;
            }
        }
        return null;
    }

    @Override
    public String removeVersion(String requestPath, String version) {
        // addVersion()にてprefixを一度取り払ってから
        // prefixとバージョンを付与しているので、
        // ここでは逆にprefixとバージョンを取り払ってprefixを付け直す
        return this.prefix + requestPath.substring(this.prefix.length() + this.version.length());
    }

    @Override
    public String addVersion(String path, String version) {
        // この分岐の必要性はわかっていない...
        // PrefixVersionPathStrategyの実装のまま
        if (path.startsWith(".")) {
            return path;
        } else {
            // "/"を先に除去しておく
            String p = path;
            if (p.startsWith("/")) {
                p = p.substring(1);
            }
            // 指定のprefixが付いているパスにのみバージョン付与可能とし
            // その他はバージョン付与せずにそのまま返す
            if (p.startsWith(this.prefix)) {
                return this.prefix + this.version + "/" + p.substring(this.prefix.length());
            } else {
                return path;
            }
        }
    }
}

PrefixVersionPathStrategyを参考に、とりあえず動くようにした実装なので、改善の余地ありという感じではあるけれど・・

最後に、WebConfigでは

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    VersionResourceResolver versionResolver = new VersionResourceResolver()
        .addContentVersionStrategy("/css/**", "/js/**")
        .addVersionStrategy(new PrefixAndFixedVersionStrategy("lib/", gitProperties.getCommitId()), "/lib/**");

というように、今回作成したPrefixAndFixedVersionStrategyを指定してaddVersionStrategy()を呼び出すようにする(上記の最後の行)。

これで/lib/9b726ab/bootstrap/dist/css/bootstrap.min.cssのようなパスが生成できるようになる。

今回更新した結果のソースコード一式はこちら

(2016/03/26 追記)
上記のコードでは、バージョンを削除したパスを作る部分(removeVersion())に問題があった。
bootRunで実行するときにはうまくいったが、java -jarで実行するときに、区切り文字(/)が重なっている部分がありバージョンを除去できない。

/lib/1c924f9/bootstrap/dist/css/bootstrap.min.css
/lib/bootstrap/dist/css/bootstrap.min.cssとしたいが
/lib//bootstrap/dist/css/bootstrap.min.cssとなっていた。

removeVersion()の中身の

return this.prefix + requestPath.substring(this.prefix.length() + this.version.length());

の部分は

return this.prefix + requestPath.substring((this.prefix + this.version + "/").length());

とする必要がある。
修正したコードはこちら

© 2010 ksoichiro