目標
- マイクロサービスゲートウェイのシステム構築をマスターする Gateway
- ゲートウェイ電流制限の実装をマスターする
- BCrypt を使用してパスワードを暗号化および検証する機能
- 暗号化アルゴリズムについて学ぶ
- JWT を使用してマイクロサービス認証を実装できる
1. マイクロサービス ゲートウェイ ゲートウェイ
1.1 マイクロサービス ゲートウェイの概要
一般に、異なるマイクロサービスは異なるネットワーク アドレスを持ち、外部クライアントはビジネス要件を満たすために複数のサービス インターフェイスを呼び出す必要がある場合があります。クライアントが各マイクロサービスと直接通信する場合、次の問題が発生します。
- クライアントは異なるマイクロサービスを複数回リクエストするため、クライアントの複雑さが増大します。
- クロスドメインリクエストがあり、特定のシナリオでは処理が比較的複雑になります
- 認証は複雑であり、サービスごとに独立した認証が必要です
- リファクタリングは難しく、プロジェクトが反復されると、マイクロサービスの再分割が必要になる場合があります。たとえば、複数のサービスを 1 つに結合したり、1 つのサービスを複数に分割したりできます。クライアントがマイクロサービスと直接通信する場合、リファクタリングの実装は困難になります
- 一部のマイクロサービスはファイアウォールやブラウザに適さないプロトコルを使用する可能性があり、直接アクセスするのは困難です
上記の問題は、ゲートウェイの助けを借りて解決できます。
ゲートウェイはクライアントとサーバー間の中間層であり、すべての外部リクエストは最初にゲートウェイを通過します。つまり、API の実装はビジネス ロジックに重点を置き、セキュリティ、パフォーマンス、監視はゲートウェイによって実行できるため、ビジネスの柔軟性が向上するだけでなく、セキュリティも欠如しません。図:
利点は次のとおりです。
- セキュリティでは、ゲートウェイ システムのみが外部に公開され、マイクロサービスはイントラネット上に隠蔽され、ファイアウォールで保護されます。
- 監視が簡単。監視データはゲートウェイで収集され、分析のために外部システムにプッシュできます。
- 認証が簡単です。各マイクロサービスで認証する代わりに、バックエンド マイクロサービスにリクエストを転送する前にゲートウェイで認証を行うことができます。
- クライアントと個々のマイクロサービス間のやり取りの数が減少
- 簡単な統合認証。
概要: マイクロサービス ゲートウェイはシステムであり、マイクロサービス ゲートウェイ システムを公開することで、関連する認証、セキュリティ制御、統合ログ処理、および監視が容易な関連機能を実行するのに便利です。
マイクロサービス ゲートウェイを実装するためのテクノロジーは数多くありますが、
- nginx Nginx (エンジン x) は、高性能 HTTP およびリバース プロキシ Web サーバーであり、IMAP/POP3/SMTP サービスも提供します
- zuul 、Zuul は、Netflix によって作成された JVM ルーティングおよびサーバー側のロード バランサーです。
- spring-cloud-gateway は、Spring によって作成された Spring ベースのゲートウェイ プロジェクトであり、集積回路ブレーカー、パス書き換え、Zuul よりも優れたパフォーマンスを備えています。
Spring Cloudをベースとしたマイクロサービスの開発にシームレスに接続するために、ゲートウェイ技術を使用します。
ゲートウェイ公式サイト:
1.2 マイクロサービスゲートウェイのマイクロサービス構築
開発したシステムにはフロントエンドシステムとバックエンドシステムがあり、管理者はバックエンドシステムを利用します。次に、さまざまなマイクロサービスを呼び出す必要があるため、管理の背景としてゲートウェイ マイクロサービスを構築します。以下のように分析します。
ビルド手順:
1) changhou_gateway プロジェクトで、changhou_gateway_system プロジェクト pom.xml を作成します。
XML
コードをコピーする
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2) パッケージ com.changhou を作成し、ブートストラップ クラス GatewayApplication を作成します。
以下
コードをコピーする
@SpringBootApplication @EnableEurekaClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
3) リソースの下に application.yml を作成します
ヤムル
コードをコピーする
spring: application: name: sysgateway cloud: gateway: routes: - id: goods uri: lb://goods predicates: - Path=/goods/** filters: - StripPrefix= 1 - id: system uri: lb://system predicates: - Path=/system/** filters: - StripPrefix= 1 server: port: 9101 eureka: client: service-url: defaultZone: http://127.0.0.1:6868/eureka instance: prefer-ip-address: true
公式マニュアルを参照してください。
1.3 クロスドメイン マイクロサービス ゲートウェイ
application.ymlを変更し、spring.cloud.gatewayノードに設定を追加し、
ヤムル
コードをコピーする
globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE
最終的な構成ファイルは次のとおりです。
ヤムル
コードをコピーする
spring: application: name: sysgateway cloud: gateway: globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE routes: - id: goods uri: lb://goods predicates: - Path=/goods/** filters: - StripPrefix= 1 server: port: 9101 eureka: client: service-url: defaultZone: http://127.0.0.1:6868/eureka instance: prefer-ip-address: true
1.4 マイクロサービス ゲートウェイ フィルター
IP ブラックおよびホワイト リストのインターセプト、特定のアドレスのインターセプトなど、ゲートウェイ フィルターを通じていくつかの論理処理を実装できます。次のコードでは 2 つのフィルターが作成されており、設定の順序はフィルターと操作の効果を示すだけです。(具体的なロジック処理は一部の学生が実装します)
1) changgou_gateway_system は IpFilter を作成します
コトリン
コードをコピーする
@Component public class IpFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("经过第1个过滤器IpFilter"); ServerHttpRequest request = exchange.getRequest(); InetSocketAddress remoteAddress = request.getRemoteAddress(); System.out.println("ip:"+remoteAddress.getHostName()); return chain.filter(exchange); } @Override public int getOrder() { return 1; } }
2) changgou_gateway_system は URLFilter を作成します
コトリン
コードをコピーする
@Component public class UrlFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("经过第2个过滤器UrlFilter"); ServerHttpRequest request = exchange.getRequest(); String url = request.getURI().getPath(); System.out.println("url:"+url); return chain.filter(exchange); } @Override public int getOrder() { return 2; } }
テストして、コンソール出力を確認します。
2 ゲートウェイ電流制限
前に述べたように、ゲートウェイは電流制限などの多くのことを実行できますが、システムが頻繁にリクエストされるとシステムに負荷がかかる可能性があるため、この問題を解決するには、各マイクロサービスで電流制限操作を行う必要があります。ただし、ゲートウェイがある場合は、すべてのリクエストがマイクロサービスにルーティングされる前にゲートウェイ システムを通過する必要があるため、ゲートウェイ システムで電流制限を行うことができます。
2.1 思考分析
2.2 トークンバケットアルゴリズム
トークン バケット アルゴリズムは、より一般的な電流制限アルゴリズムの 1 つであり、大まかに次のように説明されます。
- すべてのリクエストは、処理する前に利用可能なトークンを取得する必要があります。
- 現在の制限サイズに従って、一定の割合でトークンをバケットに追加するように設定されます。
- バケットはトークン配置の最大制限を設定します。バケットがいっぱいになると、新しく追加されたトークンは破棄または拒否されます。
- リクエストが到着したら、最初にトークン バケット内のトークンを取得する必要があり、他のビジネス ロジックはそのトークンを使用してのみ実行できます。ビジネス ロジックの処理後、トークンは直接削除されます。
- トークン バケットには最小制限があり、バケット内のトークンが最小制限に達すると、十分なフロー制限を確保するために、リクエストの処理後にトークンは削除されません。
以下に示すように:
このアルゴリズムを実装するためのテクノロジは数多くありますが、Guava (発音: Java) もその 1 つであり、redis クライアントにもその実装があります。
2.3 ゲートウェイ電流制限コードの実装
要件: 各 IP アドレスは 1 秒以内に 1 つのリクエストのみを送信でき、余分なリクエストは 429 エラーを返します。
コード:
1) Spring Cloud Gateway は、デフォルトで実装する Redis の RateLimter 電流制限アルゴリズムを使用します。したがって、最初に Redis を導入する必要がある依存関係を使用する必要があります。
XML
コードをコピーする
<!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> <version>2.1.3.RELEASE</version> </dependency>
2) キーリゾルバーを定義する
次のコードを GatewayApplicationin ブートストラップ クラスに追加します。KeyResolver は、特定の種類の現在制限された KEY を計算するために使用されます。つまり、KeyResolver は、現在制限された Key を指定するために使用できます。
タイプスクリプト
コードをコピーする
//定义一个KeyResolver @Bean public KeyResolver ipKeyResolver() { return new KeyResolver() { @Override public Mono<String> resolve(ServerWebExchange exchange) { return Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); } }; }
3) application.yml の設定項目を変更し、トラフィックを制限する設定と REDIS の設定を指定します。変更後の最終的な設定は次のようになります。
ヤムル
コードをコピーする
spring: application: name: sysgateway cloud: gateway: globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE routes: - id: goods uri: lb://goods predicates: - Path=/goods/** filters: - StripPrefix= 1 - name: RequestRateLimiter #请求数限流 名字不能随便写 args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 1 - id: system uri: lb://system predicates: - Path=/system/** filters: - StripPrefix= 1 # 配置Redis 127.0.0.1可以省略配置 redis: host: 192.168.200.128 port: 6379 server: port: 9101 eureka: client: service-url: defaultZone: http://127.0.0.1:6868/eureka instance: prefer-ip-address: true
説明:
- burstCapacity: トークン バケットの合計容量。
- recruitRate: トークン バケットが満たされる 1 秒あたりの平均速度。
- key-resolver: スロットリングに使用されるキーのリゾルバーの Bean オブジェクトの名前。SpEL 式を使用して、#{@beanName} に従って Spring コンテナから Bean オブジェクトを取得します。
安定したレートのburstCapacityはrepplementRateとburstCapacityに同じ値を設定することで実現されます。BurstCapacity を RefreshRate よりも高く設定すると、一時的なバーストが許可されます。
この場合、2 つの連続したバーストによりリクエストがドロップされるため (HTTP 429 - リクエストが多すぎます)、レート リミッターにはバーストの間に一定の時間を与える必要があります (repleyRate に従って)。
key-resolver: 「#{@userKeyResolver}」は、SPEL 式を通じて使用する KeyResolver を指定するために使用されます。
上記のように設定します。
これは、1 つのリクエストが 1 秒以内に通過できることを意味し、トークン バケットの充填率も 1 秒あたり 1 つのトークンを追加することになります。
緊急事態では最大でも 1 秒あたり 1 つのリクエストのみが許可されますが、これはビジネスに応じて調整できます。
4) テスト
Redis の開始 -> 登録センターの開始 -> コモディティ マイクロサービスの開始 -> ゲートウェイ ゲートウェイの開始
ブラウザを開く
http://localhost:9101/goods/brand
高速リフレッシュでは、1 秒以内に複数のリクエストが送信されると、429 エラーが返されます。
3.BCryptパスワード暗号化
3.1 BCrypt クイック スタート
ユーザー モジュールでは、通常、ユーザー パスワードの保護は暗号化されます。通常、パスワードは暗号化してデータベースに保存されます。ユーザーがログインすると、入力されたパスワードは暗号化され、データベースに保存されている暗号文と比較され、ユーザーのパスワードが正しいかどうかが検証されます。
現在、MD5 と BCrypt の方が人気があります。相対的に言えば、BCrypt は MD5 よりも安全です。
BCrypt 公式ウェブサイト
www.mindrot.org/projects/jB…
1) 公式ウェブサイトからソースコードをダウンロードします
2) 新しいプロジェクトを作成し、ソース コード クラス BCrypt をプロジェクトにコピーします。
3) 新しいテストクラスを作成し、main メソッドにコードを記述し、パスワードの暗号化を実現します
これ
コードをコピーする
String gensalt = BCrypt.gensalt();//这个是盐 29个字符,随机生成 System.out.println(gensalt); String password = BCrypt.hashpw("123456", gensalt); //根据盐对密码进行加密 System.out.println(password);//加密后的字符串前29位就是盐
4) 新しいテスト クラスを作成し、メイン メソッドにパスワードを検証するコードを記述します。BCrypt は逆の操作をサポートせず、パスワードの検証のみをサポートします。
これ
コードをコピーする
boolean checkpw = BCrypt.checkpw("123456", "$2a$10$61ogZY7EXsMDWeVGQpDq3OBF1.phaUu7.xrwLyWFTOu8woE08zMIW"); System.out.println(checkpw);
3.2 管理者パスワード暗号化の追加
3.2.1 需要とテーブル構造の分析
新しい管理者を追加し、パスワード暗号化に BCrypt を使用します
3.2.2 コードの実装
1) BCrypt ソース コードを changgou_common プロジェクトの org.mindrot.jbcrypt パッケージにコピーします。
2) changgou_service_system プロジェクトの AdminServiceImpl を変更します。
スクス
コードをコピーする
/** * 增加 * @param admin */ @Override public void add(Admin admin){ String password = BCrypt.hashpw(admin.getPassword(), BCrypt.gensalt()); admin.setPassword(password); adminMapper.insert(admin); }
3.3 管理者ログインパスワードの確認
3.3.1 需要分析
システム管理ユーザーはバックグラウンドを管理する必要があり、管理バックグラウンドに入る前にユーザー名とパスワードを入力してログインする必要があります。
アイデア:
- ユーザーがリクエストを送信し、ユーザー名とパスワードを入力します
- バックグラウンド管理マイクロサービス コントローラーはパラメーターを受け取り、ユーザー名とパスワードが正しいかどうかを確認し、正しい場合はユーザーのログイン成功結果を返します。
3.3.2 コードの実装
1) AdminService の新しいメソッド定義
ジャワ
コードをコピーする
/** * 登录验证密码 * @param admin * @return */ boolean login(Admin admin);
2) AdminServiceImpl はこのメソッドを実装します
ジャワ
コードをコピーする
@Override public boolean login(Admin admin) { //根据登录名查询管理员 Admin admin1=new Admin(); admin1.setLoginName(admin.getLoginName()); admin1.setStatus("1"); Admin admin2 = adminMapper.selectOne(admin1);//数据库查询出的对象 if(admin2==null){ return false; }else{ //验证密码, Bcrypt为spring的包, 第一个参数为明文密码, 第二个参数为密文密码 return BCrypt.checkpw(admin.getPassword(),admin2.getPassword()); } }
3) AdminController の新しいメソッド
タイプスクリプト
コードをコピーする
/** * 登录 * @param admin * @return */ @PostMapping("/login") public Result login(@RequestBody Admin admin){ boolean login = adminService.login(admin); if(login){ return new Result(); }else{ return new Result(false,StatusCode.LOGINERROR,"用户名或密码错误"); } }
4. 暗号化アルゴリズム(理解する)
JWT の学習には多くの暗号化アルゴリズムの使用が含まれるため、ここである程度のリテラシーを身につけておけば、簡単に理解できます。
暗号化アルゴリズムの種類は次のとおりです。
4.1. 可逆暗号化アルゴリズム
説明: 暗号化後、暗号文を逆に復号して元の暗号文を取得できます。
4.1.1. 対称暗号化
[ファイルの暗号化と復号化には同じキーが使用されます。つまり、暗号化キーは復号化キーとしても使用できます]
説明:対称暗号化アルゴリズムでは、データ送信者は、平文と暗号鍵を特別な暗号化アルゴリズムで処理して、複雑な暗号文を作成して送信します。受信者が暗号文を受信した後、暗号文を解読したい場合は、暗号文を読み取れる平文に戻すには、暗号化に使用した鍵と、同じ暗号化アルゴリズムの逆アルゴリズムを使用して暗号文を復号する必要があります。対称暗号化アルゴリズムでは、1 つのキーのみが使用され、送信側と受信側の両方がこのキーを使用するため、復号化側は事前に暗号化キーを知っている必要があります。
利点:対称暗号化アルゴリズムの利点は、アルゴリズムがオープンであり、計算量が少なく、暗号化速度が速く、暗号化効率が高いことです。
欠点:非対称暗号化セキュリティがありません。
目的:通常、ユーザーの携帯電話番号や ID カードなど、機密ではあるが解読可能な情報を保存するために使用されます。
一般的な対称暗号化アルゴリズムは次のとおりです: AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256
4.1.2. 非対称暗号化
【2つの鍵:公開鍵(公開鍵)と秘密鍵、公開鍵暗号化、秘密鍵復号化】
説明:秘密鍵と公開鍵という 2 つの鍵が同時に生成されます。秘密鍵は秘密に保たれ、公開鍵は信頼できるクライアントに送信できます。
暗号化と復号化:
- 秘密キー暗号化。秘密キーまたは公開キーを保持しているだけで復号化できます。
- 公開キー暗号化、秘密キーを保持するだけで復号化可能
サイン:
秘密鍵署名。改ざんされていないかどうかを確認するための公開鍵を保持します。
- 利点:対称暗号化と比較して、非対称暗号化はセキュリティが優れています。
- 短所:非対称暗号化の短所は、暗号化と復号化に時間がかかり、速度が遅いため、少量のデータの暗号化にのみ適していることです。
- 目的:通常、署名と認証に使用されます。秘密キー サーバーは暗号化のためにそれを保存し、公開キー クライアントは復号化またはトークンや署名の検証のためにそれを保持します。
一般的な非対称暗号化アルゴリズムは次のとおりです: RSA、DSA (デジタル署名用)、ECC (モバイル デバイス用)、RS256 (SHA-256 を使用した RSA 署名)
4.2. 不可逆暗号化アルゴリズム
説明:一度暗号化すると、逆に復号して元のパスワードを取得することはできません。
タイプ:ハッシュ暗号化アルゴリズム、ハッシュ アルゴリズム、ダイジェスト アルゴリズムなど。
目的:通常、Web サイトでファイルをダウンロードするときに確認できる、ダウンロードされたファイルの正確性を検証するために使用され、パスワード、カード番号、その他の復号化できない情報などのユーザーの機密情報を保存します。
一般的な不可逆暗号化アルゴリズムは次のとおりです: MD5、SHA、HMAC
4.3. Base64 エンコーディング
Base64 は、インターネット上で 8 ビット バイト コードを送信するために使用される最も一般的なエンコード方式の 1 つです。Base64 エンコードを使用すると、HTTP 環境でより長い識別情報を送信できます。Base64Base64 のエンコードとデコードを使用すると、読み取り不可能になります。つまり、エンコードされたデータは人間の目で直接見ることができません。注: Base64 は単なるエンコード方式であり、暗号化方式ではありません。
オンラインコーディングツール:
5. JWT はマイクロサービス認証を実装します
JWT は通常、シングル サインオンを実装するために使用されます。シングル サインオン: たとえば、テンセントには笑、空飛ぶ車などの多くのゲームがありますが、QQ ゲーム バトル プラットフォームに一度ログインすると、これらの異なるプラットフォームに直接ログインできます。シングル・サインオン。JWTはシングルサインオンを実現する技術で、他にはoath2などがあります。
5.1 マイクロサービス認証とは
以前にゲートウェイを構築したことがありますが、ゲートウェイシステムでの権限検証にはゲートウェイを使用する方が適しています。
次に、JWT を使用して認証検証を実装します。
5.2 JWT
JSON Web Token (JWT) は非常に軽量な仕様です。この仕様により、JWT を使用してユーザーとサーバー間で安全で信頼できる情報を渡すことが可能になります。
JWT は実際には文字列であり、ヘッダー、ペイロード、署名の 3 つの部分で構成されます。
ヘッダ
ヘッダーは、JWT の種類や署名に使用されるアルゴリズムなど、JWT に関する最も基本的な情報を記述するために使用されます。これは JSON オブジェクトとして表すこともできます。
json
コードをコピーする
{"typ":"JWT","alg":"HS256"}
署名アルゴリズムはヘッダーで HS256 アルゴリズムとして指定されます。BASE64 エンコードを実行します。base64.xpcha.com/、エンコードされた文字列は次のとおりです。
コードをコピーする
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
豆知識: Base64 は、バイナリ データを表現するための 64 個の印刷可能な文字に基づく表現方法です。2 の 6 乗は 64 に等しいため、各 6 ビットが単位となり、特定の印刷可能な文字に対応します。3 バイトは 24 ビットで、4 Base64 ユニットに対応します。つまり、3 バイトは 4 つの印刷可能文字で表す必要があります。
JDK は非常に便利な BASE64Encoder と BASE64Decoder を提供しており、これを使用すると BASE64 ベースのエンコードとデコードを非常に簡単に完了できます。
ロード (プレイロード)
ペイロードは、有効な情報が保存される場所です。この名前は、飛行機で運ばれる商品を特に指すようです。これらの有効な情報は 3 つの部分で構成されています
1) 標準に登録されているステートメント (推奨されますが、必須ではありません)
- iss: jwt 発行者
- sub: jwt の対象となるユーザー
- aud: jwt を受信する側
- exp: jwt の有効期限。発行時間より長くなければなりません。
- nbf: 何時より前に jwt を利用できないかを定義します。
- iat: jwtの発行時刻
- jti: jwt の一意の ID。主にワンタイム トークンとして使用されます。
2) 公的声明
公開ステートメントには任意の情報を追加できますが、通常はユーザー関連情報やビジネス ニーズに必要なその他の情報が追加されますが、この部分はクライアント側で復号化される可能性があるため、機密情報を追加することはお勧めできません。
3) 個人的な声明
プライベート ステートメントは、プロバイダーとコンシューマによって共同で定義されたステートメントです。base64 は対称的に復号化され、情報のこの部分が平文情報として分類される可能性があるため、機密情報を保存することは通常推奨されません。
これはカスタム クレームを指します。たとえば、前の構造例の admin と name は自己定義クレームに属します。これらのクレームと JWT 標準で規定されているクレームの違いは次のとおりです。
JWT によって規定されているクレームについては、JWT の受信者は、JWT を取得した後にこれらの標準クレームを検証する方法を知っています (検証できるかどうかはわかりません)。プライベート クレームは、受信者にこれらを検証するように明示的に指示されない限り検証されません。クレーム 検証とルールが必要です。
ペイロードを定義します。
json
コードをコピーする
{"sub":"1234567890","name":"John Doe","admin":true}
次に、base64 で暗号化して、Jwt の 2 番目の部分を取得します。
コードをコピーする
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
ビザ(署名)
jwt の 3 番目の部分はビザ情報であり、次の 3 つの部分で構成されます。
- ヘッダー (base64 以降)
- ペイロード (base64 以降)
- ひみつ
この部分では、base64 で暗号化されたヘッダーと Base64 で暗号化されたペイロードを使用する必要があります。連結によって形成された文字列は、ヘッダーで宣言された暗号化メソッドを通じてソルト付きシークレットの組み合わせで暗号化され、jwt の 3 番目の部分を構成します。
コードをコピーする
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
これら 3 つの部分を . で完全な文字列に連結して、最終的な jwt を形成します。
コードをコピーする
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注: シークレットはサーバー側に保存され、jwt 署名の生成もサーバー側で行われます。シークレットは、jwt の発行と jwt の検証に使用されます。したがって、これはサーバーの秘密キーであり、公開されるべきではありません。どのようなシナリオでも。クライアントがシークレットを知ってしまえば、クライアントは自ら jwt を発行できることになります。
5.3 JJWTの発行と検証トークン
JJWT は、エンドツーエンドの JWT の作成と検証を提供する Java ライブラリです。JJWT は永久に無料でオープン ソース (Apache ライセンス、バージョン 2.0) であり、使いやすく、理解しやすいです。これは、複雑さのほとんどを隠した、流動的でアーキテクチャに重点を置いたインターフェイスになるように設計されています。
公式ドキュメント:
5.3.1 トークンの作成
1) 新しいプロジェクトの pom.xml に依存関係を追加します。
XML
コードをコピーする
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
-
テストクラスを作成します。コードは次のとおりです
JwtBuilder builder= Jwts.builder() .setId("888") //一意の番号を設定します。setSubject("Xiaobai")//件名を JSON に設定できます data.setIssuedAt(new Date())//発行日を設定します。 (SignatureAlgorithm.HS256,"itcast");//HS256 アルゴリズムを使用するように署名を設定し、SecretKey(string) を設定します。 //文字列を構築して返します。 System.out.println( builder.compact() );
印刷結果を実行します。
コードをコピーする
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDQxODF9.ThecMfgYjtoys3JX7dpx3hu6pUm0piZ0tXXreFU_u3Y
もう一度実行すると、負荷には時間が含まれているため、実行ごとに結果が異なることがわかります。
5.3.2 トークンの分析
トークンを作成しました。Web アプリケーションでは、この操作はサーバーによって実行され、クライアントに送信されます。クライアントは、次回サーバーにリクエストを送信するときにこのトークンを運ぶ必要があります (これはチケットを保持するのと似ています) ) の場合、サーバーはトークンを受信した後にトークン内の情報 (ユーザー ID など) を解析し、その情報に従ってデータベースにクエリを実行して、対応する結果を返す必要があります。
これ
コードをコピーする
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDQxODF9.ThecMfgYjtoys3JX7dpx3hu6pUm0piZ0tXXreFU_u3Y"; Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJwt).getBody(); System.out.println(claims);
印刷効果を実行します。
これ
コードをコピーする
{jti=888, sub=小白, iat=1557904181}
トークンまたは署名キーを改ざんしようとすると、実行時にエラーが報告されることがわかります。そのため、トークンを解析することはトークンを検証することを意味します。
5.3.3 有効期限の設定
多くの場合、発行されたトークンを永続的なものにしたくないため、トークンに有効期限を追加できます。
1) トークンを作成し、有効期限を設定する
スクス
コードをコピーする
//当前时间 long currentTimeMillis = System.currentTimeMillis(); Date date = new Date(currentTimeMillis); JwtBuilder builder= Jwts.builder() .setId("888") //设置唯一编号 .setSubject("小白")//设置主题 可以是JSON数据 .setIssuedAt(new Date())//设置签发日期 .setExpiration(date) .signWith(SignatureAlgorithm.HS256,"itcast");//设置签名 使用HS256算法,并设置SecretKey(字符串) //构建 并返回一个字符串 System.out.println( builder.compact() );
説明:
スクス
コードをコピーする
.setExpiration(date)//用于设置过期时间 ,参数为Date类型数据
実行すると、印刷効果は次のようになります。
コードをコピーする
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI
2) トークンの解析
これ
コードをコピーする
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI"; Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJwt).getBody(); System.out.println(claims);
印刷効果:
現在の時間が有効期限を超えると、エラーが報告されます。
5.3.4 カスタムクレーム
前の例では ID と件名の情報のみが保存されていましたが、さらに多くの情報 (ロールなど) を保存したい場合は、カスタム クレームを定義できます。
テストクラスを作成し、テストメソッドを設定します。
トークンを作成します。
タイプスクリプト
コードをコピーする
@Test public void createJWT(){ //当前时间 long currentTimeMillis = System.currentTimeMillis(); currentTimeMillis+=1000000L; Date date = new Date(currentTimeMillis); JwtBuilder builder= Jwts.builder() .setId("888") //设置唯一编号 .setSubject("小白")//设置主题 可以是JSON数据 .setIssuedAt(new Date())//设置签发日期 .setExpiration(date)//设置过期时间 .claim("roles","admin")//设置角色 .signWith(SignatureAlgorithm.HS256,"itcast");//设置签名 使用HS256算法,并设置SecretKey(字符串) //构建 并返回一个字符串 System.out.println( builder.compact() ); }
印刷効果を実行します。
コードをコピーする
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDU4MDIsImV4cCI6MTU1NzkwNjgwMiwicm9sZXMiOiJhZG1pbiJ9.AS5Y2fNCwUzQQxXh_QQWMpaB75YqfuK-2P7VZiCXEJI
トークンを解析中:
これ
コードをコピーする
//解析 @Test public void parseJWT(){ String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDU4MDIsImV4cCI6MTU1NzkwNjgwMiwicm9sZXMiOiJhZG1pbiJ9.AS5Y2fNCwUzQQxXh_QQWMpaB75YqfuK-2P7VZiCXEJI"; Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJwt).getBody(); System.out.println(claims); }
実行結果:
5.4 Changgou マイクロサービス認証コードの実装 5.4.1 アイデア分析
- ユーザーがゲートウェイに入ってログインを開始すると、ゲートウェイフィルターがログインかどうかを判断し、ログインのためにバックグラウンド管理マイクロサービスにルーティングされます。
- ユーザーがログインに成功すると、バックグラウンド管理マイクロサービスが JWT TOKEN 情報を発行してユーザーに返します。
- ユーザーは再びゲートウェイに入ってアクセスを開始し、ゲートウェイフィルターはユーザーが保持する TOKEN を受け取ります。
- ゲートウェイ フィルターはトークンを解析してアクセス許可があるかどうかを判断し、アクセス許可がある場合は解放され、そうでない場合は未認証エラーを返します。
5.4.2 システムマイクロサービスによるトークンの発行
1) changgou_service_system にクラスを作成します: JwtUtil
ジャワ
コードをコピーする
package com.changgou.system.util; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; /** * JWT工具类 */ public class JwtUtil { //有效期为 public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时 //设置秘钥明文 public static final String JWT_KEY = "itcast"; /** * 创建token * @param id * @param subject * @param ttlMillis * @return */ public static String createJWT(String id, String subject, Long ttlMillis) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if(ttlMillis==null){ ttlMillis=JwtUtil.JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); SecretKey secretKey = generalKey(); JwtBuilder builder = Jwts.builder() .setId(id) //唯一的ID .setSubject(subject) // 主题 可以是JSON数据 .setIssuer("admin") // 签发者 .setIssuedAt(now) // 签发时间 .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥 .setExpiration(expDate);// 设置过期时间 return builder.compact(); } /** * 生成加密后的秘钥 secretKey * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } }
2) AdminControllerのログイン方法を変更し、ログイン成功時にTOKENを発行する
タイプスクリプト
コードをコピーする
/** * 登录 * @param admin * @return */ @PostMapping("/login") public Result login(@RequestBody Admin admin){ boolean login = adminService.login(admin); if(login){ //如果验证成功 Map<String,String> info = new HashMap<>(); info.put("username",admin.getLoginName()); String token = JwtUtil.createJWT(UUID.randomUUID().toString(), admin.getLoginName(), null); info.put("token",token); return new Result(true, StatusCode.OK,"登录成功",info); }else{ return new Result(false,StatusCode.LOGINERROR,"用户名或密码错误"); } }
郵便配達員テストを使用する
5.4.3 ゲートウェイフィルター検証トークン
1) changgou_gateway_system ゲートウェイ システムに依存関係を追加します。
XML
コードをコピーする
<!--鉴权--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
2) JWTUtil クラスを作成します。
ジャワ
コードをコピーする
package com.changgou.gateway.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; /** * jwt校验工具类 */ public class JwtUtil { //有效期为 public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时 //设置秘钥明文 public static final String JWT_KEY = "itcast"; /** * 生成加密后的秘钥 secretKey * * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 解析 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); } }
3) トークン検証用のフィルターを作成する
ジャワ
コードをコピーする
/** * 鉴权过滤器 验证token */ @Component public class AuthorizeFilter implements GlobalFilter, Ordered { private static final String AUTHORIZE_TOKEN = "token"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1. 获取请求 ServerHttpRequest request = exchange.getRequest(); //2. 则获取响应 ServerHttpResponse response = exchange.getResponse(); //3. 如果是登录请求则放行 if (request.getURI().getPath().contains("/admin/login")) { return chain.filter(exchange); } //4. 获取请求头 HttpHeaders headers = request.getHeaders(); //5. 请求头中获取令牌 String token = headers.getFirst(AUTHORIZE_TOKEN); //6. 判断请求头中是否有令牌 if (StringUtils.isEmpty(token)) { //7. 响应中放入返回的状态吗, 没有权限访问 response.setStatusCode(HttpStatus.UNAUTHORIZED); //8. 返回 return response.setComplete(); } //9. 如果请求头中有令牌则解析令牌 try { JwtUtil.parseJWT(token); } catch (Exception e) { e.printStackTrace(); //10. 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现 response.setStatusCode(HttpStatus.UNAUTHORIZED); //11. 返回 return response.setComplete(); } //12. 放行 return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
4) テスト:
注: データベースの管理者アカウントは admin、パスワードは 123456 です。
トークン直接アクセスを実行していない場合は、401 エラーが返されます。
正しいトークンが保持されている場合、クエリ結果が返されます。