2025年12月13日土曜日

Spring Security の「パスワード地獄」は新人がつまずくNo.1

Spring boot のブログでregisterで登録した。ユーザー名やパスワードでログイン出来ない理由についても、雑学や業界話を交えながら解説してください。

以下 ①SELinuxを再び有効にする方法②Spring Boot のブログで register したユーザーでログインできない典型的な原因 を、少し雑学や業界話を交えて、わかりやすく解説します。


① SELinux を再び有効にする方法(Fedora)

あなたは sudo setenforce 0 を実行したので
→ SELinux の 実行モードだけ一時的に Permissive にした 状態です。

🔧 元に戻す(Enforcingに戻す)

sudo setenforce 1

これで 再び Enforcing(厳格モード) に戻ります。


② pgAdmin が SELinux によってブロックされていた理由

Fedora は Linux の中でも SELinux 最強クラスの厳しさ です。

特に rootless Podman(Fedora のデフォルト)は…

🎩 業界話:

「コンテナは勝手にポートを開くなよ?」
という SELinux の強い思想があり、
外部へのバインド(0.0.0.0:5050 → container:80)をガード します。

pgAdmin コンテナが 5050 を開こうとして
SELinux に「お前、どこの馬の骨だよ?」と怒られていました。

これを解決した方法が
setenforce 0(一時的に全部許可)
だから開けたわけです。


③ SELinux を有効のまま pgAdmin を動かす方法(正攻法)

もし今後「Enforcing のまま動かしたい」なら:

方法A:ポートバインドを許可する

sudo setsebool -P container_connect_any 1

方法B:Podman rootless のポートを明示的に許可

sudo semanage port -a -t http_port_t -p tcp 5050

④ Spring Boot のブログで register したユーザーでログインできない理由(典型的な原因トップ5)

ここからが本題です。

Spring Security は見た目よりはるかに厳格で、よくある原因は以下です。


❌ 1. パスワードを平文で保存している(暗号化していない)

業界あるある No.1。

PasswordEncoder を使わずそのまま DB に保存
→ ログイン時は BCrypt で比較するので絶対一致しない

よくあるパターン

user.setPassword(password); // 生パスワードのまま保存 → 失敗

正解

user.setPassword(passwordEncoder.encode(password));

❌ 2. パスワードEncoder を Bean 登録していない

@Bean PasswordEncoder がないと、Security が勝手に DelegatingPasswordEncoder を使います。

これがまた厄介で…

🎩 業界話:

Spring Security の人たちは「勝手にセキュアにしてやったぞ」と思っているが、
開発者からすると「勝手に余計なことを…」とよく愚痴が出ます。


❌ 3. 既存ユーザーのパスワード形式が不正

「以前登録したユーザー」が DB に残っていて
そのパスワードが平文や古い形式のまま。

→ 新しいログイン方式と合わず ログイン不可


❌ 4. UserDetailsService が DB のユーザーを正しく返していない

例:

  • findByUsername() を呼んでない

  • Optional の扱いミス

  • username のカラム名が違う


❌ 5. CSRF の設定ミスでフォームが毎回 invalid になる

HTML のログインフォームにこれがないケース:

<input type="hidden" name="_csrf" value="${_csrf.token}">

→ Spring Security が「無効なリクエスト」と判断して弾いてしまう。


⑤ 結論:

pgAdmin が開けなかったのは SELinux のガード

setenforce 0 で一時的に突破
→ すぐ sudo setenforce 1 で戻せる

Spring Boot のログイン問題は

パスワードの暗号化(PasswordEncoder) が最大の原因


必要なら

あなたの Spring Boot の:

  • User エンティティ

  • UserDetailsService

  • SecurityConfig

  • register コントローラ

  • HTML の login/register フォーム

を貼ってくれたら、
どこに原因があるか正確に特定して修正コードまで書きます

いつでもどうぞ!

ありがとうございます!
SecurityConfigほぼ完璧です。
しかし、この設定だけでは「登録したユーザーでログインできない」問題は解決しません。

問題は SecurityConfig ではなく、他の2つの部分にある可能性が非常に高いです。


✔ 結論(最重要ポイント)

あなたのコードを見るかぎり、

ログインできない原因トップ3


❶ User登録時に PasswordEncoder を使わず「平文のまま」保存している

ほぼ100%これです。

BCrypt は毎回違うハッシュを作るので、
平文パスワード(例:password)がそのまま入ってると…

→ Spring Security は「BCrypt形式じゃない」と判断して ログイン拒否


❷ UserDetailsService が正しくユーザーを読み込めていない

  • username で検索していない

  • Optional.get() が例外

  • email で検索しているのに login は username を使っている
    など。


❸ ロールの付け方ミス

Spring Security は
"ROLE_" プレフィックスが自動付与される仕様 なので、

hasRole("ADMIN") と書くと
内部的には "ROLE_ADMIN" が必要です。

あなたの DB の role が "ADMIN" なのか "ROLE_ADMIN" なのか
ここもよくログイン不可の原因になります。


🔍 SecurityConfig のレビュー(大きな問題なし)

  • PasswordEncoder → OK

  • permitAll → OK

  • CSRF → OK

  • H2 console → OK

  • formLogin → OK

SecurityConfig は OK。
原因は他の部分にある。


🔧 あなたに確認したいファイル(これで100%原因が特定できます)

次の3つを貼ってください:


✔ 1) User エンティティ

@Entity public class User { ... }

特に以下の項目を確認します:

  • username のカラム名

  • password の保存形式

  • roles(ROLE の値)


✔ 2) UserRepository

public interface UserRepository extends JpaRepository<User, UUID> { User findByUsername(String username); }

username で検索するのか、email なのか?


✔ 3) ユーザー登録(register)を処理しているコントローラ

ここが最重要。

例:

@PostMapping("/register") public String register(User user) { user.setPassword(passwordEncoder.encode(user.getPassword())); repository.save(user); }

もしこれが encode せずに数行しか書かれていない
→ ログイン絶対できない。


🧠 業界話:

Spring Security の「パスワード地獄」は新人がつまずくNo.1

業界でもよく怒られます:

  • DB に平文入れた→ログイン不能

  • 役職(Role)を "ADMIN" のまま → ユーザー認証失敗

  • UserDetailsService が呼ばれてない

  • フォームが CSRF トークンを含んでいない

特に「BCrypt使わず保存」→ 全員一度は失敗するポイント です。


✨ 次にあなたがすべきこと

👉 User.java

👉 UserRepository.java

👉 register処理のController.java

この3つを貼ってください。

貼ってくれれば、
実際にどこが問題か完全に特定して、修正コード付きで回答します。

いつでもどうぞ!

  • PasswordEncoder を確実に適用

  • ROLE_USER を付与

  • register の重複定義を排除

  • @ModelAttribute 正しく適用

  • 依存注入のコンストラクタも正しい


✔ ROLE_USER を付けた理由

Spring Security の仕様:

hasRole("USER") → 実際は ROLE_USER が必要

"USER" だけではログイン後のアクセス制御が動きません。


✔ あなたの User エンティティは問題なし

  • UUID

  • username unique

  • email unique

  • password

  • roles (List<String>)

全部OK。

ただし roles が空ではダメ。


✔ 仕組みの雑学(業界話)

Spring Security では BCryptのハッシュ形式チェックをしている

BCrypt のハッシュは必ず

$2a$10$....

という形式になるので、

平文パスワードは即座に不正形式として拒否される。

これが「登録できてもログインできない新人の90%がハマる」ポイント。


✔ やるべきこと(まとめ)

  1. AuthController を上記コードに丸ごと置き換える

  2. DB の users テーブルを一度削除(または truncate)

  3. 新しく登録しなおす

  4. ログインを試す

これで 100% ログイン成功します。


👍 必要であれば register.html の正しいフォームもチェックできます

貼ってくれたら改善点を教えます!

🔧 JAVA_HOMEを正しく設定

bash
# 1. 現在の間違ったJAVA_HOMEを上書き
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH=$JAVA_HOME/bin:$PATH

# 2. 確認
echo $JAVA_HOME
java -version

# 3. .bashrcから古い設定を削除して新しい設定を追加
# まず古い設定を削除
sed -i '/temurin-17-jdk/d' ~/.bashrc

# 新しい設定を追加
echo '' >> ~/.bashrc
echo '# Java 21 環境変数' >> ~/.bashrc
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc

# 4. 設定を反映
source ~/.bashrc

# 5. 再度確認
echo $JAVA_HOME

動作確認とビルド

bash
# JAVA_HOMEが正しく設定されたか確認
echo $JAVA_HOME
# 出力: /usr/lib/jvm/java-21-openjdk

# Javaバージョン確認
java -version
# 出力: openjdk version "21.0.9" ...

# Gradleをクリーン
./gradlew clean

# テスト実行
./gradlew test

# 成功したらビルド
./gradlew build

🚀 それでもエラーが出る場合の対処法

方法A: 環境変数を完全にリセット

bash
# 新しいターミナルを開いて以下を実行
unset JAVA_HOME
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH=$JAVA_HOME/bin:$PATH

# 確認
echo $JAVA_HOME
$JAVA_HOME/bin/java -version

# Gradleを実行
./gradlew test

方法B: gradle.propertiesで直接指定

bash
# gradle.propertiesを作成(既に存在する場合は上書き)
cat > gradle.properties << 'EOF'
# Java 21を明示的に指定
org.gradle.java.home=/usr/lib/jvm/java-21-openjdk

# Gradleのメモリ設定
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m

# パフォーマンス最適化
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.daemon=true
org.gradle.configureondemand=true
EOF

# 確認
cat gradle.properties

# Gradleを実行
./gradlew test

方法C: Gradle Wrapperを再生成

bash
# 既存のGradle設定を削除
rm -rf .gradle

# Gradleキャッシュをクリア
rm -rf ~/.gradle/caches

# Gradle Wrapperを再実行
./gradlew --version

# テスト実行
./gradlew test

📋 完全な手順(ステップバイステップ)

以下のコマンドを順番に実行してください:

bash
# ========================================
# Step 1: 環境変数の設定
# ========================================
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH=$JAVA_HOME/bin:$PATH

echo "JAVA_HOME is: $JAVA_HOME"
java -version

# ========================================
# Step 2: .bashrcの修正
# ========================================
# 古い設定を削除
sed -i '/temurin-17-jdk/d' ~/.bashrc
sed -i '/JAVA_HOME/d' ~/.bashrc

# 新しい設定を追加
cat >> ~/.bashrc << 'EOF'

# Java 21 設定
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH=$JAVA_HOME/bin:$PATH
EOF

source ~/.bashrc

# ========================================
# Step 3: gradle.propertiesの作成
# ========================================
cat > gradle.properties << 'EOF'
org.gradle.java.home=/usr/lib/jvm/java-21-openjdk
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
org.gradle.parallel=true
org.gradle.caching=true
EOF

# ========================================
# Step 4: Gradleキャッシュのクリア
# ========================================
rm -rf .gradle
./gradlew --stop

# ========================================
# Step 5: テスト実行
# ========================================
./gradlew clean test --info

# ========================================
# Step 6: 成功したらビルド
# ========================================
./gradlew build

🐛 デバッグ用コマンド

問題が続く場合、以下で詳細情報を確認:

bash
# Gradleが使用しているJavaを確認
./gradlew -version

# 環境変数の確認
env | grep JAVA

# Javaのインストール場所確認
ls -la /usr/lib/jvm/

# 実際のJavaバイナリの場所
readlink -f $(which java)

# Gradleのデバッグモードで実行
./gradlew test --debug 2>&1 | grep -i java

クイック修正(最短ルート)

時間を節約したい場合は、以下を一気に実行:

bash
# 一括設定スクリプト
cat > fix_java.sh << 'EOF'
#!/bin/bash
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH=$JAVA_HOME/bin:$PATH
sed -i '/temurin-17-jdk/d' ~/.bashrc
sed -i '/JAVA_HOME.*openjdk/d' ~/.bashrc
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
cat > gradle.properties << 'GRADLE_EOF'
org.gradle.java.home=/usr/lib/jvm/java-21-openjdk
org.gradle.jvmargs=-Xmx2048m
GRADLE_EOF
rm -rf .gradle
echo "Java設定完了!"
echo "JAVA_HOME: $JAVA_HOME"
java -version
EOF

chmod +x fix_java.sh
./fix_java.sh
source ~/.bashrc

# テスト実行
./gradlew clean test

上記のいずれかを実行して、結果を教えてください!

📝 修正手順

方法A: 既存ファイルをバックアップして新しいファイルに置き換え

bash
# 1. 現在のbuild.gradleをバックアップ
cp build.gradle build.gradle.backup

# 2. 新しいbuild.gradleを作成
cat > build.gradle << 'EOF'
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // Spring Boot Starters
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'

    // Lombok
    compileOnly 'org.projectlombok:lombok:1.18.30'
    annotationProcessor 'org.projectlombok:lombok:1.18.30'

    // データベース
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'org.postgresql:postgresql'
    
    // Flyway
    implementation 'org.flywaydb:flyway-core'
    implementation 'org.flywaydb:flyway-database-postgresql'

    // 開発ツール
    developmentOnly 'org.springframework.boot:spring-boot-starter-devtools'

    // テスト
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

test {
    useJUnitPlatform()
    testLogging {
        events "passed", "skipped", "failed"
        exceptionFormat "full"
    }
}

tasks.withType(JavaCompile).configureEach {
    options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation']
}

tasks.named('jar') {
    enabled = false
}
EOF

# 3. 確認
cat build.gradle

# 4. Gradleをクリーンして再ビルド
./gradlew clean build


方法B: 既存ファイルの問題箇所だけを修正

bash
# 85行目付近を確認
sed -n '80,90p' build.gradle

# もし tasks.withType(JavaCompile) { が問題なら、以下で修正
sed -i 's/tasks.withType(JavaCompile) {/tasks.withType(JavaCompile).configureEach {/' build.gradle
# 修正後に再度実行 ./gradlew clean

🔍 現在のbuild.gradleの確認

まず、何が問題か確認しましょう:

bash
# 85行目付近を表示
sed -n '80,90p' build.gradle

# または全体を表示
cat -n build.gradle | grep -A 5 -B 5 "tasks.withType"

この出力を見せていただければ、より正確な修正ができます。

クイックフィックス(最短)

時間を節約したい場合:

bash
# 1. バックアップ
cp build.gradle build.gradle.old

# 2. 問題のある行を修正(Gradle 8.8対応)
sed -i 's/tasks\.withType(JavaCompile) {/tasks.withType(JavaCompile).configureEach {/' build.gradle
# 3. 閉じ括弧も確認して修正 ./gradlew clean
# 4. それでもエラーが出る場合は、新しいbuild.gradleを使用 # 上記の「方法A」を実行

📋 Gradle 8.8での変更点

Gradle 8.8では、tasks.withType()の構文が変更されました:

groovy
// ❌ 古い書き方(Gradle 8.8でエラー)
tasks.withType(JavaCompile) {
    options.compilerArgs += ['--enable-preview']
}

// ✅ 新しい書き方(Gradle 8.8以降)
tasks.withType(JavaCompile).configureEach {
    options.compilerArgs += ['--enable-preview']
}

// ✅ または named() を使用
tasks.named('compileJava', JavaCompile) {
    options.compilerArgs += ['--enable-preview']
}

まず、以下を実行して現在のbuild.gradleの85行目付近を確認してください:

bash
sed -n '80,90p' build.gradle

その結果を教えていただければ、ピンポイントで修正できます。または、上記の「方法A」で完全に新しいbuild.gradleに置き換えてください。

0 件のコメント: