1
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?

【 JWTを使ったspring security】

Last updated at Posted at 2025-06-18

今回はめちゃシンプルに「ユーザー名とパスワードでログイン → JWT発行 → 以降のAPIアクセスはトークン認証」って流れにする。

ざっくり構成

Spring Boot
├── SecurityConfig(セキュリティ設定)
├── JwtUtil(トークン発行・検証)
├── JwtAuthenticationFilter(リクエストのJWTチェック)
├── UserDetailsService(ユーザー情報取得)
├── AuthController(ログイン用API)
└── SampleController(認証が必要なAPI)

実装する流れ(ざっくり)

  1. 依存ライブラリ追加
  2. JwtUtil作成
  3. 認証フィルター作成
  4. SecurityConfig作成
  5. UserDetailsService実装
  6. ログインAPI作成
  7. フロント側でAPIにトークンの付与と保持
  8. 動作確認

サンプルコード

① 依存ライブラリ(build.gradle)
これをpom.xmlに追加。今回は上の方で実装しました。

jjwt-api → API の定義
jjwt-impl → 実装部分 (runtime スコープ)
jjwt-jackson → Jackson を使った JSON パース (runtime スコープ)

特徴
シンプルな API で JWT の生成・検証が可能
JWS (JSON Web Signature) や JWE (JSON Web Encryption) に対応
HMAC, RSA, ECDSA などの署名アルゴリズム をサポート
Jackson を使った JSON パース (jjwt-jackson が必要)

        <dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>

またはこっち!
特徴
Auth0 によって開発・メンテナンス
JWS (署名付き JWT) のみ対応 → JWE (暗号化) は非対応
よりシンプルな API
依存関係が少なく、軽量
まあ、簡単に説明するとJWE (暗号化) ができるかどうか位に認識しか今はしてないです...

		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>4.4.0</version>
		</dependency>

もしspring sequrityが入ってない場合は下記も追加

		<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-security</artifactId>
    	</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
		</dependency>

JwtUtil(トークンの発行と検証)

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.security.Keys;
import java.security.Key;

@Component
public class JwtUtil {
private final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); 

public String generateToken(String username) {
    return Jwts.builder()
        .setSubject(username)
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1時間の有効期限
        .signWith(SECRET_KEY)
        .compact();
}

public String extractUsername(String token) {
    return Jwts.parserBuilder() 
        .setSigningKey(SECRET_KEY)
        .build()
        .parseClaimsJws(token)
        .getBody()
        .getSubject();
}

public boolean validateToken(String token) {
    try {
        Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
        return true;
    } catch (Exception e) {
        return false;
    }
}
}

JWT (JSON Web Token) を扱うためのユーティリティクラス JwtUtil です。 主に トークンの生成・検証・ユーザー名の抽出 を行います。

このコードの動作
generateToken(username) → username を含む JWT を生成
extractUsername(token) → トークンから username を抽出
validateToken(token) → トークンが 改ざんされていないか検証

コードの解説

  1. クラス定義と SECRET_KEY の作成
@Component
public class JwtUtil {
    private final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);

@Component → Spring によって Beanとして管理 される
SECRET_KEY → HMAC-SHA256 の 秘密鍵を生成
Keys.secretKeyFor(SignatureAlgorithm.HS256) は、適切な長さの秘密鍵を自動生成する

  1. generateToken(String username): JWT の生成
public String generateToken(String username) {
    return Jwts.builder()
        .setSubject(username)
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1時間の有効期限
        .signWith(SECRET_KEY)
        .compact();
}

setSubject(username) → トークンの対象(subject)として username をセット setIssuedAt(new Date()) → トークンの発行日時を現在時刻に設定 setExpiration(...) → 1時間後にトークンが無効化 される設定 signWith(SECRET_KEY) → 生成した秘密鍵 (SECRET_KEY) で署名
compact() → トークンを 最終的な文字列形式 に変換

  1. extractUsername(String token): トークンからユーザー名を取得
public String extractUsername(String token) {
    return Jwts.parserBuilder() 
        .setSigningKey(SECRET_KEY)
        .build()
        .parseClaimsJws(token)
        .getBody()
        .getSubject();
}

parseClaimsJws(token) → トークンを解析
getBody().getSubject() → トークンの中から username を取得

validateToken(String token): トークンの検証

まとめ

JWT を生成 (generateToken)
JWT から username を取得 (extractUsername)
JWT の正当性を検証 (validateToken)
Spring の @Component で Bean 管理
HMAC-SHA256 で署名を行い安全性を確保

このコードで 認証・セッション管理 ができるので、Spring Security と組み合わせて使うとより強力になります

JwtAuthenticationFilter(リクエスト毎にトークンチェック)

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final MyUserDetailsService myUserDetailsService;

@Autowired
public JwtAuthenticationFilter(JwtUtil jwtUtil, MyUserDetailsService myUserDetailsService) {
    this.jwtUtil = jwtUtil;
    this.myUserDetailsService = myUserDetailsService;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    String token = request.getHeader("Authorization");//JWT(トークン)を取得.HTTPリクエストの Authorization ヘッダーからJWTを取得。通常、JWTは "Bearer <TOKEN>" という形式で送られてくる。
    if (token != null && jwtUtil.validateToken(token.replace("Bearer ", ""))) {//トークンが存在するかチェック & 検証。token が null ではなく、かつ jwtUtil.validateToken() で正しいトークンかどうか検証。.replace("Bearer ", "") で "Bearer " を削除し、純粋なJWTの文字列を取得。
        String username = jwtUtil.extractUsername(token.replace("Bearer ", ""));//ユーザー名を抽出
        UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);//ユーザー情報の取得。UserDetailsService を使って、username に該当する ユーザー情報(権限など) を取得。
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(//認証オブジェクトを作成
                userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authToken);
    }
    filterChain.doFilter(request, response);//フィルタの継続
}
}

この JwtAuthenticationFilter クラスは、Spring Security を使って JWT を検証し、認証情報をセットするフィルター です。 認証済みのリクエストのみを処理するための重要な要素!

コードの解説

  1. クラス定義とフィールド
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtUtil jwtUtil;
    private final MyUserDetailsService myUserDetailsService;

@Component → Spring によって Bean管理 される
JwtUtil → JWT の生成・検証を担当するクラス
MyUserDetailsService → ユーザー情報を取得する UserDetailsService

  1. コンストラクタ (@Autowired)
@Autowired
public JwtAuthenticationFilter(JwtUtil jwtUtil, MyUserDetailsService myUserDetailsService) {
    this.jwtUtil = jwtUtil;
    this.myUserDetailsService = myUserDetailsService;
}

依存関係の注入 (JwtUtil と MyUserDetailsService) を Spring に任せることで、適切に管理できるようになってる。

  1. JWT 認証のフィルタ処理 (doFilterInternal)
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {3. 

このメソッドは、すべてのリクエストに適用されるフィルター処理 を実装する部分です。

  1. Authorization ヘッダーからトークンを取得
String token = request.getHeader("Authorization");

リクエストの Authorization ヘッダーから JWT を取得します。 通常、ヘッダーの値は "Bearer " の形式になっているので、次の処理で "Bearer " を取り除きます。

if (token != null && jwtUtil.validateToken(token.replace("Bearer ", ""))) {

・token != null → トークンが 存在するか チェック
・jwtUtil.validateToken() → JWT の検証(有効なトークンかどうか確認)
・.replace("Bearer ", "") → Bearer の接頭辞を削除

  1. ユーザー名をトークンから取得
String username = jwtUtil.extractUsername(token.replace("Bearer ", ""));

jwtUtil.extractUsername() を使い、トークンからユーザー名を取得。

  1. ユーザー名をトークンから取得
    jwtUtil.extractUsername() を使い、トークンからユーザー名を取得。
UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);
  1. UsernamePasswordAuthenticationToken を作成
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
        userDetails, null, userDetails.getAuthorities());

userDetails → ユーザー情報
null → 認証時にパスワードを不要にするため
userDetails.getAuthorities() → ユーザーの権限情報を付与

  1. Spring Security に認証情報をセット
SecurityContextHolder.getContext().setAuthentication(authToken);

この処理によって、Spring Security はユーザーが 認証済み であることを認識します。

  1. フィルターの継続
filterChain.doFilter(request, response);

ここで、次のフィルターに処理を 渡します。 他の Spring Security のフィルターと連携して動作するため、ここで止めずに渡す必要があります。

このコードの動作
・リクエストの Authorization ヘッダーを取得
・JWT の正当性を検証 (validateToken())
・トークンから username を取得 (extractUsername())
・ユーザー情報を MyUserDetailsService から取得
・Spring Security に認証情報をセット (SecurityContextHolder)
・次のフィルターに処理を渡す (filterChain.doFilter())

SecurityConfig(セキュリティ設定)

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;

    public SecurityConfig(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService),
                             UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

UserDetailsService実装(今回は仮ユーザー)

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) {
        // 仮ユーザー名とパスワード
        return User.withUsername("testuser")
                .password("{noop}password") // {noop}はパスワードエンコーダー無効化
                .authorities("USER")
                .build();
    }
}

AuthControllerログインAPI

@RestController
public class AuthController {

    private final JwtUtil jwtUtil;

    public AuthController(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody Map<String, String> user) {
        if (user.get("username").equals("testuser") && user.get("password").equals("password")) {
            String token = jwtUtil.generateToken(user.get("username"));
            return ResponseEntity.ok(Collections.singletonMap("token", token));
        } else {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
    }
}

SampleController認証付きAPI
@RestController
public class SampleController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, Authenticated User!";
    }
}

実行の流れ
1. POST /login
{"username": "testuser", "password": "password"}
→ 成功すると token が返ってくる
2. GET /hello
Authorization: Bearer ヘッダー付けてアクセス
→ “Hello, Authenticated User!” が返る

1
0
2

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
1
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?