【 JWTを使ったspringsqurity】

今回はめちゃシンプルに「ユーザー名とパスワードでログイン → 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) は、適切な長さの秘密鍵を自動生成する

2. 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() → トークンを 最終的な文字列形式 に変換

3. 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

2. コンストラクタ (@Autowired)
```
@Autowired
public JwtAuthenticationFilter(JwtUtil jwtUtil, MyUserDetailsService myUserDetailsService) {
    this.jwtUtil = jwtUtil;
    this.myUserDetailsService = myUserDetailsService;
}
```
依存関係の注入 (JwtUtil と MyUserDetailsService) を Spring に任せることで、適切に管理できるようになってる。

3. JWT 認証のフィルタ処理 (doFilterInternal)
```
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {3. 
```
このメソッドは、すべてのリクエストに適用されるフィルター処理 を実装する部分です。

4. Authorization ヘッダーからトークンを取得
```
String token = request.getHeader("Authorization");
```
リクエストの Authorization ヘッダーから JWT を取得します。 通常、ヘッダーの値は "Bearer <TOKEN>" の形式になっているので、次の処理で "Bearer " を取り除きます。
```
if (token != null && jwtUtil.validateToken(token.replace("Bearer ", ""))) {
```
・token != null → トークンが 存在するか チェック
・jwtUtil.validateToken() → JWT の検証（有効なトークンかどうか確認）
・.replace("Bearer ", "") → Bearer の接頭辞を削除

5. ユーザー名をトークンから取得
```
String username = jwtUtil.extractUsername(token.replace("Bearer ", ""));
```
jwtUtil.extractUsername() を使い、トークンからユーザー名を取得。


6. ユーザー名をトークンから取得
jwtUtil.extractUsername() を使い、トークンからユーザー名を取得。
```
UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);
```

7. UsernamePasswordAuthenticationToken を作成
```
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
        userDetails, null, userDetails.getAuthorities());
```
userDetails → ユーザー情報
null → 認証時にパスワードを不要にするため
userDetails.getAuthorities() → ユーザーの権限情報を付与


8. Spring Security に認証情報をセット
```
SecurityContextHolder.getContext().setAuthentication(authToken);
```
この処理によって、Spring Security はユーザーが 認証済み であることを認識します。

9. フィルターの継続
```
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 <token> ヘッダー付けてアクセス
→ “Hello, Authenticated User!” が返る

⸻