iku8log

Webエンジニアのタダのメモ。

【Spring Security & axios】異なるオリジン間での認証とcookie保持について

※コードは全て載せていません。ただのメモ

以下の通り開発をしている。

フロントにはvueを使用しているが、vueに限った話ではないので割愛。

httpリクエストのツールとしてaxiosを使用しています。

問題

異なるオリジン間(今回だとlocalhost:8080とlocalhost:3000になる)で、認証を行う場合、 localhost:3000でセッションIDをcookieに保持することができなかった。

正確には、localhost:8080側のcookieにセッションIDが保持されていた。

CSRFとかCORSを意思しないと行けない。でも難しい...。

したいこと

localhost:3000からaxiosを使用し、localhost:8080に対してログイン処理のリクエストを送る。 localhost:8080側でid,passを検証し認可。 また、セッションIDの発行を行い、localhost:3000のcookieに発行したセッションIDをセットする。

解決策

  • フロントでwithCredentialsを有効にする
  • サーバ側も↑を許可するための設定を行う。
// フロント側
  public async login() {
    const params = new URLSearchParams()
    params.append('email', this.email)
    params.append('password', this.password)

    await axios.post(
      'localhost:8080/login',
      params,
      {
        withCredentials: true // ここが重要
      }
    ).then((result) => {
      window.console.log(result)
      window.console.log('成功')
    }).catch((result) => {
      window.console.log(result)
      window.console.log('失敗')
    })
  }
// サーバ側
    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedMethod("POST");
        corsConfiguration.addAllowedOrigin("http://localhost:3000");
        corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);
        corsConfiguration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
        corsSource.registerCorsConfiguration("/login", corsConfiguration);

        return corsSource;
    }

サーバ側はcorsの設定を色々していますが、 corsConfiguration.setAllowCredentials(true);この部分が重要

あとは 、corsConfigurationSourceをspring securtyに設定する。

        http
            .authorizeRequests()
                .anyRequest()
                .authenticated()
            .and()
                .formLogin()
                .loginProcessingUrl("/login").permitAll()
                .usernameParameter("email")
                .passwordParameter("password")
                .successHandler(new SuccessHandler())
            .and()
                .csrf()
                    .ignoringAntMatchers("/login")
            .and()
                .cors()
                .configurationSource(corsConfigurationSource());

とりあえず詰まっていた部分を載せました。 過不足多いと思いますので、参考程度してください。

csrfもcorsもムズイ。というかspring securityが難しい。

MyBatis Generatorの生成されたモデルでblobが分離される

MyBatis Generatorを使って、コードを自動生成したところ、DB上でtext型のカラムが分離されて他クラスに居たので、 なぜだろうと思って調べたメモ。

DBはMySQLを使用。

commentテーブルを例にとって。このテーブルには3カラムだけ。

カラム名 説明
id bigint(PK) プライマリーキー
userId bigint(index) ユーザID
text text型 コメント内容

どうやらMySQL上でtext型やPKのカラムは、コード自動生成後別クラスのモデルにされるらしい。 例えば以下自動生成されたコメントクラス

public class Comment extends CommentKey implements Serializable {
    private Long userId;
}

おや、userIdしかない。PKのidとかtextカラムは?

CommentKeyこれを継承しているよう。

CommentKeyクラスを見てみると

public class CommentKey implements Serializable {
    private Long id;
    private static final long serialVersionUID = 1L;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
}

なるほどPKはなぜか別クラスになっているらしい。

そして、textカラムはどこにいったかと言うと、

CommentWithBLOBsクラス

public class CommentWithBLOBs extends Comment implements Serializable {
    private String text;
    private static final long serialVersionUID = 1L;
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }

テキスト型のカラムは、〇〇WithBLOBsクラスになるっぽい。

で、Commentを継承しているので、すべてのカラムの情報を取得する場合は、

CommentWithBLOBsクラスを使えば良さそう。


でも、Mapperの方にもちょっと問題があって、

PK(今回はlong型のid)を指定して、データを取得するとき、 long型のidをCommentMapperselectByPrimaryKey に与えられないっぽい。なぜなら。

 CommentWithBLOBs selectByPrimaryKey(CommentKey key);

こんな感じで、long型ではなく、CommentKey型を期待しているから。

ちょっと面倒なので、MyBatis Generatorの設定を見直す。

generator_config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="schema" defaultModelType="hierarchical">
// 以下省略

設定はどこからコピって来たので、あまり気にしてはいなかった。 どうも、defaultModelTypeが怪しそうなので、調べてみると。

このプロパティは生成方法らしい。 以下公式。 http://www.mybatis.org/generator/configreference/context.html

3つくらいあるが、hierarchicalを明示的に指定していたため、〇〇Keyや〇〇WithBlobsクラスが出来ていた。

シンプルが楽なので、デフォルトにしてやることにした。↓

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="schema" defaultModelType="conditional" targetRuntime="MyBatis3">
// 以下省略

指定しなくても、デフォルト値なので一緒だが明示的にね。 あと、今回関係ないがtargetRuntimeもデフォルト値をセットしておいた。

やっぱり公式ちゃんと見ないとね。

【Mac】Intellij内でMacのfinderを使わない方法

macのfinderは使いづらいので、intellijでファイル開いたりする時に、デフォルトfinderじゃなくします。

以下すべてintellij上の操作です。phpstormとか他のソフトも同じなはず。

  1. cmd + shift + a(shift2回でも可)を押す
  2. registryと検索
  3. ide.mac.file.chooser.nativeを検索
  4. 3のチェックを外す

f:id:iku8:20190706160008p:plain

これでfileを開いた時に、finderは使わなくなります。安心安心。

gradleのapiとimplementationの指定で、依存関係を伝搬したりしなかったり

gradleのapiとimplementationの指定で、依存関係を伝搬したりしなかったりするらしい。

apiとimplementationの違いは伝搬するかどうか

gradleで以下のような記述がある

Aプロジェクト(伝搬する)

dependencies {
    api 'org.apache.commons:commons-lang3:3.7'
}

Aプロジェクト(伝搬しない)

dependencies {
    implementation 'org.apache.commons:commons-lang3:3.7'
}

違いとしては、

apiは他プロジェクトにcommons-lang3:3.7が伝搬する。つまり他のプロジェクトでも、 Aプロジェクトに依存していれば、commons-lang3:3.7が使える。

implementationは他のプロジェクトで、Aプロジェクトに依存していても、 commons-lang3:3.7は使えない。

Aプロジェクトに依存している状態というのは、以下な感じかな。

Bプロジェクト

dependencies {
    implementation project(':Aプロジェクト')
}

この例では、implementationを使っているので、Aプロジェクトで依存しているものをBで使うことは出来ない

compileは非推奨

compileはとっくに非推奨になっているので使っちゃダメ。 ちなみにcompileはapiと同じく伝搬する。

なるほどなるほど。理解できた。

参考(わかりやすい) Gradle の compile, api, implementation とかについて - Qiita

EC2 Linux2上に立てた、MySQL5.7のタイムゾーンをJSTにする

EC2 Linuxと書いていますが、あまり関係はなくMySQL5.7のタイムゾーン設定のメモです。

my.confにタイムゾーンの設定を書くだけだとうまく反映されず Fatal error: Illegal or unknown default time zone 'Asia/Tokyo' となってしまったので、メモ。

MySQL起動

my.cnfにタイムゾーンを設定して起動すると、コケて起動しないので、 まずはmy.cnfを何もいじらず起動する。

sudo systemctl start mydqld
sudo systemctl status mydqld

mysqlタイムゾーン初期化

参考: MySQLでタイムゾーンを設定する - Qiita

どうやらMySQLタイムゾーンがバカになっているので、初期化する

/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo > ~/timezone.sql
mysql_tzinfo_to_sql /usr/share/zoneinfo/ | mysql -u root -p

パスワードを求められ、入力後初期化が実行される

再起動時にコケる可能性があり、念の為一度MySQLを停止しておく。

sudo systemctl stop mydqld

タイムゾーン設定

/etc/my.cnfに設定を追記する。

[mysqld]
default-time-zone='Asia/Tokyo'

起動

sudo systemctl start mydqld
sudo systemctl status mydqld

これでNOW()関数を実行していみると、JST時間で表示されると思います。 SHOW VARIABLES LIKE '%time_zone%'; 上記のクエリで、タイムゾーンの設定が見え、JSTになっていることが分かります。