Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

⾼宮裕⼦(たかみや ひろこ) • ⽶IBM所属 • ノースカロライナ州在住 • アプリケーションサーバー開発部⾨ • セキュリティチームに約10+年 • 東京⽣まれ、東京育ち • JJUG初参加です • よろしくお願いします︕ ⾃⼰紹介 画像︓Wikipedia/ノースカロライナ州観光協会

Slide 3

Slide 3 text

トーク概要 MicroProfile JWTの概要を紹介 • サービスから別のサービスへと安全にアクセスする仕組み § マイクロサービスのセキュリティ関連⽤語 § なぜJWTトークンを使うか § JWTトークンについて § JWTトークンの内容、信⽤できるのはなぜ︖ § JWT、JWS、JWE、JWKの違いは︖ § MicroProfile JWT (mpJWT)は、マイクロサービス間の運⽤性を⾼める § 代表的なユースケース § デモ JWTトークン クライアント サービスA サービスB サービスC サービスC

Slide 4

Slide 4 text

マイクロサービスのセキュリティ関連⽤語 OAuth2 • 認可(Authorization)のプロトコル • OAuth2はソーシャルネットワー クのログインなどに使われる OpenID Connect • OAuth2をもとに構築されたオー プン・スタンダード • OpenID Connectをサポートする プロバイダがユーザー認証 (Authenticate)する • クライアントサービス(Relying Party)は、プロバイダを使って認 証するため、⾃前でユーザーデー タを持たないでよい JWT • JSON Web Token • 情報(Claim)を運ぶトークン • ClaimをJSONオブジェクトで表現、 エンコードののち、デジタル署名 または暗号化、もしくはその両⽅ を使って安全に送受信する

Slide 5

Slide 5 text

なぜマイクロサービスの認証、認可にJWTを使うのか 従来のアプリケーションの場合、 • ユーザーは、いつも同じサーバー(またはクラスター)と通信 • サーバーは、認証認可して、セッション・クッキーやトークンを作成 • サーバーは、セキュリティのコンテクストを、クッキーやトークンと関連づけて保存 5 JSESSIONID SSO Tokenなど Security Context

Slide 6

Slide 6 text

なぜマイクロサービスの認証、認可にJWTを使うのか マイクロサービスの場合、 • サービスは、セキュリティ情報を保存しない • 同じサービスにいく保証もない • リクエストごとに認証、認可の情報(JWT)を送る 6 JWT JWT JWT

Slide 7

Slide 7 text

なぜマイクロサービスの認証、認可にJWTを使うのか • マイクロサービスはステートレスである • セキュリティ コンテキストを、サーバー側の HTTP セッションに保存しない • マイクロサービス クライアントに関連付けられたセキュリティ コンテキストは、通 常、JWT としてリクエスト毎に使われる • JWTが次のマイクロサービスに伝播、送信される 7

Slide 8

Slide 8 text

JWTの中に⼊れる情報 8 Propagate ID ユーザーのアイデンティティ Propagate User Entitlement ユーザーの属性や資格 Transfer ユーザーデータ以外の ⾊々なデータを サーバー間で安全に伝達できる

Slide 9

Slide 9 text

9

Slide 10

Slide 10 text

JSON Web Token (JWT) 定義: (RFC 7519, https://tools.ietf.org/html/rfc7519より) • 情報(Claim)を伝達する⽅法 • JWT内のClaimはJSONオブジェクトとしてエンコードされる • エンコードは、URL Safeな形式(“/”や”?”を使わずURLの⼀部として送れる) • エンコードされたデータは • JSON Web Signature (JWS) 構造のペイロードとして署名または、 • JSON Web Encryption (JWE)構造の⽂字データとして署名・暗号化される 10

Slide 11

Slide 11 text

JWT Trust Model • JWTの作成時に、発⾏者が、使⽤前に、秘密鍵でJWTに署名する • JWTの受け取り側(マイクロサービス)は⼀致する公開鍵を使って JWTを検証する • 検証した情報(claim)は信頼できる • 各種の署名、暗号化のアルゴリズムをサポート • JWTは、必ずHTTPSで送る 11

Slide 12

Slide 12 text

JWT, JWS, JWE, JWKの違いは何︖ 12 • 署名付き(Signed) JWTは、JWS (JSON Web Signature). • 暗号化された(Encrypted)JWTは、JWE (JSON Web Encryption). • 実際、オブジェクトは JWS または JWE のいずれか • JWT は抽象クラスのようなもので、JWS と JWE がセキュアな実装 • JSON Web Key (JWK) は、暗号鍵を表す JSON オブジェクトです • この鍵は、JWE を復号化、または JWS が署名を検証するために使⽤されます (JWT 発⾏者の公 開鍵が含まれています) • JWKキーセット(JSON形式で表されたキーのグループ JWKのArray)というものもある • 詳しくは JSON Object Signing and Encryption spec (JOSE) を参照 • https://tools.ietf.org/html/rfc7520

Slide 13

Slide 13 text

署名済みJWT (JWS) 署名済み JWT は JWS と呼ばれます。ドット (.) で区切られた 3 つの base64 でエンコードされた JSON オブジェク ト (ヘッダー、ペイロード、署名) で構成されます。 Sample decoded JWT { "typ": "JWT", "alg": "RS256" }. { "aud": "server", "iss": "https://ibm.com/oidc/endpoint/OP", "iat": 1311281970, "exp": 1311283970, "sub": “tom tom”, “email": “[email protected]”, }. {JWTの署名} Base64 URL encoded ヘッダー Base64 URL encoded ペイロード Base64 URL encoded 署名 . . 13 このRS256は、発⾏者(Issuer)の秘密鍵 によって署名が作成されていることを 表します。受け取り側は発⾏者の公開 鍵によって、署名を検証します。

Slide 14

Slide 14 text

暗号化済みJWT (JWE) 14 • 暗号化された JWS • ドット (.) で区切られた 5 つの Base64 URL エンコードされた JSONオブジェクト • ヘッダー、暗号化鍵、初期化ベクトル、暗号⽂、および認証タグ ヘッダー JWE Header 暗号化鍵 Encrypt Key 初期化ベクトル Initialization vector 暗号化された内容 Encrypted JWS 認証タグ Authentication tag . . . . 複雑な仕組みです︕ 1) 発⾏者が JWS とランダムな⼀時キーを作成 2) 発⾏者は、受信者の公開鍵を使⽤して⼀時的な鍵を暗号化し、「暗号化鍵」と して JWE に保存します (* 受信者の公開鍵へのアクセスが必要です*) 3) 受信者は独⾃の秘密鍵を使⽤して暗号化鍵を復号化します 4) 受信者は暗号⽂を復号化して JWS を取得します

Slide 15

Slide 15 text

JWTトークンの中⾝をみてみる https://jwt.io/#debugger-io にトークンを張り付けてみる

Slide 16

Slide 16 text

JWTトークンを⼊⼿するには • JWT は、サーバー間通信で、信頼のおけるサーバーが発⾏できる。こ の場合、呼ばれた側のサーバーは、呼び出し側サーバーによって保証 されたトークンを信頼する • セキュリティ リバース プロキシ サーバーもJWTを発⾏できる。この リバースプロキシサーバーは、ログイン後の JWT 作成をサポートする • JWT は、信頼できる OpenID Connect プロバイダー (OP) によっても 発⾏できる (例: Identity Managers, Ping, Azure, Keycloak) 16

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

MicroProfile JWT (MP-JWT) マイクロサービスにおいて、セキュリティ トークンとして JWT を使⽤する際の相互運⽤性を促進する仕様 仕様の詳細は下記のリリースノートを参照ください https://microprofile.io/project/eclipse/microprofile-jwt-auth/spec/src/main/asciidoc/release-notes.asciidoc 18 バージョン 内容 MP-JWT 1.0 相互運⽤可能な JWT トークン形式を定義 トークン アクセス API を定義 MP-JWT 1.1 MicroProfile Config を使⽤するポータブル JWT 構成を定義 JSON Web Key (JWK) のサポート MP-JWT 1.2 JWT を Cookie に含めることができます 署名のアルゴリズム追加 MP-JWT 2.0 JakartaEE対応

Slide 19

Slide 19 text

MicroProfile JWTに必要な情報 19 相互運⽤のために、必要な情報を定義. 太字は必須、太字でないものは推奨 {//ヘッダー “typ”: “JWT”, //トークンのタイプ “alg”: “RS256”, // 署名のアルゴリズム } {{//Claim “iss”: “https://server.example.com”, //発⾏者(Issuer) “aud”: “s6BhdRkqt3”, //トークンが対象とする受信者(audience) “jti”: “a-123”, //トークンのID(Identifier) “exp”: 1311281970, //トークンの期限(Expiration) “iat”: 1311280970, //トークン発⾏時(IssuedAt) “sub”: “24400320”, //ユーザーのSubject (java.security.Subject) "upn": “[email protected]", //ユーザーPrincipal (java.security.Principal) “groups”: [“red-group”, “green-group”], //ユーザーが所属するグループ(認可に使われる) “custom-value”: “Javaユーザーグループ” //アプリに必要な情報があればClaimにして送ることができる } { //署名 } (https://github.com/eclipse/microprofile-jwt-auth/pull/191 for MP-JWT1.2より)

Slide 20

Slide 20 text

MicroProfile JWTのAPI 20 JWTトークンにアクセスするためのInterfaceやClaimの名前などの定義 https://download.eclipse.org/microprofile/microprofile-jwt-auth-2.0/apidocs/

Slide 21

Slide 21 text

関連するMicroProfile Config 21 下記の構成は、MicroProfile Configの中のMP-JWT関連のもの https://github.com/eclipse/microprofile-jwt-auth/blob/master/spec/src/main/asciidoc/configuration.asciidoc mp.jwt.token.header mp.jwt.token.cookie mp.jwt.verify.audiences mp.jwt.decrypt.key.location mp.jwt.verify.publickey.algorithm mp.jwt.verify.publickey mp.jwt.verify.publickey.location mp.jwt.verify.issuer … 構成を別にまとめて、コードから参照することで、コードを変更せず、構成を変えるだけで、 サービスがよりポータブルになります

Slide 22

Slide 22 text

トークンの作り⽅は︖(start.microprofile.io) import io.vertx.ext.auth.JWTOptions; import io.vertx.ext.auth.PubSecKeyOptions; import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.jwt.JWTAuthOptions; … private static String generateJWT(String key) { JWTAuth provider = JWTAuth.create(null, new JWTAuthOptions() .addPubSecKey(new PubSecKeyOptions() .setAlgorithm("RS256") .setSecretKey(key) )); MPJWTToken token = new MPJWTToken(); token.setAud("targetService"); token.setIss("https://server.example.com"); // Must match configuration values token.setUpn("Jessie"); token.addAdditionalClaims("custom-value", "Jessie specific value"); token.setGroups(Arrays.asList("user", "protected")); return provider.generateToken(new io.vertx.core.json.JsonObject().mergeIn(token.toJSONString()), new JWTOptions().setAlgorithm("RS256")); } 22

Slide 23

Slide 23 text

トークンの作り⽅は︖(Quarkus) import io.smallrye.jwt.build.JwtClaimsBuilder; public static String generateToken(String username, Set roles, Long duration, String issuer) throws Exception { String privateKeyLocation = "/privatekey.pem"; PrivateKey privateKey = readPrivateKey(privateKeyLocation); JwtClaimsBuilder claimsBuilder = Jwt.claims(); long currentTimeInSecs = currentTimeInSecs(); Set groups = new HashSet<>(); for (Role role : roles) groups.add(role.toString()); claimsBuilder.issuer(issuer); claimsBuilder.subject(username); claimsBuilder.issuedAt(currentTimeInSecs); claimsBuilder.expiresAt(currentTimeInSecs + duration); claimsBuilder.groups(groups); return claimsBuilder.jws().signatureKeyId(privateKeyLocation).sign(privateKey); } 23

Slide 24

Slide 24 text

トークンの作り⽅は︖(OpenLiberty) import com.ibm.websphere.security.jwt.JwtBuilder; import com.ibm.websphere.security.jwt.Claims; private String buildJwt(String userName, Set roles) throws Exception { return JwtBuilder.create("jwtFrontEndBuilder") .claim(Claims.SUBJECT, userName) .claim("upn", userName) .claim("groups", roles.toArray(new String[roles.size()])) .claim("aud", "systemService") .buildJwt() .compact(); https://openliberty.io/docs/latest/reference/config/jwtBuilder.html 24

Slide 25

Slide 25 text

トークンの送り⽅(1) サーバーがAuthorizationヘッダーにJWTトークンを⼊れて送っているコード WebTarget target = ClientBuilder.newClient().target(serviceB); Response response = target.request().header("authorization", "Bearer " + jwt).buildGet().invoke(); 25 JWTトークンは、Authorizationヘッダーの中に、Bearerトークンとして送る HTTP Requestの例 GET /endp/echo HTTP/1.1 Host: server.example.com Authorization: Bearer レスポンス例(認証されて、相⼿サービスからGreetingが来たとき) HTTP/1.1 200 OK Hello, [email protected]

Slide 26

Slide 26 text

トークンの送り⽅(2) JWTトークンをクッキーにするコード(レスポンスとして送付) String jwt = tokenProvider.generateToken(...); Cookie cookie = new Cookie(“Bearer”, jwt); //mp.jwt.token.cookieで構成できる cookie.setHttpOnly(true); cookie.setMaxAge(ExpirationTime); cookie.setSecure(true); response.addCookie(cookie); 26 サーバーが、JWTトークンを作って、Cookieにして返しているコード HTTP Requestの例 GET /endp/echo HTTP/1.1 Host: server.example.com Cookie: Bearer= JWTトークンは、リクエストの中のCookieとして送付することもできる

Slide 27

Slide 27 text

MicroProfile JWT – 認証の仕⽅ MP-JWT は認証トークンであり、ロールに直接マップできるグループ属性が含まれています。 セキュリティ ロール名とグループ名が同じ場合、 @RolesAllowed アノテーションが使える @Inject @Claim("custom-value") private ClaimValue custom; @GET @RolesAllowed(“protected") public String getJWTBasedValue() { if (custom != null) { return "Protected Resource; Custom value : " + custom.getValue(); } } またトークンのオブジェクトを、APIで読み込んで、Claimを検証することもある 27

Slide 28

Slide 28 text

アプリからのアクセス アプリケーションは、SecurityContext アノテーションから JsonWebToken にアクセスできる • 次の例では、UserPrincipalを org.eclipse.microprofile.jwt.JsonWebToken API のインスタンスとしてキャストして、 アプリケーションは JsonWebToken ゲッターを介してすべてのクレームにアクセスできます @GET @Path("/getGroups") public Set getGroups(@Context SecurityContext sec) { Set<= null; Principal user = sec.getUserPrincipal(); if (user instanceof JsonWebToken) { JsonWebToken jwt = (JsonWebToken) user; groups= = jwt.getGroups(); } return groups; } アプリケーションは、Raw Type、ClaimValue、javax.inject.Provider、および JSON-P タイプを介して、 org.eclipse.microprofile.jwt.JsonWebToken API を直接注⼊することもできます @Inject private JsonWebToken jwt; @Inject @Claim(standard= Claims.raw_token) private String rawToken; @Inject @Claim("iat") private Long dupIssuedAt; @Inject @Claim("sub") private ClaimValue> optSubject; 28

Slide 29

Slide 29 text

29

Slide 30

Slide 30 text

Web リソースと サービス トークンでサー ビスをリクエス トする(JWT) JWT クレームに基づい て認証決定を⾏う アプリケーション・サーバー クライアント アプリケー ション • クライアントアプリがJWTを作成し、JWTでサービスを呼び出す • HTTP client: CLI, Restful Service Client ユースケース: シンプルな HTTPS クライアント 30

Slide 31

Slide 31 text

フロントエンド Web アプリケー ション、またはマ イクロサービス トークンでサー ビスをリクエス トする(JWT) マイクロサービス JWT JWT クレームに基づ いて認証決定を⾏う アプリケーション・サー バー 1. ユーザー要求は常に認証リバース プロキシを通過する (エンタープライズ アプリケーションでは⼀般的な構成) 2. サービスは JWT を検証し、サブジェクトを作成する 3. サービスはサブジェクトで、リクエストを承認する 4. サービスは、JWT (オリジナルまたは⾃⼰発⾏) を他のサービスに伝搬する リバース プロキシ サーバー ユーザーの認証 JWT の提供 • ユーザー認証はリバース プロキシ サーバーによって実⾏され、既存の認証サービスと連携する場合があります • リバース プロキシ サーバーは、ユーザーに代わって JWT をセキュリティ トークンとして提供します リバース プロキ シ サーバー経由 で Web を閲覧 する ユースケース: リバース プロキシ セキュリティ サーバー ブラウザ/ クライアント 31 JWT クレームに基づ いて認証決定を⾏う

Slide 32

Slide 32 text

JWT を使⽤したシングル サインオン⽤の Open ID Connect ユースケース: Open ID Connect プロバイダー/クライアント ブラウザ/ クライアント アプリケーションが サーバー付属の OIDC クライアント機能 (Replying Party) を使⽤ OIDCプロバイダ(OP) マイクロサービス1 JsonWebToken は、CDI または JAX-RS SecurityContext を介してアクセスできます。追加の認証に JWT を使⽤する か、JWT を別のサービスに伝播します マイクロサービス2 propagate JWT 認可サーバー︓アクセストークン(JWT)とIDトークン (JWT)を発⾏ 1 2 3 4 5 6 propagate JWT リソース サーバー: バックエンド リポジトリへの認証を実 ⾏します • LDAP based with username or client cert. • Use of SAML external identity provider • Use of external OIDC provider • Use of social medium (ie: Facebook, google) • Use of TAI (trust association interceptor) 7 1. ユーザーは アプリケーションサーバーの OIDC認証機能を使⽤して、使⽤するアプリ にログイン 2. アプリケーションサーバーは、OIDCクライ アント(RP)として振舞い、ユーザーを OIDCプロバイダ(OP)にリダイレクトする 3. RPは、認証コード、IDトークン、JWTアク セス・トークンなどをOPと交換する 4. アプリは、別アプリ(アプリ1)にリクエス トを送る(JWTが伝搬される) 5. アプリ1をホストしているサーバーがJWTを評 価し、サブジェクトとJWTを作成 6. アプリ1をホストしているサーバーはJWTを認 可して、JWTを使ってアプリ2を呼び出し 7. . アプリ2をホストしているサーバーがJWTを 評価し、サブジェクトとJWTを作成 32

Slide 33

Slide 33 text

JWTトークン使⽤のシステム事例 User A モバイルデバイス ピーク時には ⼀分あたり 14000 リクエスト アクセスマネージャー • ログイン認証 • JWT トークンを作成 リクエストは契約タイプに よって4つのFunctionalID にマップされる User B User C User D User A 認証キャッシュ User B User C User D ユーザーレジストリ ユーザー検証は • トークン期限切れ • キャッシュタイム アウト キャッシュでスルー プットを改善 アプリケーションサーバー

Slide 34

Slide 34 text

Demo

Slide 35

Slide 35 text

デモのプログラム(MicroProfile starter) Demo OpenLiberty MP 5.0 Demo Quarkus MP 3.2 start.microprofile.ioから、デモプログラムをダウンロードします。 異なるランタイム、異なるMicroProfileバージョンでのJWTの認証を実際に⾒てみます。

Slide 36

Slide 36 text

デモの流れ ブラウザー Service-a localhost:8080 Quarkus Service-b localhost:8180 OpenLiberty JWT グループ値 カスタム値 アクセスの結果を返す 0. Quarkus上で、Service-aを起動、OpenLiberty上で、Service-bを起動する 1. ブラウザーから、デモ⽤のサンプル起動⽤アプリを開く http://localhost:8080/ 2. アプリからService-aのエンドポイントにアクセスする http://localhost:8080/data/secured/test 3. Service-aのエンドポイントで、JWTトークンを作成、セキュアなService-bのエンドポイントにリクエスト送信 http://localhost:8180/data/protected 4. Service-bは、JWTトークンを評価して、アクセス権限がある時のみ、トークンのカスタム値をService-aに返す 5. Service-aは、結果をブラウザに表⽰ アクセスの結果を返す

Slide 37

Slide 37 text

MicroProfileのStarterデモは、service-aとservice-bで構成 Quarkusのservice-aをdevモードでスタート > cd service-a > mvn compile quarkus:dev ブラウザからservice-aにアクセスするURL http://0.0.0.0:8080 (localhost) service-aはポート8080で動く Service-aをQuarkusでスタート

Slide 38

Slide 38 text

Service-bをOpenLibertyでスタート OpenLibertyのservice-bをdevモードでスタート > cd service-b > mvn liberty:dev ブラウザからservice-bにアクセスするURL http://172.25.154.129:8180 (これもlocalhost) service-bはポート8180で動く Startup messages…

Slide 39

Slide 39 text

デモのノート • デモで使⽤したMicroProfileのStarterのサンプルは、簡単に動いて勉強に お勧めです︕ • 違うランタイム間で動かすときは、ポートを変える必要があるかもしれません • Quarkusのサービスbは、ポート8180動いていました • OpenLibertyのサービスbは、ポート9080をListenしていました • OpenLibertyの構成を、9080から8180をListenするように変えました • コードと構成はGithubにあります https://github.com/una-tapa/MicroProfileDemo OpenLibertyのserver.xml httpsPort="9444"/>

Slide 40

Slide 40 text

今回のトークのまとめ • クラウド環境で、マイクロサービスをセキュアに動かすには、JWT トークンを使う • マイクロサービスは、ステートレスなので、セキュリティのコンテ クストは、リクエストのたびに、毎回JWTトークンを送る • MP-JWTは、マイクロサービス同⼠の相互運⽤のための仕様 • 認証されたユーザーはUserPrincipalで表され、認可はGroupで⾏われる • MP-JWTには、JWTトークンを簡単に使える仕組みが⽤意されている

Slide 41

Slide 41 text

41

Slide 42

Slide 42 text

@Path("/client-test1") public class ClientTestDefault { @GET @Produces(MediaType.TEXT_PLAIN) public String ping() { Client client = ClientBuilder.newBuilder().build(); WebTarget webTarget = client .target("http://localhost:9081/endpoint"); String output = webTarget.request().get(String.class); client.close(); return output; } } 左のコードは、JakartaのRestfulクライアントか らサーバーにリクエストを送るコードです。サン プルにも同様のコードがありました。 ClientBuilderで、送信先のWebTargetを作成して リクエストを送っています。 Jakarta RESTful クライアントのパフォーマンス改善

Slide 43

Slide 43 text

@Path("/client-test2") public class ClientTestCached { private static WebTarget cachedWebTarget = ClientBuilder.newBuilder().build() .target("http://localhost:9081/endpoint"); @GET @Produces(MediaType.TEXT_PLAIN) public String ping() { return cachedWebTarget.request().get(String.class); } } パフォーマンス裏技 記事(⽇本語) https://community.ibm.com/community/user/wasdevops/blogs/kaori- asada/2022/08/17/microservices?CommunityKey=d6c93aa2-6e10-48da-96dc-3831da8ee185 前のページと、コードがよく似ていますが、 WebTarget をstaticで作ると、再利⽤でき、ク ライアントを閉じる必要がありません。 これだけの違いで、JakartaのRESTfulクライア ントのスループットが3倍になったそうです。 Jakarta RESTful クライアントのパフォーマンス改善

Slide 44

Slide 44 text

ありがとうございました︕ ご感想、フィードバックなどお待ちしています E-mail: [email protected] Twitter: @htakamiy