0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Security + MVC 認証システム

Posted at

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.javaCommandLineRunner を使用:

@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>

学んだこと

意外だった落とし穴

  1. ROLE_ プレフィックス

    • Spring Security では権限名に ROLE_ プレフィックスが自動的に付与される
    • hasRole('ADMIN') は内部的に ROLE_ADMIN として処理される
  2. パスワードエンコーディング

    • 平文パスワードを保存すると認証に失敗する
    • 必ず PasswordEncoder を使用してハッシュ化が必要
  3. H2 コンソールアクセス

    • CSRFトークンとX-Frame-Optionsの設定が必要
    • .csrf().ignoringRequestMatchers("/h2-console/**").frameOptions().sameOrigin() の設定

今後使えそうな知見

  • メソッドレベル認可: @PreAuthorize によりきめ細かいアクセス制御が可能
  • フラッシュメッセージ: RedirectAttributes でPRGパターンを実装
  • 条件分岐レンダリング: Thymeleaf の th:ifth: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 連携などのより高度な認証機能を実装してみたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?