こんにちは!!
前回の記事
なぜ Spring Security と JWT を混同してはいけないのか?【Spring編】
では、両者の「役割の違い」と「補完関係」について解説しました。
今回はその続編として、
実際に Spring Security と JWT を組み合わせてログイン認証を実装してみます!
目標
こんなシンプルな構成を目指します。
ログイン(ID・パスワード入力)
↓
認証成功 → JWTトークン発行
↓
以降のリクエストはトークンで認可
使用環境
Spring Boot 3.x
Spring Security 6.x
Java 17
io.jsonwebtoken (JJWT)
1.依存関係の追加(Gradle)
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
2. 仮のユーザーサービス
本来は DB を使いますが、ここでは簡単のため固定ユーザーで実装します!
@Service
public class UserService {
private final Map<String, String> users = Map.of(
"sangmin", "password123",
"admin", "adminpass"
);
public boolean authenticate(String username, String password) {
return users.containsKey(username) && users.get(username).equals(password);
}
}
3. JWT ユーティリティクラス
JWT の発行・検証を行う基本クラスです。
@Component
public class JwtUtil {
private static final String SECRET_KEY = "super-secret-key-example";
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1時間
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public String extractUsername(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean isTokenValid(String token) {
try {
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
return true;
} catch (JwtException e) {
return false;
}
}
}
4. JWT フィルター設定
Authorization ヘッダにあるトークンを検証し、
ユーザー情報を SecurityContext にセットします。
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtil.isTokenValid(token)) {
String username = jwtUtil.extractUsername(token);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, List.of());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(request, response);
}
}
5. SecurityConfig 設定クラス
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
6. 認証エンドポイント(Controller)
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody Map<String, String> req) {
String username = req.get("username");
String password = req.get("password");
if (userService.authenticate(username, password)) {
String token = jwtUtil.generateToken(username);
return ResponseEntity.ok(Map.of("token", token));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("error", "Invalid credentials"));
}
@GetMapping("/hello")
public String hello() {
return "認証済みユーザーのみ見れるページです ";
}
}
7. 動作確認
ステップ1:ログインリクエスト
POST /auth/login
Content-Type: application/json
{
"username": "sangmin",
"password": "password123"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6Ikp..."
}
ステップ2:トークンでアクセス
GET /auth/hello
Authorization: Bearer <token>
GETMAPPINGの
/auth/hello URLでの接続し、接続できることを確認する。
まとめ
| 役割 | 担当 |
|---|---|
| Spring | Security リクエストの保護・ルール設定 |
| JWT | 「誰がログイン中か」を記録・検証 |
二つを組み合わせることで、
セッションレスな API 認証 が実現できます。
次回も4回目の投稿しますので、よろしくお願します!!🙇
フォローしていただけると確認できます!!
コメントもよろしくお願いします!!!