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?

Qiita第3投稿!Spring Security × JWT でログイン機能を実装してみた【Spring編(実装)】

Last updated at Posted at 2025-11-02

こんにちは!!
前回の記事
なぜ 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回目の投稿しますので、よろしくお願します!!🙇

フォローしていただけると確認できます!!
コメントもよろしくお願いします!!!

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?