はじめに
アプリケーション開発では、「セキュリティ」と「認証」は、ユーザーのデータを守り、アクセスを管理するために重要です。Spring Frameworkには、これらを簡単に実装できるSpring Securityという強力なツールがあります。
Spring Securityの概要
Spring Securityとは?
Spring Securityは、Webアプリケーションの「認証(ログイン)」や「認可(権限管理)」を提供するフレームワークです。
- 認証(Authentication): 「この人が本当に誰なのか?」を確認します。
- 例: ユーザー名とパスワードでログイン。
- 認可(Authorization): 「この人はこの操作をしてもいいのか?」を確認します。
- 例: 管理者だけが特定のページにアクセスできるようにする。
Spring Securityでログイン機能を実装
作成は下記の流れで行なっていきます。
- 依存関係の追加
- エンティティの作成
- リポジトリの作成
- サービスの作成(認証(Authentication)の設定)
- Spring Securityの設定
- コントローラーおよびログインページの作成
プロジェクトに依存関係を追加
build.gradle
に以下の依存関係を追加します。
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
エンティティ: User
ユーザー情報を保存するエンティティを作成します。
src/main/java/com/example/sql/model/User.java
package com.example.sql.model;
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String role; // 例: "ROLE_USER", "ROLE_ADMIN"
public User() {}
public User(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
// ゲッターとセッター
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
}
リポジトリ: UserRepository
ユーザー情報をデータベースから取得するためのリポジトリを作成します。
src/main/java/com/example/sql/repository/UserRepository.java
package com.example.securitydemo.repository;
import com.example.securitydemo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
ポイント
findByUsername
findByUsername
は、Spring Data JPA が自動的に解釈してクエリを生成する仕組みを利用しています。
findBy
:- この部分は 検索処理を行う ことを表します。
- 「
find
」は「データを検索する」アクションで、「By
」は検索条件の指定を示します。
Username
:By
に続く部分(Username
)は、エンティティのフィールド名 に対応します。User
エンティティのusername
フィールドに基づいて検索条件が作られます。
Spring Data JPA のクエリ生成の流れ
- Spring Data JPA は、
findByUsername
のメソッド名を解析して以下のような SQL を自動生成します
SELECT * FROM user WHERE username = ?; (user はテーブル名、username はカラム名)
- つまり、
findBy<FieldName>
という命名規則に従っている場合、Spring Data JPA が自動的に適切なクエリを生成します。
今回はusernameを使ってユーザーを特定するので追加しています。
ユーザー詳細サービスの設定
Spring Securityが使用するユーザー情報を提供するサービスを作成します。
src/main/java/com/example/sql/service/UserDetailsServiceImpl.java
package com.example.sql.service;
import com.example.sql.model.User;
import com.example.sql.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Collections;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
Collections.singleton(new SimpleGrantedAuthority(user.getRole()))
);
}
}
ポイント
UserDetailsService
の実装- このクラスは Spring Security がユーザー情報を取得する際に使用するサービスを提供します。
- インターフェース
UserDetailsService
のメソッドloadUserByUsername
をオーバーライドすることで、カスタムな認証ロジックを実装します。
セキュリティ設定
Spring Securityの設定をカスタマイズします。
目的
SecurityConfig
は、Spring Security を利用した認証と認可の設定を行うためのクラスです。このクラスは、アプリケーションのセキュリティポリシーを定義します。
src/main/java/com/example/sql/config/SecurityConfig.java
package com.example.sql.config;
import com.example.sql.service.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@Configuration
public class SecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/error", "/resources/**").permitAll() // 認証不要
.anyRequest().authenticated() // 認証が必要
)
.formLogin(form -> form
.loginPage("/login") // カスタムログインページ
.defaultSuccessUrl("/", true) // 認証成功後のリダイレクト先
.failureUrl("/login?error=true") // 認証失敗時のリダイレクト先
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout=true") // ログアウト成功後のリダイレクト先
.permitAll()
);
return http.build();
}
@Bean
public HttpFirewall allowSemicolonFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
}
主要ポイント
SecurityFilterChain
役割
HTTPリクエストに対するセキュリティ設定を定義します。
ポイント
- CSRF無効化
→ テストやシンプルなアプリケーションで CSRF 保護を無効化しています(実運用では注意が必要)。
.csrf(csrf -> csrf.disable())
- リクエストごとの認可設定
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/error", "/resources/**").permitAll() // 認証不要
.anyRequest().authenticated() // 他のリクエストは認証が必要
)
- フォームログイン設定
→/login
,/error
,/resources/**
は認証不要、それ以外は認証が必要
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/error", "/resources/**").permitAll() // 認証不要
.anyRequest().authenticated() // 他のリクエストは認証が必要
)
- ログアウト設定
→/logout
エンドポイントでログアウトを処理し、成功後に/login?logout=true
へリダイレクト。
.logout(logout -> logout
.logoutUrl("/logout") // ログアウトのエンドポイント
.logoutSuccessUrl("/login?logout=true") // ログアウト成功後のリダイレクト先
.permitAll()
)
HttpFirewall
役割
HTTPリクエストの構文検証やセキュリティ強化を行います。
今回は;
(セミコロン)を URL 内で許可しています。
通常、セミコロンは HTTPリクエストのパラメータ区切りに使われ、セキュリティリスクがある場合があるため、デフォルトではブロックされます。特定の要件がある場合にのみ設定します。
@Bean
public HttpFirewall allowSemicolonFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
PasswordEncoder
役割
パスワードをハッシュ化して保存・認証時に比較します。
ポイント
安全なハッシュアルゴリズムである BCrypt
を使用。
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
AuthenticationManager
役割
認証プロセスを管理します。
ポイント
Spring Security が提供する認証マネージャをカスタマイズせず利用。
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
コントローラー作成
src/main/java/com/example/sql/controller/LoginController.java
package com.example.sql.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
// GETリクエストで "/login" にアクセスされたときに呼び出される
@GetMapping("/login")
public String loginPage() {
return "login"; // "login.html" というテンプレートを返す
}
}
ログインページ
ログインフォームを提供するHTMLを作成します。
src/main/resources/templates/login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form th:action="@{/login}" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Login</button>
</form>
<div>
<p th:if="${param.error}">Invalid username or password.</p>
<p th:if="${param.logout}">You have been logged out.</p>
</div>
</body>
</html>
ホームページ
ログイン後に表示されるホームページを作成します。
src/main/resources/templates/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Home</title>
</head>
<body>
<h1>Welcome!</h1>
<p>You are successfully logged in.</p>
<a th:href="@{/logout}">Logout</a>
</body>
</html>
検証
テストデータの作成
ハッシュ化されたパスワードを用意する必要があります。
Spring CLIをインストールしている場合、以下のコマンドで生成できます。
spring encodepassword your_password_here
MySQLにアクセスし、データをinsertします。
INSERT INTO DB名.users (username, password, role)
VALUES ('user1’, 'password', 'ROLE_USER')
アプリケーションの起動
./gradlew bootRun
ブラウザでアクセス
- ログインページ:
http://localhost:8080/login
- ログイン成功後: 指定したデフォルトページ(例:
/
)にリダイレクト。
もしうまくレンダリングされていない場合はsrc/main/resources/application.properties
に下記を追加してください。
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
その他の高度な認証方法
OAuth2とは?
OAuth2は、ユーザーが安全に外部サービス(例: Google, Facebook)を利用してアプリにログインできる仕組みです。
メリット:
- ユーザー名やパスワードを直接管理する必要がない。
- 外部認証プロバイダー(Googleなど)を利用できる。
JWT(JSON Web Token)とは?
JWTは、認証情報を安全にやり取りするためのトークンです。ユーザーがログインすると、サーバーがJWTを生成し、クライアントに渡します。
- メリット:
- トークンを使うことで、サーバー側でセッションを管理する必要がない。
- 軽量で効率的。
- JWTを使った認証フロー:
- ユーザーがログインする。
- サーバーがJWTを生成してクライアントに返す。
- クライアントはリクエストごとにJWTを送る。
- サーバーはJWTを検証してリソースを提供する。
認証方法の選択
認証方法 | 使用例 | メリット | デメリット |
---|---|---|---|
Spring Security (基本) | シンプルなアプリや内部システム | 容易にセットアップ可能 | 拡張性に欠ける |
OAuth2 | GoogleやFacebookでログイン | 外部認証を活用でき、セキュリティが向上 | 外部サービス依存 |
JWT | モバイルアプリや分散システム | 軽量でスケーラブル | セキュリティ設定の理解が必要 |
まとめ
認証のフローをまとめるとこのようになります。
通常のMVCフロー | Spring Security認証フロー |
---|---|
クライアントがリクエストを送信する | クライアントがリクエスト(例: /login )を送信する |
コントローラーがリクエストを受け取る | フィルタチェーンがリクエストを受け取る |
サービスがリポジトリを通じてデータを取得 | UserDetailsServiceがリポジトリを通じてユーザーを取得 |
結果をコントローラーに返す | 認証結果をフィルタチェーンが処理し、SecurityContextに保存 |
コントローラーが結果をクライアントに返す | 認証成功後、リクエストはコントローラーに渡される |
フィルタ層とは
フィルタ層は、リクエストやレスポンスに対して特定の処理(例: 認証、認可、ログ記録、データ変換など)を行う仕組みを指します。Spring Frameworkでは、特にServlet Filter
やFilter Chain
を通じて、このフィルタリング処理が実現されます。
重要なポイント
- Spring Securityはフィルタ層で認証を処理
- 通常のMVCフローとは異なり、認証部分は コントローラーよりも前 に処理されます。
- そのため、認証処理はコントローラーの中ではなく、
UserDetailsService
やSecurityConfig
で定義します。
- 認証後にコントローラーにリクエストが渡る
- 認証成功後、通常のコントローラーの処理(サービスクラスやリポジトリの呼び出し)に進みます。
- 設定ファイルでアクセス制御
- Spring Securityは
HttpSecurity
を使って、リクエストごとのアクセス制御を定義できます。 - コントローラーでリクエストを受ける前に、アクセス可能かを判定します。
- Spring Securityは
セキュリティはアプリ開発において非常に重要です。この基本を理解し、実践してみましょう!
この記事が参考になれば幸いです。
コメント