最近では、パフォーマンスに関する懸念がますます高まっており、同時に、システムが高速かつ確実に通信できる方法を知りたいと考えています。多くの場合、情報を送信し、可能な限り機密性と安全性を維持したいと考えています。機密データは、Web を介して公開され、ネットワークの反対側でアクションをトリガーする必要もあります。ほとんどの場合、データの変更を引き起こすアクションを生成したいと考えます。これらの場合、データの保護だけを検討しているわけではありません。データの送信によってトリガーされるアクションが信頼できるものであることを確認する必要があります。データを保護する方法はいくつかあります。最も一般的な方法は、 TLS
(トランスポート層セキュリティ) の安全な接続を介してデータを送信します。これにより、データがネットワーク経由で暗号化されることが保証されます。証明書を使用して、2 者間の信頼関係を構築し、これを実現します。この記事では、 JWT
標準について説明し、さらに、 JWT
一般的なEnterprise
アプリケーションに統合する方法を説明します。この場合、 KumuluzEE
について説明します。いくつかの基本的な概念を見てみましょう。 JWT
または JSON Web Token、または JavaScript Object Notation Web Token は、 RFC7519で定義されている標準です。この標準は、すべてのRFC
(Request For Comments) 標準と同様に、 IETF
(Internet Engineering Task Force) によって定義、作成、公開されています。定義方法は複数あります。一般的に、 JWT
2 者間でクレームを送信するためのコンパクトで安全な形式であると言えます。クレームを簡素化する 1 つの方法は、基本的に、情報を含む名前と値のペアとして説明することです。この情報は、インターネット通信のいくつかの重要な側面を保証するために必要です。まず、受信した情報が検証され、信頼されていることを確認する必要があります。次に、それを検証する必要があります。基本的にはこれだけです。この標準を実装するには、Java エンタープライズ アプリケーションの実装に役立ついくつかのフレームワークを使用できます。Spring Boot は広く使用されています。多くの場合、銀行やその他の金融機関などの特定の組織の独自のソフトウェアで別の名前でラップされています。この例では、何か違うことをすることにしました。 Spring Boot の代わりに、 KumuluzEE
の例を見てみましょう。ポイントは、 JWT
が何であるか、そしてそれがどのように見えるかを正確に特定することです。 Java エンタープライズ アプリケーションは基本的に、アプリケーション サーバーにデプロイすることも、組み込みサーバーを使用して単独で実行することもできるアプリケーションです。 たとえば、Spring Boot アプリケーションは組み込みの Tomcat サーバーで実行されます。 この記事では、 KumuluzEE
に焦点を当てます。 Spring Boot と同様に、 KumuluzEE にも組み込みサーバーが含まれています。 ただし、この場合は Jetty と呼ばれます。 これは、CDI (コンテキスト依存性注入) を提供するために Weld と組み合わせて使用されます。 すべての Java EE
およびJakarta EE
テクノロジ標準は、このframework
と互換性があります。
JWT
基本的な形でどのように機能するかを説明するために、私はそれを提示する方法を考えなければなりませんでした。セキュリティが懸念される典型的な例は銀行です。しかし、 JWT
どのように機能するかを示すために銀行アプリケーション全体を作成するのは時間の無駄であり、おそらくあまりにも多くの概念が関係するでしょう。代わりに、私が作ったのは非常にシンプルな銀行システムです。私たちの主な関心事は、データがどのようにネットワークを流れるか、そしてユーザーがアプリケーションの特定の領域にどのようにアクセスするかを示すことです。また、TLS や暗号化された情報をネットワーク経由で送信する方法についても説明しません。最も純粋な形のJWT
に焦点を当てます。私たちのケースは、自然と環境を保護するグループが使用する銀行システムです。これは、 JWT
どのように機能するかを示す楽しい方法です。この自然連盟の主人公は、私のすべての記事で共通のキャラクターになりつつあるルーシーです。
始める前に、実行中のアプリケーションをスケッチしてみましょう。非常に単純なアプリケーションですが、それでも描いておくとよいでしょう。
これがとても簡単なのは、 JWT
がすべてのリクエストでチェックされ、すべてのリクエストが公開鍵に対して検証されるため、すべてのリクエストで正しいトークンを送信すれば通過できることがわかっているからです。JWT JWT
、OAuth2、Okta SSO、またはその他の認証メカニズムと統合できます。この場合、認証と承認を確立しています。アプリケーションでは、 JWT
使用し、署名を使用してメッセージを認証します。ただし、アプリケーションにログインすることはありません。代わりに、認証が成功した後にユーザーがアプリケーションを使用できるようにします。この時点で、 JWT
の核は実際には完全なアプリケーションのごく一部であることが簡単にわかります。それでも、いくつかの機能を追加する必要があります。必要なリソースは次のとおりです。
基本的なシステムでは、お金とクレジットのリクエストのみを登録するとしましょう。基本的には、価値を蓄積するだけです。また、クレジットを取得できる人もいれば、できない人もいます。お金を保管できる人もいれば、クレジットを取得できる人もいます。
はじめに述べたように、 KumuluzEE
エンタープライズ アプリケーション フレームワークとして使用し、基本的なJWT
用語と概念を確認できるような超基本的なアプリケーションを実装します。正しい Java バージョンを使用していることを確認してください。この段階では、最小限の Java 17 SDK がインストールされている必要があります。Maven、Git、IntelliJ などの Java 互換 IDE、および何らかのシェルが必要になります。
アプリケーションを開始するには、いくつかのKumuluzEE
依存関係が必要です。これは主に、Spring Boot と同様に、 KumuluzEE
にもいくつかの依存関係が必要であるためです。POM ファイルを簡単に見てみましょう。
<dependencies> <dependency> <groupId>com.kumuluz.ee.openapi</groupId> <artifactId>kumuluzee-openapi-mp</artifactId> </dependency> <dependency> <groupId>com.kumuluz.ee.openapi</groupId> <artifactId>kumuluzee-openapi-mp-ui</artifactId> </dependency> <dependency> <groupId>com.kumuluz.ee</groupId> <artifactId>kumuluzee-microProfile-3.3</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.mockk</groupId> <artifactId>mockk-jvm</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.ninja-squad</groupId> <artifactId>springmockk</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.kotest</groupId> <artifactId>kotest-assertions-core-jvm</artifactId> <scope>test</scope> </dependency> </dependencies>
いくつかの依存関係について簡単に説明しましょう。これを読むときは、 pom.xml
ファイルを上から下まで追ってください。これは、以下の説明を理解するために重要です。アプリケーションを動作させるには、依存関係のパッケージが必要です。幸いなことに、 KumuluzEE
、このアプリケーションを開始するための基本的な標準バンドルを含む Microprofile ライブラリを提供しています。これらはすべて、 KumuluzEE
-Microprofile ライブラリに含まれています。必要なすべてのJWT
パラメーターを使用してアプリを構成できるようにするには、MicroProfile ライブラリを追加する必要があります。同時に、JSON 処理ライブラリが必要です。これは Johnson Core が行うことです。もちろん、動作するにはKumuluzEE
のコアが必要です。 Jetty は、 KumuluzEE
フレームワークを実行する基盤となるサーバーです。これが、依存関係に Jetty が必要な理由です。 CDI
必要であることを考えると、それをサポートするライブラリも必要です。 REST エンドポイントを有効にするには、 KumuluzEE
の REST ライブラリが必要です。 API を取得するには、Geronimo ライブラリが必要です。これにより、 JSR-374
の実装が利用可能になります。また、 JWT
とそのJSON-formatted
コンテンツを解釈する必要があります。Lombok は、それ自体は必要ありません。すべてが美しく輝くようになるだけです。ログをより適切に解釈して結果を理解するために、Logback も重要です。では、 resources
フォルダーを見てみましょう。まず、このフォルダーに何があるかを理解しましょう。JWT やJWT
に関連するものでアプリケーションを構成する必要があり、最後に、作成する Bean について何かを指定する必要があります。そこで最も単純なファイルを見てみましょう。beans.xml は META-INF にあります。
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" xmlns:weld="http://jboss.org/schema/weld/beans" bean-discovery-mode="all"> <weld:scan> <weld:exclude name="org.jesperancinha.fintech.model.Accounts"/> </weld:scan> </beans>
これは典型的なファイルであり、少し古いファイルだと思うかもしれません。この時点では、 KumuluzEE
実行することだけが目的です。除外アクションがあります。これは、Weld に、Bean アクションのスキャンで Accounts クラスを考慮しないように指示します。これは重要です。使用している実装では、 Weld
基本的に空のコンストラクタを持つすべてのクラスを Bean と見なすからです。Accounts を Bean と見なさない理由は後で説明します。今のところ、リクエストは Request スコープで作成していることに留意してください。これは、リクエストごとに異なるユーザーを持つことができるため、論理的です。では、「 logback
」がどのように実装されているかを見てみましょう。これはMETA-INF
にも記載されています。
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration>
これは、 logs
の非常に簡単な構成です。最後に、おそらくアプリケーションで最も重要なファイルです。これは config-template です。この時点で、このプロジェクトで作成したファイルの一部がテンプレート構造の一部であることに注意することが重要です。これについては後で詳しく説明します。このテンプレート ファイルは、MicroProfile によって読み取られる config.yml ファイルに変換されるはずです。このファイルは、リソースのルートにあります。
kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: {{ publicKey }} issuer: {{ issuer }} healthy: true
これらのプロパティが実際に何を意味するのかは、後で説明します。すべて説明不要です。publicKey と issuer はすべて置換されるパラメーターです。これについては後で説明します。bash スクリプトによって置換が確実に行われます。コーディングの準備はほぼ整いましたが、まずはJWT
トークンの構造を見てみましょう。
非常に小さなアプリケーションを作成しましょう。このセクションでは、アプリケーションをJWT
で動作させる方法について説明します。確認したいのは、一部のREST
メソッドにアクセスできるようにユーザーを指定し、他のメソッドにはアクセスできないようにできるかどうかです。このコードを確認する方法の 1 つは、まずプレーンなJWT
トークンを確認することです。これが管理者の例です。
{ "iss": "joaofilipesabinoesperancinha", "jti": "01MASTERFINANCE", "sub": "admin", "aud": "nature", "upn": "admin", "groups": [ "user", "admin", "client", "credit" ], "user_id": 1, "access": "TOP", "name": "Admin" }
JSON
内のこれらの名前はそれぞれクレームと呼ばれます。この例では、いくつかの予約済みクレームが表示されています。
iss
」 — これはトークンの発行者です。この値は任意に選択できます。このパラメータの値は、前に見た config.yml で置き換えられる発行者変数と一致する必要があります。jti
」 — これはトークンの一意の識別子です。たとえば、このクレームを使用して、トークンが 2 回以上使用されるのを防ぐことができます。sub
」 — これはトークンのサブジェクトです。ユーザーまたは任意のものにすることができます。これは識別子、キー、名前、または任意のものとしても使用できることを覚えておくことが重要です。upn
」 — ユーザー プリンシパル名。これは、ユーザーが使用しているプリンシパルを識別するために使用されます。groups
」 — これは現在のユーザーが属するグループの配列です。基本的に、これはこのトークンを使用したリクエストが実行できることを決定します。トークンには、いくつかのカスタムクレームがあります。これは予約済みクレームと同様に使用できます。user_id
」 — これを使用してユーザー ID を設定します。access
」 — ユーザーのアクセス レベルを決定します。name
」 — ユーザーの名前。これまでの内容をまとめてみましょう。決定した構造を持つトークンと通信することがわかっています。さらに、アプリケーションの構成、ログバック構成を設定し、最後にエンタープライズ Bean ルックアップのカスタム構成を設定しました。パッケージ モデルを見てみましょう。ここには 3 つのクラスがあります。これらのクラスは基本的に、アカウントの集約と、 client
とaccount
間の表現を表しています。このように、まず、 Client
が配置されている Kotlin ファイル Model.kt を見てみましょう。
data class Client constructor( @JsonProperty var name: String ?= null )
この最初のモデル クラスは、クライアントの表現です。このケースのclient
には名前しかありません。これは、" jwt
" 属性名で表されるユーザー名です。さらに、 Account
あります。
data class Account( @JsonProperty val accountNumber: String?, @JsonProperty val client: Client? = null, @JsonProperty var currentValue: BigDecimal = BigDecimal.ZERO, @JsonProperty var creditValue: BigDecimal = BigDecimal.ZERO ) { fun addCurrentValue(value: Long) = Account( accountNumber, client, currentValue .add(BigDecimal.valueOf(value)), creditValue ) fun addCreditValue(value: Long): Account = Account( accountNumber, client, currentValue, currentValue .add(BigDecimal.valueOf(value)) ) }
このクラスでは、基本的に accountNumber、client、currentValue、そして最後に creditValue を設定します。すべての値を 0 にデフォルト設定していることに注意してください。また、BigDecimal も使用していますが、これは単にお金を扱っているからです。お金は正確である必要があり、システムの切り上げや切り捨ての影響を受けることはありません。言い換えると、たとえば 0. 0000000000000000000000000000000000000000000000000001
ユーロなどの数値は常にその数値のままである必要があります。また、アカウントに値を追加する必要があります。ここで、addCurrentValue メソッドが登場します。同じ理由で、 addCreditValue
を使用してクレジットも補充します。最後に、データ設定の最後の部分で、 Accounts
クラスを使用します。
open class Accounts constructor( open val accountMap: MutableMap<String, Account> = mutableMapOf() )
これは本質的にはすべてのアカウントの集約です。マップ コンテンツを使用して、データベースの動作を模倣します。次に、コントローラー パッケージを見てみましょう。ここで、データ モデルを使用して実行するアプリケーションを作成します。まず、 BankApplication
クラスを見てみましょう。
@LoginConfig(authMethod = "MP-JWT") @ApplicationPath("/") @DeclareRoles("admin", "creditor", "client", "user") class BankApplication : Application()
ここで、3 つの重要な点を述べます。LoginConfig アノテーションでは、MicroProfile に従ってJWT
トークンを使用および理解するように定義します。ApplicationPath は、アプリケーション ルートを定義します。ここからアプリケーションの URL が始まります。この例では、 HTTP://localhost:8080になります。最後に、 DeclareRoles は、アプリケーションで使用および受け入れられるロールを定義します。この状況では、ロールとグループは互換性のある用語です。インジェクションを効率的に機能させるために、アカウント マップを識別するためのアノテーションを作成します。
annotation class AccountsProduct
フルスクリーンモードに入る フルスクリーンモードを終了する
次に、キャッシュ オブジェクト ファクトリ AccountsFactory を作成します。
class AccountsFactory : Serializable { @Produces @AccountsProduct @ApplicationScoped fun accounts(): Accounts = Accounts(mutableMapOf()) companion object { @Throws(JsonProcessingException::class) fun createResponse( currentAccount: Account, name: JsonString, accounts: Accounts, log: Logger, objectMapper: ObjectMapper, principal: Principal?, jsonWebToken: JsonWebToken? ): Response { val jsonObject = Json.createObjectBuilder() .add("balance", currentAccount.currentValue) .add("client", name) .build() accounts.accountMap[name.string] = currentAccount log.info("Principal: {}", objectMapper.writeValueAsString(principal)) log.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(jsonObject) .build() } } }
このファクトリが、 Accounts
のルックアップを無効にした理由です。ルックアップ プロセスで Bean を作成できるようにする代わりに、アグリゲーター インスタンスを自分で作成します。 Produces アノテーションを使用すると、Bean を作成できます。カスタム アノテーション AccountsProduct を使用して、この Bean の使用をより具体的にします。最後に、 ApplicationScoped
使用して、そのスコープをApplication
スコープとして定義します。つまり、アカウント アグリゲーション Bean は、アプリケーション全体でシングルトン オブジェクトとして動作します。「 createResponse
」は、JSON 応答を作成するための汎用メソッドです。ここで必要なのAccountsResource
、2 つの「リソース」です。これは基本的に Spring の「 Controllers
」と同じです。名前は異なりますが、使用方法はまったく同じです。AccountsResource クラスを見てみましょう。
@Path("accounts") @RequestScoped @Produces(MediaType.APPLICATION_JSON) open class AccountResource { @Inject @AccountsProduct open var accounts: Accounts? = null @Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null @Inject @Claim("access") open var access: JsonString? = null @Claim("iat") @Inject open var iat: JsonNumber? = null @Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null @POST @RolesAllowed("admin", "client", "credit") @Throws(JsonProcessingException::class) open fun createAccount(): Response = createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account( client = Client(name = requireNotNull(name).string), accountNumber = UUID.randomUUID().toString() ) ) @POST @RolesAllowed("admin", "user") @Path("user") @Throws(JsonProcessingException::class) open fun createUser(): Response { return createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account( client = Client(name = requireNotNull(name).string), accountNumber = UUID.randomUUID().toString() ) ) } @GET @RolesAllowed("admin", "client") @Throws(JsonProcessingException::class) open fun getAccount(): Response? { return createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError() .build() ) } @PUT @RolesAllowed("admin", "client") @Consumes(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun cashIn(transactionBody: TransactionBody): Response? { val userAccount = requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError() .build() val currentAccount = userAccount.addCurrentValue(transactionBody.saldo?: 0) requireNotNull(accounts).accountMap[requireNotNull(name).string] = currentAccount return createResponse(currentAccount) } @GET @Path("all") @Produces(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun getAll(): Response? { val allAccounts = ArrayList( requireNotNull(accounts).accountMap .values ) logger.info("Principal: {}", objectMapper.writeValueAsString(principal)) logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(allAccounts) .build() } @GET @Path("summary") @Throws(JsonProcessingException::class) open fun getSummary(): Response? { val totalCredit = requireNotNull(accounts).accountMap .values .map(Account::currentValue) .stream() .reduce { result, u -> result.add(u) } .orElse(BigDecimal.ZERO) val jsonObject = Json.createObjectBuilder() .add("totalCurrent", totalCredit) .add("client", "Mother Nature Dream Team") .build() logger.info("Summary") logger.info("Principal: {}", objectMapper.writeValueAsString(principal)) logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(jsonObject) .build() } @GET @RolesAllowed("admin", "client") @Path("jwt") open fun getJWT(): Response? { val jsonObject = Json.createObjectBuilder() .add("jwt", requireNotNull(jsonWebToken).rawToken) .add("userId", requireNotNull(userId).doubleValue()) .add("access", requireNotNull(access).string) .add("iat", requireNotNull(iat).doubleValue()) .build() return Response.ok(jsonObject) .build() } @Throws(JsonProcessingException::class) private fun createResponse(currentAccount: Account): Response = AccountsFactory.createResponse( currentAccount, requireNotNull(name), requireNotNull(accounts), logger, objectMapper, principal, jsonWebToken ) companion object { val objectMapper: ObjectMapper = ObjectMapper() val logger: Logger = LoggerFactory.getLogger(AccountResource::class.java) } }
このクラスをもう少し詳しく見てみましょう。Path アノテーションは、 Path
からこのリソースに到達する方法を定義します。ルートとして「/」を使用していることに注意してください。この場合、「accounts」がこのリソースのルート アクセス ポイントです。すべてのリソース (この場合は 2 つだけ) は、スコープ RequestResource で実行されています。アノテーション Produces により、タイプに関係なくすべてのリクエストに対するすべての応答が JSON 形式のメッセージ形式になることが決定されます。 aggregator
挿入するには、Inject アノテーションとAccountsProduct
アノテーションの組み合わせを使用します。
@Inject @AccountsProduct open var accounts: Accounts? = null
これは、ファクトリーで定義したものと一致します。さらに、セキュリティの重要な要素であるprincipal
とjsonWebToken
も挿入しています。
@Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null
JsonWebToken
とPrincipal
両方とも同じであり、ログでそれを確認できます。リソースでは、特定のトークンを使用してリクエストからクレームを常に挿入できます。
@Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null
これは、 Inject
アノテーションとClaim
アノテーションの組み合わせで実現されます。Claim アノテーションの下に配置される名前は、 Claim
クレームを注入するかを定義します。パラメータを定義する型には注意する必要があります。この例では、 JsonString
とJsonNumber
型のみが必要です。まず、アカウントとユーザーを作成する方法を見てみましょう。
@POST @RolesAllowed("admin", "client", "credit") @Throws(JsonProcessingException::class) open fun createAccount(): Response = createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account( client = Client(name = requireNotNull(name).string), accountNumber = UUID.randomUUID().toString() ) ) @POST @RolesAllowed("admin", "user") @Path("user") @Throws(JsonProcessingException::class) open fun createUser(): Response { return createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account( client = Client(name = requireNotNull(name).string), accountNumber = UUID.randomUUID().toString() ) ) }
アカウントとユーザーの作成
ここでの目的は、メソッドを分離して、異なる権限を付与できるようにすることです。この例では、どちらもアカウントを作成するだけですが、ユーザー ロールを持つユーザーのみが createUser メソッドを使用できることに注意してください。同様に、クライアントとクレジット ロールを持つユーザーのみが createAccount メソッドにアクセスできます。次に、このリソースの PUT リクエスト メソッドを詳しく見てみましょう。
@PUT @RolesAllowed("admin", "client") @Consumes(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun cashIn(transactionBody: TransactionBody): Response? { val userAccount = requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError() .build() val currentAccount = userAccount.addCurrentValue(transactionBody.saldo?: 0) requireNotNull(accounts).accountMap[requireNotNull(name).string] = currentAccount return createResponse(currentAccount) }
現金化
PUT
アノテーションは、このメソッドがPUT
タイプのリクエストでのみアクセス可能であることを示していることがわかります。次に、Path アノテーションは、このメソッドへのパスが値であることを Jetty に伝えます。これはPathParam
とも呼ばれます。最後に、このメソッドを admin または client のロールを持つユーザーのみが使用できるように定義できます。入力値は、PathParam を使用して Long 値変数に渡されます。ロールを定義しない場合は、適切なトークンを持つすべてのユーザーがこれらのメソッドにアクセスできます。CreditResource CreditResource
同様に実装されます。
@Path("credit") @RequestScoped @Produces(MediaType.APPLICATION_JSON) open class CreditResource { @Inject @AccountsProduct open var accounts: Accounts? = null @Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null @Inject @Claim("access") open var access: JsonString? = null @Inject @Claim("iat") open var iat: JsonNumber? = null @Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null @GET @RolesAllowed("admin", "credit") @Throws(JsonProcessingException::class) open fun getAccount(): Response = requireNotNull(accounts).let { accounts -> createResponse( accounts.accountMap[requireNotNull(name).string] ?: return Response.serverError().build() ) } @PUT @RolesAllowed("admin", "credit") @Consumes(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun cashIn(transactionBody: TransactionBody) = requireNotNull(accounts).let { accounts -> requireNotNull(name).let { name -> accounts.accountMap[name.string] = (accounts.accountMap[name.string] ?: return Response.serverError() .build()).addCreditValue(transactionBody.saldo?: 0L) createResponse( (accounts.accountMap[name.string] ?: return Response.serverError() .build()).addCreditValue(transactionBody.saldo?: 0L) ) } } @GET @Path("all") @Produces(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun getAll(): Response? { val allAccounts = ArrayList( requireNotNull(accounts).accountMap .values ) logger.info("Principal: {}", objectMapper.writeValueAsString(principal)) logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(allAccounts) .build() } @GET @Path("summary") @Produces(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun getSummary(): Response? { val totalCredit = requireNotNull(accounts).accountMap .values .map(Account::creditValue) .stream() .reduce { total, v -> total.add(v) } .orElse(BigDecimal.ZERO) val jsonObject = Json.createObjectBuilder() .add("totalCredit", totalCredit) .add("client", "Mother Nature Dream Team") .build() logger.info("Summary") logger.info("Principal: {}", objectMapper.writeValueAsString(principal)) logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(jsonObject) .build() } @GET @RolesAllowed("admin", "client") @Path("jwt") open fun getJWT(): Response? { val jsonObject = Json.createObjectBuilder() .add("jwt", requireNotNull(jsonWebToken).rawToken) .add("userId", requireNotNull(userId).doubleValue()) .add("access", requireNotNull(access).string) .add("iat", requireNotNull(iat).doubleValue()) .build() return Response.ok(jsonObject) .build() } @Throws(JsonProcessingException::class) private fun createResponse(currentAccount: Account): Response { return AccountsFactory.createResponse( currentAccount, requireNotNull(name), requireNotNull(accounts), logger, objectMapper, principal, jsonWebToken ) } companion object { val objectMapper: ObjectMapper = ObjectMapper() val logger: Logger = LoggerFactory.getLogger(CreditResource::class.java) } }
唯一の違いは、 admin
とclient
ロールを使用する代わりに、 admin
とcredit
ロールを使用することです。また、このresource
ではユーザーのアカウントは作成されないことに注意してください。これは、アカウントのresource
を介してのみ可能です。コードの実装方法がわかったので、まずはREST
サービスでどのメソッドを利用できるようにしたかをまとめてみましょう。
使用されているサービスのリストを確認しましょう:
タイプ、URL、ペイロード、結果、許可されるロール
役職、
役職、
得る、
置く、
得る、
得る、
得る、
置く、
得る、
得る、
ルート フォルダーにbash
ファイルを作成しました。このファイルは「setupCertificates.sh」と呼ばれます。このファイルの内容を見て、何をするのか理解しましょう。
#!/bin/bash mkdir -p your-finance-files cd your-finance-files || exit openssl genrsa -out baseKey.pem openssl pkcs8 -topk8 -inform PEM -in baseKey.pem -out privateKey.pem -nocrypt openssl rsa -in baseKey.pem -pubout -outform PEM -out publicKey.pem echo -e '\033[1;32mFirst test\033[0m' java -jar ../your-finance-jwt-generator/target/your-finance-jwt-generator.jar \ -p ../jwt-plain-tokens/jwt-token-admin.json \ -key ../your-finance-files/privateKey.pem >> token.jwt CERT_PUBLIC_KEY=$(cat ../your-finance-files/publicKey.pem) CERT_ISSUER="joaofilipesabinoesperancinha" echo -e "\e[96mGenerated public key: \e[0m $CERT_PUBLIC_KEY" echo -e "\e[96mIssued by: \e[0m $CERT_ISSUER" echo -e "\e[96mYour token is: \e[0m $(cat token.jwt)" cp ../your-financeje-banking/src/main/resources/config-template ../your-financeje-banking/src/main/resources/config_copy.yml CERT_CLEAN0=${CERT_PUBLIC_KEY//"/"/"\/"} CERT_CLEAN1=${CERT_CLEAN0//$'\r\n'/} CERT_CLEAN2=${CERT_CLEAN1//$'\n'/} CERT_CLEAN3=$(echo "$CERT_CLEAN2" | awk '{gsub("-----BEGIN PUBLIC KEY-----",""); print}') CERT_CLEAN4=$(echo "$CERT_CLEAN3" | awk '{gsub("-----END PUBLIC KEY-----",""); print}') CERT_CLEAN=${CERT_CLEAN4//$' '/} echo -e "\e[96mCertificate cleanup: \e[0m ${CERT_CLEAN/$'\n'/}" sed "s/{{ publicKey }}/$CERT_CLEAN/g" ../your-financeje-banking/src/main/resources/config_copy.yml > ../your-financeje-banking/src/main/resources/config_cert.yml sed "s/{{ issuer }}/$CERT_ISSUER/g" ../your-financeje-banking/src/main/resources/config_cert.yml > ../your-financeje-banking/src/main/resources/config.yml rm ../your-financeje-banking/src/main/resources/config_cert.yml rm ../your-financeje-banking/src/main/resources/config_copy.yml echo -e "\e[93mSecurity elements completely generated!\e[0m" echo -e "\e[93mGenerating tokens...\e[0m" TOKEN_FOLDER=jwt-tokens mkdir -p ${TOKEN_FOLDER} # CREATE_ACCOUNT_FILE=createAccount.sh CREATE_USER_FILE=createUser.sh SEND_MONEY_FILE=sendMoney.sh ASK_CREDIT_FILE=askCredit.sh TOKEN_NAME_VALUE=tokenNameValue.csv echo "#!/usr/bin/env bash" > ${CREATE_ACCOUNT_FILE} chmod +x ${CREATE_ACCOUNT_FILE} echo "#!/usr/bin/env bash" > ${CREATE_USER_FILE} chmod +x ${CREATE_USER_FILE} echo "#!/usr/bin/env bash" > ${SEND_MONEY_FILE} chmod +x ${SEND_MONEY_FILE} echo "#!/usr/bin/env bash" > ${ASK_CREDIT_FILE} chmod +x ${ASK_CREDIT_FILE} for item in ../jwt-plain-tokens/jwt-token*.json; do if [[ -f "$item" ]]; then filename=${item##*/} per_token=${filename/jwt-token-/} token_name=${per_token/.json/} cp "${item}" jwt-token.json java -jar ../your-finance-jwt-generator/target/your-finance-jwt-generator.jar \ -p jwt-token.json \ -key ../your-finance-files/privateKey.pem > token.jwt cp token.jwt ${TOKEN_FOLDER}/token-"${token_name}".jwt token=$(cat token.jwt) echo "# Create account: ""${token_name}" >> ${CREATE_ACCOUNT_FILE} echo "echo -e \"\e[93mCreating account \e[96m${token_name}\e[0m\"" >> ${CREATE_ACCOUNT_FILE} echo curl -i -H"'Authorization: Bearer ""${token}""'" http://localhost:8080/accounts -X POST >> ${CREATE_ACCOUNT_FILE} echo "echo -e \"\e[93m\n---\e[0m\"" >> ${CREATE_ACCOUNT_FILE} echo "# Create user: ""${token_name}" >> ${CREATE_USER_FILE} echo "echo -e \"\e[93mCreating user \e[96m${token_name}\e[0m\"" >> ${CREATE_USER_FILE} echo curl -i -H"'Authorization: Bearer ""${token}""'" http://localhost:8080/accounts/user -X POST >> ${CREATE_USER_FILE} echo "echo -e \"\e[93m\n---\e[0m\"" >> ${CREATE_USER_FILE} echo "# Send money to: "${token_name} >> ${SEND_MONEY_FILE} echo "echo -e \"\e[93mSending money to \e[96m${token_name}\e[0m\"" >> ${SEND_MONEY_FILE} echo curl -i -H"'Content-Type: application/json'" -H"'Authorization: Bearer ""${token}""'" http://localhost:8080/accounts -X PUT -d "'{ \"saldo\": "$((1 + RANDOM % 500))"}'" >> ${SEND_MONEY_FILE} echo "echo -e \"\e[93m\n---\e[0m\"" >> ${SEND_MONEY_FILE} echo "# Asking money credit to: "${token_name} >> ${ASK_CREDIT_FILE} echo "echo -e \"\e[93mAsking credit from \e[96m${token_name}\e[0m\"" >> ${ASK_CREDIT_FILE} echo curl -i -H"'Content-Type: application/json'" -H"'Authorization: Bearer ""${token}""'" http://localhost:8080/credit -X PUT -d "'{ \"saldo\": "$((1 + RANDOM % 500))"}'">> ${ASK_CREDIT_FILE} echo "echo -e \"\e[93m\n---\e[0m\"" >> ${ASK_CREDIT_FILE} echo "${token_name},${token}" >> ${TOKEN_NAME_VALUE} fi done
環境生成
ファイルの説明に従って、その内容を確認してください。これは、その内容を正確に理解するために重要です。まず、 PEM
形式で秘密鍵と公開鍵を作成します。次に、実行可能な「your-finance-jwt-generator.jar」で秘密鍵を使用します。これは、トークンをすばやく作成できる実行可能な jar です。発行者は後で変更できません。最後に、トークンを作成します。このトークンの読み方は後で説明します。このトークンには、3 つの追加ヘッダー クレームが含まれています。これらは、「kid」、「typ」、および「alg」です。次の形式に従います。
{ "kid": "jwt.key", "typ": "JWT", "alg": "RS256" }
JWT
のヘッダー
これらの主張をもう少し詳しく見てみましょう。
IANA
メディア タイプを宣言するために使用されます。JWT (JSON Web トークン)、 JWE
(JSON Web 暗号JWT
)、 JWA
(JSON Web アルゴリズム) の 3 つのオプションがあります。これらのタイプは、この実験には関係ありません。トークンが十分に暗号化されておらず、復号化が非常に簡単であることがわかります。また、トークンを復号化することはできますが、他のアクションを実行するために簡単に改ざんすることはできないこともわかります。公開キーを使用すると、最終的にテンプレートを変更するために使用できます。新しい config.yml ファイルは次のようになります。
kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN issuer: joaofilipesabinoesperancinha healthy: true
config.yml
2 番目のステップは、4 つのファイルを作成することです。ディレクトリ「 jwt-plain-tokens
」内の各プレーン トークンに対して、4 つのコマンドを作成します。最初のコマンドは、アカウントで効果的に操作できるユーザーを作成することです。これらは、プロファイル「 admin
」、「 client
」、「 credit
」を持つユーザーです。これらを作成するには、「 createAccount.sh
」ファイルを実行します。2 番目のコマンドは、まだ権限を持っていない残りのユーザーを作成します。これは「createUser.sh」ファイルです。これを実行してみましょう。これで、すべてのユーザーが最終的に作成されたことがわかります。次に、トランザクションの詳細を確認し、残りの 2 つのコマンドを見てみましょう。1 つは「cashin」コマンドで、もう 1 つはクレジットを増やすよう要求するコマンドです。最初に生成されるファイルは、「sendMoney.sh」bash スクリプトです。ここに、「 cashin
」へのすべてのリクエストがあります。このファイルには、ユーザーごとにランダムな金額をユーザーに送信するための curl リクエストがあります。管理者の場合を見てみましょう。
#!/usr/bin/env bash # Send money to: admin echo -e "\e[93mSending money to \e[96madmin\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer= FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 125}' echo -e "\e[93m\n---\e[0m" # Send money to: cindy echo -e "\e[93mSending money to \e[96mcindy\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 360}' echo -e "\e[93m\n---\e[0m" # Send money to: faustina echo -e "\e[93mSending money to \e[96mfaustina\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 50}' echo -e "\e[93m\n---\e[0m" # Send money to: jack echo -e "\e[93mSending money to \e[96mjack\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 205}' echo -e "\e[93m\n---\e[0m" # Send money to: jitska echo -e "\e[93mSending money to \e[96mjitska\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 332}' echo -e "\e[93m\n---\e[0m" # Send money to: judy echo -e "\e[93mSending money to \e[96mjudy\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 295}' echo -e "\e[93m\n---\e[0m" # Send money to: lucy echo -e "\e[93mSending money to \e[96mlucy\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 160}' echo -e "\e[93m\n---\e[0m" # Send money to: malory echo -e "\e[93mSending money to \e[96mmalory\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 413}' echo -e "\e[93m\n---\e[0m" # Send money to: mara echo -e "\e[93mSending money to \e[96mmara\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 464}' echo -e "\e[93m\n---\e[0m" # Send money to: namita echo -e "\e[93mSending money to \e[96mnamita\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 51}' echo -e "\e[93m\n---\e[0m" # Send money to: pietro echo -e "\e[93mSending money to \e[96mpietro\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 491}' echo -e "\e[93m\n---\e[0m" # Send money to: rachelle echo -e "\e[93mSending money to \e[96mrachelle\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 474}' echo -e "\e[93m\n---\e[0m" # Send money to: sandra echo -e "\e[93mSending money to \e[96msandra\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 417}' echo -e "\e[93m\n---\e[0m" # Send money to: shikka echo -e "\e[93mSending money to \e[96mshikka\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 64}' echo -e "\e[93m\n---\e[0m"
sendMoney.sh 抽出
同じユーザーにはクレジットリクエストも割り当てられます。
#!/usr/bin/env bash # Asking money credit to: admin echo -e "\e[93mAsking credit from \e[96madmin\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 137}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: cindy echo -e "\e[93mAsking credit from \e[96mcindy\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 117}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: faustina echo -e "\e[93mAsking credit from \e[96mfaustina\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 217}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: jack echo -e "\e[93mAsking credit from \e[96mjack\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 291}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: jitska echo -e "\e[93mAsking credit from \e[96mjitska\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 184}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: judy echo -e "\e[93mAsking credit from \e[96mjudy\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 388}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: lucy echo -e "\e[93mAsking credit from \e[96mlucy\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 219}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: malory echo -e "\e[93mAsking credit from \e[96mmalory\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 66}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: mara echo -e "\e[93mAsking credit from \e[96mmara\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 441}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: namita echo -e "\e[93mAsking credit from \e[96mnamita\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 358}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: pietro echo -e "\e[93mAsking credit from \e[96mpietro\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 432}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: rachelle echo -e "\e[93mAsking credit from \e[96mrachelle\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 485}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: sandra echo -e "\e[93mAsking credit from \e[96msandra\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 500}' echo -e "\e[93m\n---\e[0m" # Asking money credit to: shikka echo -e "\e[93mAsking credit from \e[96mshikka\e[0m" curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 89}' echo -e "\e[93m\n---\e[0m"
askCredit.sh の抜粋
私たちのcharacters
すべてNature
連盟の一部です。基本的には、この銀行システムの一部である人々のグループです。この文脈では、彼らは環境を守っています。このグループの人々が何をしているのか、ストーリーのどこに当てはまるのかは記事にはあまり関係ありませんが、文脈上、彼らは環境を守り、気候変動の影響を遅らせる活動に参加しています。私たちのcharacters
の中には何でもできる人もいれば、何もできない人もいれば、「現金化」または「クレジットの要求」しかできない人もいます。また、機密情報を難読化していることにも注意してください。これらのトークンは通常、共有したり、特定の URL で表示したりしないでください。ブラウザの開発者コンソールからは常に利用できますが、いずれにしても、行われているリクエストprotect
ためです。これは「security-per-obscurity」と呼ばれる概念でありand
技術的にはユーザーが使用されているトークンに気付くのを防ぐことはできませんが、抑止力として機能します。どちらの方法でも、入金を行うときやクレジットを要求するときは、リクエストごとに 1 ~ 500 のランダムな数字が送信されることに注意してください。これでアプリケーションを開始する準備がほぼ整いましたが、まずは理論をもう少し詳しく見てみましょう。
JWT
トークンはどのように作られるか
トークンを生成したので、その 1 つを見てみましょう。難読化されたトークンを示し、これFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO
使用してこれを理解します。これが私たちのトークンです: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE
... FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN
ここで注目すべき重要な点は、トークンが 3 つの部分に分割されていることです。
Header
とPayload
の暗号化された組み合わせです。使用するアルゴリズムを決定し、トークンのこの部分によって、送信するメッセージが信頼できるかどうかが基本的に決定されます。これはその組み合わせに固有のものであり、サーバーは作成した「公開キー」を使用して、一致するかどうかを判断します。上記の例でRS256
使用していることを覚えていると思います。
先に進む前に、この例ではHeader
とPayload
両方がdecyphered
可能であることに注意してください。ペイロードまたはヘッダーを改ざんしても、信頼できるものにすることはできません。悪意のあるトークンの潜在的な影響に対する保護は、選択したアルゴリズムによってのみ保護できます。したがって、慎重に選択してください。銀行など、極秘情報が懸念される組織で働いている場合は、これから行うことは絶対にしないでください。これは、ローカルで生成したトークンの内容をオンラインで確認するための手段にすぎません。まず、 https://jwt.io/にアクセスして、 JWT
トークンを入力します。生成したトークンを使用します。
https://jwt.io/を使用してトークンの内容を確認します。ここで何があるか確認してみましょう。これは管理者トークンです。この例では、その人物は「Admin」です。パラメータがすべて使用可能であることがわかります。リストには、「sub」、「aud」、「upn」、「access」、「user_id」、「iss」、「name」、「groups」、最後に「jti」があります。また、追加のクレームもいくつかあります。これらを見てみましょう。
「 auth_time 」 — これは認証が行われた時刻です。トークンは、2022 年 7 月 17 日日曜日 16:15:47 GMT+02:00 DST に認証されました。「 iat 」 — これはトークンが作成された時刻です。この場合、これは auth_time と同時に発生します。「 exp 」 — これはトークンの有効期限です。有効期限は 2022 年 7 月 17 日日曜日 16:32:27 GMT+02:00 DST です。トークンに有効期限を指定しませんでした。つまり、 JWT
デフォルト値の約 15 分を使用します。
それでは、いくつかテストを実行してみましょう。
コードはGitHubで使用できる状態になっています。コードをチェックアウトして Intellij で開く場合、このアプリケーションを Spring Boot アプリケーションのように実行できないことに注意する必要があります。実行するための「psvm」はありません。代わりに、生成された jar を直接実行し、その直前に「mvn build」を行うようにしてください。現時点では、次のように使用しています。
[ ] https://github.com/jesperancinha/your-finance-je 「アプリケーションを実行するための環境設定」)
ここで、「 setupCertificates.sh
」スクリプトをもう一度実行してみましょう。ここまでにどのくらいの時間がかかったかはわかりませんが、この時点ですでに 15 分が経過している可能性が高いです。念のため、もう一度実行してください。アプリを起動してみましょう。次のように起動できます。
mvn clean install java -jar your-financeje-banking/target/your-financeje-banking.jar
または、すぐに実行できる構成で実行することもできます。すべての動作を理解したい場合は、事前にリポジトリと Makefile を確認してください。
make dcup-full-action
このスクリプトは 2 つのサービスを実行します。1 つはポート8080
で、もう 1 つはポート8081
です。ポート8080
では、 JWT
トークンを生成するために独自のコードを実行するこのソフトウェアのバージョンを実行します。ポート 8081 では、 Adam Bien
が作成したjwtknizr
ジェネレーターを使用するバージョンを実行します。ただし、この記事ではポート8080
で実行されているサービスに焦点を当てます。必要に応じて、次のコマンドでcypress
実行することもできます。
make cypress-open
これにより、 cypress
コンソールがopen
、選択したブラウザでテストを実行できるようになります。ただし、この段階ではブラウザのオプションはまだ限られています。リクエストのほとんどは、実際にはcypress
によって提供されるコマンド ライン リクエストになります。今のところ、「 cypress
」には立ち入りません。ブラウザで次の場所に移動してください。
http://localhost:8080/アカウント/すべて
次のような結果が得られるはずです:
ご覧のとおり、「 Malory
」、「 Jack Fallout
」、および「 Jitska
」にはクレジットもお金もありません。これは、これらのクライアントにユーザー グループのみが与えられているためです。また、 Shikka
はクレジットが与えられていないことにも注意してください。「 Shikka
」は、グループ クレジットを持たない唯一のクライアントです。ログを見ると、成功した操作は次の形式になっていることがわかります。
Sending money to admin HTTP/1.1 200 OK Date: Sun, 17 Jul 2022 15:01:13 GMT X-Powered-By: KumuluzEE/4.1.0 Content-Type: application/json Content-Length: 32 Server: Jetty(10.0.9) {"balance":212,"client":"Admin"}
200 は、操作が正常に完了したことを示します。「Malory」、「Jack Fallout」、および「Jitska」の場合、両方の操作が失敗し、次のようなメッセージが表示されます。
Sending money to jitska HTTP/1.1 403 Forbidden X-Powered-By: KumuluzEE/4.1.0 Content-Length: 0 Server: Jetty(10.0.9)
403 は、 JWT
トークンが検証され、信頼されていることを知らせます。ただし、ユーザーはその操作を実行することが禁止されています。つまり、指定されたメソッドにアクセスできないということです。
トークンを少し変更してみましょう。sendMoney.sh ファイルのトークンの一部を変更すると、次のようになります。
Sending money to admin HTTP/1.1 401 Unauthorized X-Powered-By: KumuluzEE/4.1.0 WWW-Authenticate: Bearer realm="MP-JWT" Content-Length: 0 Server: Jetty(10.0.9)
フルスクリーンモードに入る フルスクリーンモードを終了する
この401
トークンが検証されなかったことを意味します。つまり、サーバーがトークンが信頼できるかどうかを確認するために使用する公開キーが一致しなかったことを意味します。公開キーが JWT トークンの署名を評価および検証できない場合、トークンは拒否されます。
要約すると、ヘッダーと「ペイロード」は暗号化されていません。これらは単に Base 64 で「エンコード」されています。つまり、「デコード」により、ペイロードの実際の内容をいつでも確認することができます。ペイロードを盗聴から保護したい場合は、トークンの「ペイロード」を識別パラメータの選択以外に使用しないでください。実際に問題となるのは、誰かがJWT
トークンを入手した場合です。たとえば、TLS トンネルが侵害され、交換されたメッセージの内容を誰かが読み取ることができる場合などです。その場合、別の保護手段があります。それが署名です。受信メッセージを検証できるのは、公開鍵を含むサーバーだけです。この公開鍵は公開されていますが、署名と「ヘッダー + ペイロード」に対して実行することによってのみ、受信メッセージを検証できます。
セッションはこれで終了です。お付き合いありがとうございました。JWT トークンはコンパクトで、XML 版のJWT
トークンに比べてはるかに簡潔であることがおわかりいただけたと思います。トークンを作成して使用して特定SAML
メソッドに必要な特定の承認を取得するのがいかに簡単か、また署名付きトークンを使用してそこに到達する方法も確認しました。ただし、 JWT
どのように機能するかを理解することは非常に重要だと思います。これで、 JWT
トークンがどのように機能するかについて、良い紹介ができたと思います。これらすべてがどのように機能するかをよりよく理解するには、実装されたcypress
テストを試してみることをお勧めします。これは、リクエストがどのように行われているか、何をテストしているのか、何が期待されているのかを確認するのに最適な方法です。また、一部のユーザーが特定の操作を実行できるのに、他のユーザーが実行できない理由もよりよく理解できます。このアプリケーションのソース コードはすべてGitHubに置きました。この記事を執筆するのを楽しんだのと同じくらい、皆さんもこの記事を楽しんでいただければ幸いです。お読みいただきありがとうございました。