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?

JWTについて調べてみた

Last updated at Posted at 2024-09-05

はじめに

JWTって聞いたことあるけど、詳しく知らないと思い、調べてみることにしました。
基本的なことを調べ、SpringSecurityとauth0:java-jwtを使い実際に簡単な実装をしてみたいと思います。

目次

  1. JWTって何?
  2. どう認証するのか?
  3. 簡単に実装してみた
    1. 実装
    2. 検証
  4. まとめ
  5. 参考文献

JWTって何?

JWTとはJsonWebTokenの略称であり、以下のようなJsonをBase64エンコードなどでURLセーフにしたものです
あくまでJsonの表示形式を決めただけのもので、これ自体が認証するわけではないありません

{
  "iss": "jwt-demo-server",
  "sub": "1",
  "jti": "89ce044c-6fbe-4e99-b465-089e0d5c2aba",
  "iat": 1722736918,
  "exp": 1722740518
}

このJsonのキーの説明

キー名 説明
iss JWTの発行者
sub ユーザの識別子など
jti JWTの一意な識別子
iat JWTの発行日時
exp JWTの有効期限

どう認証するのか?

JWS(JsonWebSignature)というJWTから作る検証シグネチャによって認証を行います
JWSは以下のように作成されます
まずは暗号アルゴリズムとペイロード(JWT)の種別を表すヘッダーを用意します

{
  "alg": "HS256",
  "typ": "JWT"
}

次にURLエンコードしたヘッダーとJWTを暗号キーで暗号化したシグネチャを作成します

シグネチャ = 「{ヘッダと言われるJsonをURLエンコードしたもの} . {JWT}」を暗号化キーで暗号化

それぞれ作成したものを「.」で繋いだものがJWS

JWS = {ヘッダと言われるJsonをURLエンコードしたもの} . {JWT} . {シグネチャをURLエンコードしたもの}

簡単に実装してみた

実装

今回はauth0:java-jwtとSpring Securityを使って認証機能を作っていきます
以下認証までの流れです

1. /auth/login にemailとpasswordでリクエストを送る
2. LoginControllerでリクエストを受け取り、emailとpasswordで認証。ユーザIDを含んだJWSを作成し、返却する。
3. /hello のHeaderに発行したJWSをつけ、リクエストを送る
4. filterでJWSで認証する
5. "Hello, World!"が返される

build.gradleにライブラリを追加

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'com.auth0:java-jwt:4.4.0'

JwtUtilsを作成
ここでJWSの作成・認証を行います
本来なら暗号化キーはもっと厳重に管理した方が良いですが、今回は簡単な実装なためコードにベタ書しています。

JwtUtils
public class JwtUtils {
    private static final Long EXPIRATION_TIME = 1000L * 60L * 60L;
    private static final String SECRET_KEY = "secret";

    public static String build(Integer userId) {
        Date issuedAt = new Date();
        Date expiresAt = new Date(issuedAt.getTime() + EXPIRATION_TIME);

        Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);

        return JWT.create()
                .withIssuer("jwt-demo-server")
                .withSubject(userId.toString())
                .withJWTId(UUID.randomUUID().toString())
                .withIssuedAt(issuedAt)
                .withExpiresAt(expiresAt)
                .sign(algorithm);
    }

    public static DecodedJWT verify(String token) {
        Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
        JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("jwt-demo-server")
                .build();

        return verifier.verify(token);
    }
}

LoginControllerを作成
受け取ったemailとpasswordで認証し、ユーザIDを取得します。
そのユーザIDを利用し、JWSを作成します。

LoginController
@RestController
@RequestMapping("/auth")
public class LoginController {
    @Resource
    AuthService authService;

    @PostMapping("/login")
    public ResponseEntity login(@RequestBody LoginRequest request) {
        // emailとpasswordで認証し、ユーザID(1)を返す
        Integer userId = authService.auth(request.getEmail(), request.getPassword());

        String token = JwtUtils.build(userId);

        return ResponseEntity.ok(token);
    }
}

SecurityConfigを作成
今回はpostmanなどでAPIを直接叩いたので、csrfの設定を無効にしておくきます。

SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable);
        return http.build();
    }
}

AuthorizeFilterを作成
OncePerRequestFilterというリクエストごとに毎回呼び出すクラスを継承し作成します。
matcherに合致しないリクエストのみを認証対象とします。
JWSがない or 認証できなければ401が返されます。

AuthorizeFilter
@Component
public class AuthorizeFilter extends OncePerRequestFilter {

    private final AntPathRequestMatcher matcher = new AntPathRequestMatcher("/auth/login");

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (!matcher.matches(request)) {
            String authorizationHeader = request.getHeader("Authorization");

            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                String token = authorizationHeader.replace("Bearer ", "");

                try {
                    DecodedJWT decodedJWT = JwtUtils.verify(token);
                } catch (Exception e) {
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return;
                }
            } else {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
        }

        filterChain.doFilter(request, response);
    }
}

HelloControllerを作成

HelloContorller
@RestController
@RequestMapping("/hello")
public class HelloController {
    @GetMapping("")
    public String hello() {
        return "Hello, World!";
    }
}
検証

まずは/auth/login にリクエストを送り、JWSを取得します
Cursor_と_ログイン_-_My_Workspace.png

次に/hello へJWSをつけてリクエストを送ると、Hello, World!と返ってきます
return_hello_-_My_Workspace.png

JWSを持たせずにリクエストを送ると、401となります
return_hello_-_My_Workspace.png

まとめ

今回はJWTについて調べ、それを使って簡単に認証機能を作ってみました。
JWTとして送る情報によって、認可処理とかも入れることができるようなので、次は入れてみたいと思います。

参考文献

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?