Spring Security + MVC 認証システム実装記録
概要
Spring Boot 3.2.0 を使用して、認証・認可機能を持つ Web アプリケーションを一から実装しました。Spring Security によるフォームベース認証と、ロールベースのアクセス制御(ADMIN/USER)を実装し、完全な管理者・一般ユーザー管理システムを構築しました。
技術スタック
- Spring Boot: 3.2.0
- Spring Security: 6.x
- Spring Data JPA: データベースアクセス
- H2 Database: インメモリデータベース(開発環境)
- Thymeleaf: テンプレートエンジン
- BCrypt: パスワードハッシュ化
実装内容
1. セキュリティ設定の実装
SecurityConfig.java
では、Spring Security の設定を行いました。
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
)
.logout(logout -> logout
.logoutUrl("/logout")
.invalidateHttpSession(true)
);
return http.build();
}
}
重要なポイント:
-
@EnableMethodSecurity(prePostEnabled = true)
によりメソッドレベルの認可を有効化 -
/admin/**
パスは ADMIN ロールのみアクセス可能 - BCrypt によるパスワード暗号化
2. ユーザーエンティティとロール管理
User.java
で Spring Security の UserDetails
インターフェースを実装:
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role = Role.USER;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.name()));
}
}
学んだこと:
-
UserDetails
の実装により Spring Security との統合が簡単になる -
@Enumerated(EnumType.STRING)
でロールをデータベースに文字列として保存 - 権限名には
ROLE_
プレフィックスが必要
3. 管理者機能の実装
AdminController.java
でメソッドレベルの認可を活用:
@Controller
@RequestMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public class AdminController {
@GetMapping("/users")
public String userManagement(Model model) {
List<User> users = userService.getAllUsers();
model.addAttribute("users", users);
return "admin/users";
}
@PostMapping("/users")
public String createUser(@RequestParam String username,
@RequestParam String password,
@RequestParam Role role,
RedirectAttributes redirectAttributes) {
try {
userService.createUser(username, password, role);
redirectAttributes.addFlashAttribute("successMessage", "ユーザーが正常に作成されました。");
} catch (Exception e) {
redirectAttributes.addFlashAttribute("errorMessage", "ユーザーの作成に失敗しました: " + e.getMessage());
}
return "redirect:/admin/users";
}
}
実装のポイント:
- クラスレベルでの
@PreAuthorize
により全メソッドに認可を適用 -
RedirectAttributes
でフラッシュメッセージを実装 - 例外処理によるユーザーフレンドリーなエラー表示
4. 初期データの自動作成
DemoApplication.java
で CommandLineRunner
を使用:
@Override
public void run(String... args) throws Exception {
if (!userService.getUserByUsername("admin").isPresent()) {
userService.createUser("admin", "admin", Role.ADMIN);
System.out.println("デフォルト管理者ユーザーを作成しました: admin/admin");
}
if (!userService.getUserByUsername("user").isPresent()) {
userService.createUser("user", "user", Role.USER);
System.out.println("デフォルト一般ユーザーを作成しました: user/user");
}
}
5. Thymeleaf での条件分岐レンダリング
dashboard.html
でロールベースの表示制御:
<div th:if="${isAdmin}" class="admin-section">
<h3>🔧 管理者メニュー</h3>
<a th:href="@{/admin}" class="btn btn-primary">管理画面トップ</a>
<a th:href="@{/admin/users}" class="btn btn-success">ユーザー管理</a>
</div>
<div th:unless="${isAdmin}" class="dashboard-card">
<h3>📋 利用可能な機能</h3>
<p>一般ユーザーとして以下の機能を利用できます:</p>
</div>
学んだこと
意外だった落とし穴
-
ROLE_ プレフィックス
- Spring Security では権限名に
ROLE_
プレフィックスが自動的に付与される -
hasRole('ADMIN')
は内部的にROLE_ADMIN
として処理される
- Spring Security では権限名に
-
パスワードエンコーディング
- 平文パスワードを保存すると認証に失敗する
- 必ず
PasswordEncoder
を使用してハッシュ化が必要
-
H2 コンソールアクセス
- CSRFトークンとX-Frame-Optionsの設定が必要
-
.csrf().ignoringRequestMatchers("/h2-console/**")
と.frameOptions().sameOrigin()
の設定
今後使えそうな知見
-
メソッドレベル認可:
@PreAuthorize
によりきめ細かいアクセス制御が可能 -
フラッシュメッセージ:
RedirectAttributes
でPRGパターンを実装 -
条件分岐レンダリング: Thymeleaf の
th:if
とth:unless
でロールベース表示
もっと良い書き方の発見
// 改善前: 例外を投げるだけ
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
// 改善後: 適切な例外クラスを使用
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found with id: " + id));
終わりに
今回の実装により、Spring Security を使った認証・認可システムの基本的な構造を理解できました。特に、設定の統合化(Java Config)、メソッドレベルの認可、Thymeleaf との連携など、実際のプロジェクトで使える知識を得られました。
次回は、この基盤を元に JWT 認証や OAuth2 連携などのより高度な認証機能を実装してみたいと思います。