(転送+共有)JWTはユーザートークン検証/アヒルの血のファンを実現Javaオタク技術

JSON Web Token(JWT)は、現在最も人気のあるクロスドメイン認証ソリューションの1つです。本日、その謎を解き明かします。

1.物語の起源

JWTと言えば、従来のsession認証ベースのソリューションとボトルネックについて話しましょう

従来のsession対話プロセスは次のとおりです。

ブラウザがサーバにログイン要求を送信すると、認証が渡された後、ユーザー情報がseesionされ保存された中で、その後、サーバーは1を生成しますsessionIdそれを置くcookieにして、ブラウザにそれを返します。

ブラウザが要求を再送信すると、それがされますcookie置くリクエストヘッダにそれをsessionIdして、サーバーに要求データを送信します。

サーバーはseesionユーザー情報を再度取得でき、プロセス全体が完了します。

通常、サーバーはseesion30分間の非アクティブなど期間を設定し、ユーザーはのseesion削除からの情報を保存する必要があります。

  •  
session.setMaxInactiveInterval(30 * 60);//30分钟没活动,自动移除

一方、サーバーはseesion、現在のユーザーがログインしているかどうかを判断することもできます。空白の場合はログがないことを示し、ログインページに直接ジャンプします。空でない場合は、session取得した情報からユーザーは続行できます。

モノリシックアプリケーションでは、この種の相互作用は問題ありません。

ただし、アプリケーションサーバーの要求量が非常に大きくなり、1台のサーバーでサポートできる要求量が限られている場合は、この時点で要求を遅くしたり遅くしたりするのは簡単OOMです。

解決策は、単一のサーバーに構成を追加するか、新しいサーバーを追加して、負荷分散を通じてビジネスニーズを満たすことです。

単一のサーバーに構成を追加する場合、要求量は増加し続け、ビジネス処理をサポートできなくなります。

明らかに、新しいサーバーを追加すると、無制限の水平拡張を実現できます。

ただし、新しいサーバーを追加した後、異なるサーバー間の違いsessionIdA成功する場合がありますサーバーに正常にログインし、サーバーsessionからユーザー情報を取得できますが、Bサーバー上のsession情報見つかりません。これ間違いなく比類のないものです。厄介なことに、ログインを続行するにはログアウトする必要がありました。その結果、Aサーバーsessionはタイムアウトに失敗し、ログイン後に再度ログインを要求するためにログアウトを余儀なくされました。それについて考えるのは恥ずかしいことでした〜 〜

このような状況に直面して、いくつかの大物が一緒に話し合い、token計画を立てました。

インメモリデータベースに各アプリケーションを接続redisし、正常にログインしたユーザ情報に特定のアルゴリズムの暗号化を行う発生。ID呼ばれtokentokenユーザの情報が格納されredis、ユーザが開始要求が再び、ときにtokenそこであろうリクエストデータ1。そしてそれをサーバーに送信します。サーバーはtokenそれが存在するかどうかを検証します。存在するredis場合は検証に合格したことを意味します。存在しない場合はログインページにジャンプするようにブラウザに指示し、プロセスは終了します。

tokenこのソリューションは、サービスがステートレスであり、すべての情報が分散キャッシュに格納されることを保証します。分散ストレージに基づいて、水平方向に拡張して高い同時実行性をサポートできます。

もちろん、現在は共有スキームspringbootもあり、session同様のtokenスキームがsession格納さredisれます。クラスター環境でログインが実装されると、各サーバーはユーザー情報を取得できます。

第二に、JWTとは何ですか

上記では、session他のtokenソリューションについて説明しました。クラスター環境では、それらはすべてサードパーティのキャッシュデータベースredis依存してデータを共有します。

redis1回のログインでどこでも見られる効果を実現するために、ユーザー情報の共有を実現するためにデータベースをキャッシュする必要がないソリューションはありますか?

答えは間違いなくそこにあります。それが今日紹介するものですJWT

JWT完全JSON Web Tokenに、実装プロセスは、ユーザーが正常にログインした後、ユーザーの情報が暗号化され、生成さtokenれてクライアントに返されることを意味します。これは、従来のsession対話と大差ありません。

相互作用のプロセスは次のとおりです。

唯一の違いは、ということですtokenユーザーの基本情報を保存するために、より直感的なポイントが最初に配置されてredis置くために、ユーザデータにtoken行くに!

その結果、クライアント、サーバーtokenはユーザーの基本情報にアクセスできるようになります。これは、ブラウザーがユーザー情報を直接取得するため、クライアントは機密情報を保存しないようにすることができるためtokenです。

JWTは正確にはどのように見えますか?

JWTは3つの情報で構成されており、これら3つの情報テキストは.リンクされてJWT文字列を形成します。このような:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • 最初の部分:これをヘッダーと呼びます。これは、トークンの種類と暗号化プロトコルを格納するために使用されます。これらは通常、固定されています。

  • 2番目の部分:これをペイロードと呼び、ユーザーデータがそこに格納されます。

  • 3番目の部分:主にサーバーの検証に使用されるビザ(署名)です。

1、ヘッダー

JWTのヘッダーには、次の2つの情報が含まれています。

  • 宣言タイプ。これがJWTです。

  • 暗号化アルゴリズム、通常はHMACSHA256を直接宣言します。

完全なヘッダーは次のJSONのようになります。

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

base64暗号化の使用は最初の部分を構成します。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2、ペイロード

ペイロードは、有効な情報が格納される場所です。有効な情報は、次の3つの部分で構成されます。

  • 規格に登録されている宣言。

  • 公式声明

  • プライベートステートメント

その中で、規格に登録されているステートメント(推奨されますが必須ではありません)には、次の部分が含まれています 。

  • iss:jwt発行者;

  • sub:jwtが直面しているユーザー。

  • aud:jwtを受信するパーティ。

  • exp:jwtの有効期限。この有効期限は、発行時間よりも長くする必要があります。

  • nbf:jwtが使用できなくなるまでの時間を定義します。

  • iat:jwtの発行時間。

  • jwtの一意のIDは、主にリプレイ攻撃を回避するための1回限りのトークンとして使用されます。

パブリックステートメント部分:一般に、ユーザー関連情報やビジネスに必要なその他の必要な情報を追加して、任意の情報をパブリックステートメントに追加できますが、この部分はクライアントで復号化できるため、機密情報を追加することはお勧めしません。

プライベート宣言部分:プライベート宣言は、プロバイダーとコンシューマーが共同で定義する宣言です。機密情報base64は対称的に復号化されるため、一般に機密情報の保存はお勧めしません。つまり、情報のこの部分はプレーンテキスト情報として分類できます。

ペイロードを定義します。

{
   
     "sub": "1234567890",  "name": "John Doe",  "admin": true}

次に、base64暗号化してJwt2番目の部分を取得します

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

3、署名

jwtの3番目の部分はビザ情報であり、このビザ情報は3つの部分で構成されています。

  • ヘッダー(base64の後);

  • ペイロード(base64の後);

  • シークレット(キー);

この部分は、接続で構成される文字列を使用しbase64暗号化headerおよびbase64暗号化され、次にソルトコンビネーション暗号化で宣言された暗号化メソッドを介して暗号化され3番目の部分を構成する必要があります。payload.headersecretjwt

//javascriptvar encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);var signature = HMACSHA256(encodedString, '密钥');

暗号化後、signature署名情報が取得されます。

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

これらの3つの部分.を完全な文字列に接続して、最終的なjwtを形成します。

//jwt最终格式eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

これはjavascript実装による単なるデモンストレーションでありJWT、発行とキーの保存はすべてサーバー側で行われます。

secretjwt発行とjwt検証に使用されるため、どのシーンでも公開しないでください

3、実際の戦闘

私はたくさん紹介しました、それを達成する方法は?ナンセンスな話をしないで、真下から始めましょう!

  • springbootプロジェクトを作成し、JWT依存ライブラリを追加します

<!-- jwt支持 --><dependency>    <groupId>com.auth0</groupId>    <artifactId>java-jwt</artifactId>    <version>3.4.0</version></dependency>
  • 次に、ユーザー情報のクラスを作成し、によって暗号化されtoken保存されます

@Data@EqualsAndHashCode(callSuper = false)@Accessors(chain = true)public class UserToken implements Serializable {
   
   
    private static final long serialVersionUID = 1L;
    /**     * 用户ID     */    private String userId;    /**     * 用户登录账户     */    private String userNo;    /**     * 用户中文名     */    private String userName;}
  • 次に、作成および検証するJwtTokenUtilツールクラスを作成しますtokentoken

public class JwtTokenUtil {
   
   	//定义token返回头部    public static final String AUTH_HEADER_KEY = "Authorization";	//token前缀    public static final String TOKEN_PREFIX = "Bearer ";	//签名密钥    public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";		//有效期默认为 2hour    public static final Long EXPIRATION_TIME = 1000L*60*60*2;    /**     * 创建TOKEN     * @param content     * @return     */    public static String createToken(String content){
   
           return TOKEN_PREFIX + JWT.create()                .withSubject(content)                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))                .sign(Algorithm.HMAC512(KEY));    }    /**     * 验证token     * @param token     */    public static String verifyToken(String token) throws Exception {
   
           try {
   
               return JWT.require(Algorithm.HMAC512(KEY))                    .build()                    .verify(token.replace(TOKEN_PREFIX, ""))                    .getSubject();        } catch (TokenExpiredException e){
   
               throw new Exception("token已失效,请重新登录",e);        } catch (JWTVerificationException e) {
   
               throw new Exception("token验证失败!",e);        }    }}
  • 構成クラスを記述し、クロスドメインを許可し、アクセス許可インターセプターを作成します

@Slf4j@Configurationpublic class GlobalWebMvcConfig implements WebMvcConfigurer {
   
   	   /**     * 重写父类提供的跨域请求处理的接口     * @param registry     */    @Override    public void addCorsMappings(CorsRegistry registry) {
   
           // 添加映射路径        registry.addMapping("/**")                // 放行哪些原始域                .allowedOrigins("*")                // 是否发送Cookie信息                .allowCredentials(true)                // 放行哪些原始域(请求方式)                .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")                // 放行哪些原始域(头部信息)                .allowedHeaders("*")                // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)                .exposedHeaders("Server","Content-Length", "Authorization",
                               "Access-Token", 
                               "Access-Control-Allow-Origin",
                               "Access-Control-Allow-Credentials");    }
    /**     * 添加拦截器     * @param registry     */    @Override    public void addInterceptors(InterceptorRegistry registry) {
   
           //添加权限拦截器        registry.addInterceptor(
                 new AuthenticationInterceptor())
                .addPathPatterns("/**").excludePathPatterns("/static/**");    }}
  • AuthenticationInterceptorインターセプターを使用してインターフェイスパラメーターを確認します

@Slf4jpublic class AuthenticationInterceptor implements HandlerInterceptor {
   
   
    @Override    public boolean preHandle(
                   HttpServletRequest request, 
                   HttpServletResponse response, Object handler) throws Exception {
   
   		// 从http请求头中取出token        final String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);        //如果不是映射到方法,直接通过        if(!(handler instanceof HandlerMethod)){
   
               return true;        }        //如果是方法探测,直接通过        if (HttpMethod.OPTIONS.equals(request.getMethod())) {
   
               response.setStatus(HttpServletResponse.SC_OK);            return true;        }        //如果方法有JwtIgnore注解,直接通过        HandlerMethod handlerMethod = (HandlerMethod) handler;        Method method=handlerMethod.getMethod();        if (method.isAnnotationPresent(JwtIgnore.class)) {
   
               JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);            if(jwtIgnore.value()){
   
                   return true;            }        }        LocalAssert.isStringEmpty(token, "token为空,鉴权失败!");        //验证,并获取token内部信息        String userToken = JwtTokenUtil.verifyToken(token);				//将token放入本地缓存        WebContextUtil.setUserToken(userToken);        return true;    }    @Override    public void afterCompletion(
                         HttpServletRequest request, HttpServletResponse response, 
                        Object handler, Exception ex) throws Exception {
   
           //方法结束后,移除缓存的token        WebContextUtil.removeUserToken();    }}
  • 最後に、controllerレイヤーユーザーがログインした後、レイヤーを作成tokenしてヘッダーに保存します。

/** * 登录 * @param userDto * @return */@JwtIgnore@RequestMapping(value = "/login", method = RequestMethod.POST,
                produces = {"application/json;charset=UTF-8"})public UserVo login(@RequestBody UserDto userDto, HttpServletResponse response){
   
       //...参数合法性验证    //从数据库获取用户信息    User dbUser = userService.selectByUserNo(userDto.getUserNo);    //....用户、密码验证    //创建token,并将token放在响应头    UserToken userToken = new UserToken();    BeanUtils.copyProperties(dbUser,userToken);    String token = JwtTokenUtil.createToken(JSONObject.toJSONString(userToken));    response.setHeader(JwtTokenUtil.AUTH_HEADER_KEY, token);    //定义返回结果    UserVo result = new UserVo();    BeanUtils.copyProperties(dbUser,result);    return result;}

基本的にここで行われます!

その中AuthenticationInterceptorで使用されているのJwtIgnoreは、token検証コードの取得など、検証を必要しないメソッドに使用されるアノテーションです。@Target({ElementType.METHOD、ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)public @interface JwtIgnore {
   
       boolean value() default true;}

またWebContextUtil、スレッドキャッシュツール、他のインターフェイスは、この方法でtokenユーザー情報を取得できます。

​​​​​​

public class WebContextUtil {
   
       //本地线程缓存token    private static ThreadLocal<String> local = new ThreadLocal<>();    /**     * 设置token信息     * @param content     */    public static void setUserToken(String content){
   
           removeUserToken();        local.set(content);    }    /**     * 获取token信息     * @return     */    public static UserToken getUserToken(){
   
           if(local.get() != null){
   
               UserToken userToken = JSONObject.parseObject(local.get() , UserToken.class);            return userToken;        }        return null;    }    /**     * 移除token信息     * @return     */    public static void removeUserToken(){
   
           if(local.get() != null){
   
               local.remove();        }    }}

最後に、プロジェクトを開始し、postmanテストして、ヘッダーが結果を返すかどうかを確認しましょう

返された情報を抽出し、ブラウザbase64使用して最初の2つの部分を復号化します。

  • 最初の部分であるヘッダーの結果は次のとおりです。

  • ペイロードである2番目の部分では、結果は次のようになります。

ヘッダーとペイロードの情報をbase64復号化できることがはっきりとわかります。

したがって、token機密情報を保存しないように注意してください。

他のサービスインターフェースをリクエストする必要がある場合はリクエストヘッダーにパラメータをheaders追加するだけで済みAuthorizationます。

パーミッションインターセプターが検証された後、WebContextUtilユーザー情報は、インターフェイスメソッドのツールクラスを介してのみ取得できます。

//获取用户token信息UserToken userToken = WebContextUtil.getUserToken();

4、まとめ

JWT比較するとsessionプログラムは、理由はjson汎用性のために、それはJWTのように、クロス言語サポートすることができJAVAJavaScriptPHPおよび他の多くの言語を使用することができるが、sessionプログラムがのみJAVA

このpayload部分のために、JWT他のビジネスロジックに必要な機密性の低い情報を格納できます。

同時に、secret秘密鍵はデータを検証および復号化できるため、サーバーの秘密鍵を保護することは非常に重要です。

できればhttps契約書をご利用ください

5、参照

1.簡単な本-JWT-JSONWEBTOKENとは

2. BlogGarden-セッションとトークンに基づくID認証スキーム

                                     ------------------------------------------- <終了> ---- -----------------------------------

JWTを使用してシングルサインオンを実現する方法を教えてください

オリジナルのアヒルの血のファン Javaオタクテクノロジー 5月15日

https://mp.weixin.qq.com/s/3qm3RvSG_1LQ0v676XU7jA

私たちの記事が気に入ったら、転送してクリックすると、より多くの人に見てもらうことができます。また、テクノロジーを愛し、知識の惑星に参加することを学ぶ友人を歓迎します。私たちは一緒に成長し、進歩します。

おすすめ

転載: blog.csdn.net/qq_31653405/article/details/105659832