JWTの導入と利用

1. JWT はステートレスな Web サービスを実装します

1. ステートフルとは

ステートフル サービス、つまり、サーバーは、クライアントの ID を識別し、ユーザーの ID に基づいてリクエストを処理するために、各セッションのクライアント情報を記録する必要があります。典型的な設計は、Tomcat のセッションなどです。

たとえば、ログイン: ユーザーがログインした後、ログイン情報をサーバー セッションに保存し、対応するセッションを記録するための Cookie 値をユーザーに与えます。次のリクエストで、ユーザーが Cookie 値を持ってくると、対応するセッションを識別してユーザーの情報を見つけることができます。

デメリットは何ですか?

  • サーバーは大量のデータを保存するため、サーバーへの負荷が増大します。
  • サーバーはユーザーのステータスを保存し、水平方向に拡張することはできません。
  • クライアントのリクエストはサーバーに依存し、複数のリクエストは同じサーバーにアクセスする必要があります。

2. ステートレスとは何ですか?

サーバーはクライアントのステータス情報、つまり次の情報を記録する必要はありません。

  • サーバーはクライアントのリクエスタ情報を保存しません
  • クライアントからの各リクエストには、クライアントの ID を識別できる自己記述情報が必要です。

メリットは何ですか?

  • クライアントリクエストはサーバー情報に依存せず、複数のリクエストが同じサービスにアクセスする必要はありません。
  • サーバーのクラスターとステータスはクライアントに対して透過的です
  • サーバーは任意に移行および拡張可能
  • サーバーストレージの負荷を軽減

3. 無国籍を実現する方法

ステートレス ログイン プロセス:

  • クライアントが初めてサービスを要求するとき、サーバーはユーザーの情報を認証します(ログイン)
  • 認証に合格すると、ユーザー情報が暗号化されてトークンが形成され、ログイン資格情報としてクライアントに返されます。
  • 後続のリクエストごとに、クライアントは認証トークンを保持します。
  • サービスはトークンを復号化し、それが有効かどうかを判断します。

フローチャート:

クライアントはログインを要求し、ログイン後に資格情報が発行されます。

画像の説明を追加してください

ログインプロセス全体の中で最も重要なポイントは何ですか?

トークンのセキュリティ

トークンはクライアントを識別する唯一の識別子であり、暗号化が不十分で偽造されたら終わりです。

安全で信頼できる暗号化方式は何ですか?

JWT + RSA 非対称暗号化を使用します。

4. JWT の概要

JWTの正式名称はJson Web Tokenで、ステートレスかつ分散型のWebアプリケーション認可を実現できるJSON形式の軽量認可・本人認証仕様です、公式サイト:https://jwt.io

JWT にはデータの 3 つの部分が含まれています。

  • ヘッダー: ヘッダー。通常、ヘッダーには 2 つの情報部分があります。

    • 宣言タイプ。これは JWT の自己記述情報です

    ヘッダーをbase64エンコードして、データベースの最初の部分をbase64でエンコードおよびデコードします。

  • ペイロード: ペイロードは有効なデータであり、通常は次の情報が含まれます。

    • ユーザー ID 情報 (ここでは Base64 エンコードが使用されているため、デコードは元に戻すことができるため、機密情報は保存しないでください)
    • 登録ステートメント: トークンの発行時刻、有効期限、発行者など。コンテンツのこの部分は ID カードの情報に似ています。

    この部分も、データの 2 番目の部分を取得するために、base64 を使用してエンコードされます。

  • 署名: 署名はデータ全体の認証情報です。一般に、最初の 2 つのステップのデータと秘密 (漏洩しないように、定期的に変更するのが最善です) に基づいて、暗号化アルゴリズム (不可逆的) によって署名が生成されます。データ全体の完全性と信頼性を検証するために使用されます。

生成されるデータ形式:

画像の説明を追加してください

3 つのセグメントに分割されていることがわかります。各セグメントは上記のデータの一部です。

5. JWT対話プロセス

ステップ翻訳:

  • 1. ユーザーログイン
  • 2.サービス認証、通過後にjwtが生成される
  • 3. 生成された jwt をブラウザに返します
  • 4. ユーザーはリクエストするたびに jwt を実行します
  • 5. サーバーは公開鍵を使用して JWT 署名を解釈し、署名が有効であると判断した後、ペイロードからユーザー情報を取得します。
  • 6. リクエストを処理し、レスポンス結果を返す

2. nimbus-jose-jwt ライブラリ

1. 依存関係を入力します

nimbus-jose-jwt、jose4j、および java-jwt は、Java で JWT を操作するためのいくつかの一般的なライブラリです。

nimbus-jose-jwt 公式ウェブサイト: https://connect2id.com/products/nimbus-jose-jwt

必要な座標

    <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>9.11.1</version>
    </dependency>

2.コアAPI

2.1. 暗号化プロセス
  • nimbus-jose-jwt では、JWT の先頭を表すために Header クラスが使用されますが、Header クラスは抽象クラスであるため、そのサブクラスJWSHeaderを使用します。

    ヘッダー オブジェクトを作成します。

     @Test
     public void createToken(){
          
          
         //创建头部对象
         JWSHeader jwsHeader =
                 new JWSHeader.Builder(JWSAlgorithm.HS256) // 加密算法
                         .type(JOSEObjectType.JWT) // 静态常量
                         .build();
         System.out.println(jwsHeader);
     }
    

    .toBase64URL()次のメソッドを通じて、Base64 形式のヘッダー情報 (JWT 内の実際のヘッダー情報でもあります) を取得できます

  • Payloadクラスを使用してJWT のペイロード部分を表現します。

    ロードセクションオブジェクトを作成します。

        @Test
        public void createToken(){
          
          
            //创建头部对象
            JWSHeader jwsHeader =
                    new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                            .type(JOSEObjectType.JWT) // 静态常量
                            .build();
            System.out.println(jwsHeader);
    
            //创建载荷
            Payload payload = new Payload("hello world");
            System.out.println(payload);
        }
    

    .toBase64URL()次のメソッドを通じて、Base64 形式のペイロード情報を取得できます (これは、JWT 内の実際のペイロード情報でもあります)

  • 署名部分

    署名部分には特別なクラス表現はなく、自分で作成するのではなく、によって头部 + 荷载部 + 加密算法計算されます。

    nimbus-jose-jwt は、署名プロセスに参加するための署名者JWSSigner を特別に提供します。キーは署名者の作成時に指定されます。

JWSSigner jwsSigner = new MACSigner("Key"); //MACSigner() でキーを指定する必要があります


最终,整个 JWT 由一个 **JWSObject** 对象表示:

```java
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
// 进行签名(根据前两部分生成第三部分)
jwsObject.sign(jwsSigner);

最終的に必要なのはオブジェクトではなく JWT 文字列です。ここで、.serialize()JWT を表す JWSObject オブジェクトのメソッドを呼び出すことができます。

String token = jwsObject.serialize();

完全な例:

  @Test
    public void createToken() throws JOSEException {
    
    

        //创建头部对象
        JWSHeader jwsHeader =
                new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                        .type(JOSEObjectType.JWT) // 静态常量
                        .build();
        //创建载荷
        Payload payload = new Payload("hello world");

        //创建签名器
        JWSSigner jwsSigner = new MACSigner("woniu");//woniu为密钥
        //创建签名
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷
        jwsObject.sign(jwsSigner);//再+签名部分

        //生成token字符串
        String token = jwsObject.serialize();
        System.out.println(token);
    }

例外が発生した場合: com.nimbusds.jose.KeyLengthException: 秘密の長さは少なくとも 256 ビットである必要があります。キーの長さが十分でないためです。キーの長さを増やしてください。

2.2. 復号化

逆復号化および検証プロセス用のコア API は 2 つだけです。JWSObject の静的メソッドの解析メソッドと、その JWSVerifier オブジェクトの検証です。

JWSObject オブジェクトの有効性を直接検証したい場合は、JWSVerifierオブジェクトを作成する必要があります。

//创建验证器
JWSVerifier jwsVerifier = new MACVerifier("密钥");//密钥要和加密时的相同

次に、 jwsObject オブジェクトのverifyメソッドを直接呼び出します。

if (!jwsObject.verify(jwsVerifier)) {
    
    
    throw new RuntimeException("token 签名不合法!");
}

3. トークンの更新

実際の開発では、トークンが常に有効であるとは限りません。たとえば、30 分以内に何も操作しないと認証が切れて、再度ログインする必要があります。アクセスを要求していた場合、トークンは最後まで有効です。次の訪問までに 30 秒以上の時間があり、時間が 30 分を超えると、認定は期限切れになります。

springsecurity は JWT を統合します。


@Component
public class JWTfilter extends OncePerRequestFilter {
    
    

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired(required = false)
    private SecurityLoginService securityLoginService;

    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain)
            throws ServletException, IOException {
    
    

        //功能点1:在请求头拿到jwt
        String jwt = httpServletRequest.getHeader("jwt");
        if (jwt == null) {
    
    
            //放给security 其他过滤器,该方法不做处理
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        // 功能点2:jwt不合法
        if (!JWTUtil.decode(jwt)) {
    
    
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        //功能点3 获取jwt的用户信息
        Map payLoad = JWTUtil.getPayload(jwt);
        String username = (String) payLoad.get("username");

        //拿到redis的jwt
        String redisJWT = redisTemplate.opsForValue().get("jwt:" + username);

        //判断redis是否有该jwt
        if (redisJWT == null) {
    
    
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        if (!jwt.equals(redisJWT)) {
    
    
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        //给redis 的jwt续期
        redisTemplate.opsForValue().set("jwt:" + username, jwt, 30,
                TimeUnit.MINUTES);

        //获取用户名,密码,权限
        UserDetails userDetails = securityLoginService.loadUserByUsername(username);

        // 获取用户信息 生成security容器凭证
        UsernamePasswordAuthenticationToken upa =
                new UsernamePasswordAuthenticationToken(userDetails.getUsername()
                        , userDetails.getPassword(), userDetails.getAuthorities());

        //放入凭证
        SecurityContextHolder.getContext().setAuthentication(upa);

        // 本方法共功能执行完了,交给下一个过滤器
        filterChain.doFilter(httpServletRequest, httpServletResponse);

    }
}
   //前后端项目中要禁用掉session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
在securityConfig 类注入
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);

おすすめ

転載: blog.csdn.net/lanlan112233/article/details/129762966