Upgrade to Pro — share decks privately, control downloads, hide ads and more …

與 Sign in with Apple 的愛恨情仇 @ iPlayground2020

Johnny Sung
November 08, 2020

與 Sign in with Apple 的愛恨情仇 @ iPlayground2020

注重用戶隱私的 Apple 在開發者大會 WWDC 2019 發表了 Sign in with Apple 的功能,iOS 13 後皆支援。今年(2020 年) 4 月之後強制要求所有新上架的 App(包含更新)皆要支援這個服務。

Sign in with Apple 怎麼接?跟其他第三方服務有什麼不同?

App 端、網站前端、後端要如何因應?會有什麼樣的雷?
讓我來跟你娓娓到來...

https://blog.jks.coffee/sign-in-with-apple/

#iPlayground2020

Johnny Sung

November 08, 2020
Tweet

More Decks by Johnny Sung

Other Decks in Programming

Transcript

  1. ⼤綱 • Sign in with Apple 的介紹 • iOS App

    端流程 • 網站端流程 • 關於虛擬 E-mail • 蘋果開發者網站設定 • iOS、網站、後端 實作概念
  2. Sign in with Apple • 使⽤ Apple ID 登入,強化保護您的隱私權 •

    當⽤⼾不願給出⾃⼰的 E-mail 時,蘋果可以⽣成⼀組「虛擬 E-mail」給⽤⼾使⽤。 • 「虛擬 E-mail」收到的資訊,蘋果會代轉給⽤⼾的「真實 E-mail」
  3. App 登入 1. 使⽤者按下 Sign in with Apple 按鈕, 呼叫相關

    AuthenticationServices framework 的相關函式 2. iOS 系統跳出 AppleId 登入確認,利⽤ TouchId 指紋辨識 或 FaceId 臉部辨識 (可選擇是否隱藏其 E-mail) 3. 當登入授權成功時,系統呼叫 didCompleteWithAuthorization 處理該 Callback 相關參數 4. ⽤ API 傳回⾃⼰伺服器做驗證 https://support.apple.com/library/content/dam/edam/applecare/images/zh_TW/appleid/ios14-iphone-11-pro-sign-in-with-apple.jpg
  4. 網站登入 1. 使⽤者按下 Sign in with Apple 按鈕, 呼叫蘋果提供的 JavaScript

    帶⼊ Redirect URI(轉址位址) 2. 使⽤者在 Apple 網站進⾏登⼊,輸⼊ AppleId 的帳號密碼, 並授權開發商授權允許(可選擇是否隱藏其 E-mail ) 3. 當登⼊授權成功時,轉址回設定之 Redirect URI 並附上參數 4. ⽤ API 傳回⾃⼰伺服器做驗證
  5. 蘋果開發者網站設定 1. App ID (iOS app 使⽤) 2. Service ID

    (網站使⽤) 3. Sign Key (後端驗證使⽤) 4. Email 寄件⼈ & 網域 (Email relay service 使⽤)
  6. 所需材料 (1/2) • Service ID Certificates, Identifiers & Profiles ->

    Identifiers -> Service IDs • App ID (Bundle ID) Certificates, Identifiers & Profiles -> Identifiers -> App IDs 範例值: - Name: SignAppleDemo - AppID: com.johnny.signapple 範例值: - Name: SignAppleDemo - AppID: com.johnny.websignapple
  7. 所需材料 (2/2) • Sign Key Certificates, Identifiers & Profiles ->

    Keys 範例值: - Name: SignAppleKey - KeyID: D2XXXXX7KY - p8 file: -----BEGIN PRIVATE KEY----- EGCCqGSM49AwEHBHkwdwIBA -----END PRIVATE KEY-----
  8. 材料整理 • Client ID • iOS App - App ID

    (Bundle ID) • 網站 - Service ID • Redirect URI • iOS ⽤的 API endpoint • 網站 API endpoint • Team ID • Sign Key & Key ID
  9. 設定 SPF (DNS Sender Policy Freamwork) "v=spf1 include:_spf.google.com include:sendgrid.net include:amazonses.com

    ~all" 新增 DNS TXT Record 郵件服務 Email Service Provider (ESP) • Google Gmail • SendGrid • Amazon SES
  10. • 樣式:⿊⾊ vs. ⽩⾊有邊框 vs. ⽩⾊無邊框 • 字樣:Sign in with

    Apple vs. Continue with Apple vs. 無 • 圓⾓:有圓⾓ vs. 無圓⾓ 登入按鈕樣式 https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/
  11. iOS App 部分 1. 使⽤者按下 Sign in with Apple 按鈕,

    呼叫相關 AuthenticationServices framework 的相關函式 2. iOS 系統跳出 AppleId 登入確認,利⽤ TouchId 指紋辨識 或 FaceId 臉部辨識 (可選擇是否隱藏其 E-mail) 3. 當登入授權成功時,系統呼叫 didCompleteWithAuthorization 處理該 Callback 將相關參數,⽤ API 傳回⾃⼰的網站
  12. import Foundation import AuthenticationServices class AppleSignInManager: NSObject { var currentView:

    UIView? func signIn(currentView: UIView) { guard #available(iOS 13.0, *) else { return } self.currentView = currentView let provider = ASAuthorizationAppleIDProvider() let request = provider.createRequest() request.requestedScopes = [.email, .fullName] request.nonce = "[NONCE]" request.state = "[STATE]" let controller = ASAuthorizationController(authorizationRequests: [request]) controller.delegate = self controller.presentationContextProvider = self controller.performRequests() } } iOS 實作片段 (1/3) 辨識⽤的值,避免 CSRF 攻擊
  13. extension AppleSignInManager: ASAuthorizationControllerDelegate { @available(iOS 13.0, *) func authorizationController(controller: ASAuthorizationController,

    didCompleteWithError error: Error) { // Handle error print(error) } @available(iOS 13.0, *) func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return } // Post identityToken & authorizationCode to your server print(String(decoding: credential.identityToken ?? Data.init(), as: UTF8.self)) print(String(decoding: credential.authorizationCode ?? Data.init(), as: UTF8.self)) } } iOS 實作片段 (2/3) 要處理請求失敗 請求成功,⽤ API 回傳 id_token 與 auth_code 回您的 Server
  14. extension AppleSignInManager: ASAuthorizationControllerPresentationContextProviding { @available(iOS 13.0, *) func presentationAnchor(for controller:

    ASAuthorizationController) -> ASPresentationAnchor { return self.currentView?.window ?? UIApplication.shared.keyWindow! } } iOS 實作片段 (3/3) 要放入⽬前所在 ViewController 的 window
  15. • 樣式:⿊⾊ vs. ⽩⾊有邊框 vs. ⽩⾊無邊框 • 字樣:Sign in with

    Apple vs. Continue with Apple vs. 無 • 圓⾓:有圓⾓ vs. 無圓⾓ 登入按鈕樣式 https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/
  16. <html> <head> </head> <body> <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script> <div id="appleid-signin" data-color="black"

    data-border="true" data-type="sign in"></div> <script type="text/javascript"> AppleID.auth.init({ clientId: '[CLIENT_ID]', scope: 'name email', redirectURI: '[REDIRECT_URI]', state: '[STATE]', nonce: '[NONCE]', usePopup: false }); </script> </body> </html> 網站實作片段 (1/2) 辨識⽤的值,避免 CSRF 攻擊 設定轉跳的網址 使⽤彈窗模式? 填入 Service ID
  17. async function doAppleSignIn() { try { const data = await

    AppleID.auth.signIn(); console.log(data); } catch (error) { //handle error. } } 網站實作片段 (2/2)
  18. • RFC 7519 - JSON Web Token(JWT) 定義了 header 內容與

    claim 內容,以及 token 的相關規範 • RFC 7515 - JSON Web Signature(JWS) 定義如何做帶有簽章的 token • RFC 7516 - JSON Web Encryption(JWE) 定義內容加密的 token • RFC 7517 - JSON Web Key(JWK) 定義⾦鑰的格式 • RFC 7518 - JSON Web Algorithms(JWA) 定義加解密的演算法 https://ithelp.ithome.com.tw/articles/10224787 JWT? JWS? JWK? JWA? - JWS 與 JWE 都是屬於 JWT 的⼀種。 - 如果沒特別說明,則 JWT 皆是指 JWS。 https://hlqft4a0qsn1cy1gi1bevyhj-wpengine.netdna-ssl.com/wp-content/uploads/2019/05/key-types-600x453.jpg
  19. https://twitter.com/irina_papuc/status/900085165568937984/photo/1 base64UrlEncode({ "alg": "RS256",
 "typ": "JWT" }) base64UrlEncode{{ "sub": "1234567890",

    "name": "John Doe", "iat": 1516239022 }) HMACSHA256(
 base64UrlEncode(header) + "." +
 base64UrlEncode(payload)
 , secretKey) . . Header Algorithm & token type Payload Data Signature Verification Json Web Token https://19yw4b240vb03ws8qm25h366-wpengine.netdna-ssl.com/wp-content/uploads/Why-Cant-I-Just-Send-JWTs-Without-OAuth-JWT.png
  20. { "iss": "https://appleid.apple.com", "aud": "com.johnny.signapple", "exp": 1596621649, "iat": 1596621049, "sub":

    "001451.3dc43615849bcf03b2cce4e48048a59f.0447", "c_hash": "iUqI9RgFbplkcW9Org-CyoA", "email": "[email protected]", "email_verified": "true", "is_private_email": "true", "auth_time": 1596621049, "nonce_supported": true } { "kid": "86D88Kf", "alg": "RS256" } eyJraWQiOiI4NkQ4OEtmIiwiYWxnIjoiUlMyNTYifQ Header Algorithm & token type eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkI joiY29tLjkxcHUhQ5LCJpYXQiOjE1OTY2MjEwNDksInN1YiI6IjAw MTQ1MS4zZGM0MzYxNTVmYjA0NjAzYjJjY2U0ZTQ4MDQ4YTU5Zi4wN Q3lvQSIsImVtYWlsIjoiOkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZX JpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSI sImF1dGhfdGltZSI6MTU5NjYyMTA0OSwibm9uY2Vfc3VwcG9ydGVk Ijp0cnVlfQ Payload Data Signature Verification KccMlP7OoIctkaEzy09pJK0t-uzwkT-u3UT7BNWJ1mS2mU_FXH- pKTjnyO7Whd9vh-pBQdVU5vZ7kKeD3rMqGi- kKkLmNO1yjBse2cnfs7WLthtrhrthnbi1iDxHvedFjBOUhOzfcFfwn4- CLSUZcb7aukHd0hj0NYzigBWzFAbxDmYtM7hlqQmJaYBh6RzYfwMOxK58 HRcVnx9wy3vzTFfj25x6nqMGxosVXkD0N0PP1b30uem8tyS4wSu3w5zoE 2Bpaos_fhattM_p28WdcrA4Ne89ET8D5GYSe448VlIyXSonfHBveE5- hSVQAzIjP4B_JOCcRBpM3Q
  21. JWT • Header • alg - 加解密演算法 JWA (JSON Web

    Algorithms) • "HS256" (HMAC-SHA256) • "RS256" (RSA-SHA256) • "ES256" (ECDSA-SHA256) • typ - JWT 本⾝的媒體類型 ⽤預設值 "JWT" • kid - Sign Key ID { "kid": "86D88Kf", "typ": "JWT" "alg": "RS256" } Header Algorithm & token type
  22. JWT • Payload • iss - 發⾏者 ( Issuer )

    • aud - 接收者 ( Audience ) • iat - 發⾏時間 ( Issued at (time) ) ⽤ Unix timestamp 表⽰ • exp - 過期時間 ( Expiration (time) ) ⽤ Unix timestamp 表⽰ • sub - 主題 ( Subject ) ⽤ URI 表⽰唯⼀識別訊息 • Signature • 非對稱演算法加密(Header + "." + Payload) { "iss": "https://appleid.apple.com", "aud": "com.johnny.signapple", "iat": 1596621049, "exp": 1596621649, "sub": "001451.3dc436158........9f.0447", "c_hash": "iUqI9RgFbplkcW9Org-CyoA", "email": "[email protected]", "email_verified": "true", "is_private_email": "true", "auth_time": 1596621049, "nonce_supported": true } Payload Data
  23. 步驟 1. 接收從 client 傳來的 IdentityToken (JWT) ( 蘋果 會⽤它的私鑰加密

    IdentityToken 的簽章部分 ) 2. 呼叫 /auth/keys 取得 蘋果 公鑰 (JWK) 3. 整理 JWK 成 pem 格式 4. ⽤ 蘋果 公鑰 來驗證 client 傳來的 IdentityToken 5. 從 IdentityToken 的 payload 取得資料
  24. { "keys": [ { "kid": "86D88Kf", "kty": "RSA", "use": "sig",

    "alg": "RS256", "n": "iGaLqP6y-SJCCBq5H.....Yw-zHLwQ", "e": "AQAB" }, { "kty": "RSA", "kid": "eXaunmL", "use": "sig", "alg": "RS256", "n": "4dGQ7bQK8LgI...........JNdUhxw", "e": "AQAB" } ] } 取得蘋果公鑰 • API Endpoint GET https://appleid.apple.com/auth/keys • 內容為標準 JWK (JSON Web Key) 格式 • kid - ⾦鑰 ID • kty - Key Type ⾦鑰類型 • alg - 演算法 值 "RS256" 為 RSA-SHA256 演算法 • use - ⽤途描述 值 "sig" 意思是 數位簽章 • n - RSA 模數 (Modulus) 為公鑰內容的⼀部分,內容為 base64urlUInt 編碼後的結果 • e - RSA 指數 (Exponent) 為公鑰內容的⼀部分,內容為 base64urlUInt 編碼後的結果 回傳結果
  25. require_once '../vendor/autoload.php'; function getUserDataFromIdentityToken($idToken) { $token = (new Lcobucci\JWT\Parser())->parse((string)$idToken); $applePublicKeysRaw

    = curlGetAppleAuthKeys(); $applePublicKeys = JWKParseKeySet($applePublicKeysRaw); $applePublicKey = $applePublicKeys[$token->getHeader('kid')]; $signer = new Lcobucci\JWT\Signer\Rsa\Sha256(); $keychain = new Lcobucci\JWT\Signer\Keychain(); if (!$token->verify($signer, $keychain->getPublicKey($applePublicKey))) { throw new RuntimeException("Key validation failed."); } if ('https://appleid.apple.com' !== $token->getClaim('iss')) { throw new RuntimeException("Source incorrect."); } $userData = array(); $userData['email'] = $token->getClaim('email'); $userData['id'] = $token->getClaim('sub'); return $userData; } 驗證 JWT 的 Signature 找到指定的公鑰 擷取 JWT 的 Payload 內容 抓取蘋果的公鑰
  26. function curlGetAppleAuthKeys() { $ch = curl_init('https://appleid.apple.com/auth/keys'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result

    = curl_exec($ch); curl_close($ch); return json_decode($result, true); } function JWKParseKeySet($keySets) { $parsed = \Firebase\JWT\JWK::parseKeySet($keySets); $pemKeySets = array(); foreach ($parsed as $keyId => $sslKey) { $pemKeySets[$keyId] = openssl_pkey_get_details($sslKey)['key']; } return $pemKeySets; } function JWKVerify($idToken, $publicKeyPem) { $signer = new Lcobucci\JWT\Signer\Rsa\Sha256(); $keychain = new Lcobucci\JWT\Signer\Keychain(); $token = (new Lcobucci\JWT\Parser())->parse((string)$idToken); return $token->verify($signer, $keychain->getPublicKey($publicKeyPem)); } composer.json { "require": { "firebase/php-jwt": "5.2.0", "lcobucci/jwt": "3.3.2" } }
  27. -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiGaLqP6y+ SJCCBq5Hv6p GDbG/ SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3avoVNM8jXevG1Iu1SY 11qInq UvjJur+ +hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOw

    CPyg jLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQtiLk+ ILjv1bORSRl 8AK677+1T8isGfHKXGZ/ ZGtStDe7Lu0Ihp8zoUt59kx2o9uWpROkzF56ypresiIl 4WprClRCjz8x6cPZXU2qNWhu71TQvUFwvIvbkE1oYaJMb0jcOTmBR ZA2QuYw+zHL wQIDAQAB -----END PUBLIC KEY----- { "kty": "RSA", "kid": "86D88Kf", "use": "sig", "alg": "RS256", "n": "iGaLqP6y- SJCCBq5Hv6pGDbG_SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3av oVNM8jXevG1Iu1SY11qInqUvjJur-- hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOwC PygjLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQti Lk- ILjv1bORSRl8AK677-1T8isGfHKXGZ_ZGtStDe7Lu0Ihp8zoUt59k x2o9uWpROkzF56ypresiIl4WprClRCjz8x6cPZXU2qNWhu71TQvUF wvIvbkE1oYaJMb0jcOTmBRZA2QuYw-zHLwQ", "e": "AQAB" } JWKParseKey
  28. 步驟 1. 接收從 client 傳來的 AuthorizationCode 2. ⽤ 您的私鑰 製作

    client_secret 也是⼀個 JWT Token 3. 呼叫 /auth/token 的 API,得到 IdentityToken ( 蘋果 會⽤ 您的公鑰 解密驗證 client_secret 的簽章部分 ) ( 蘋果 會⽤它的私鑰加密 IdentityToken 的簽章部分 ) 4. ⽤ 蘋果 公鑰 來驗證 client 傳來的 IdentityToken(步驟同上述做法) 5. 解開 IdentityToken 的 JWT 取得資料
  29. • API Endpoint POST https://appleid.apple.com/auth/token • 傳送參數 • client_id -

    填入 Service ID (網站) 或 App ID (iOS App) • client_secret - ⼀個 JWT 格式的要求⽂件(詳) • code - Authorization code • grant_type - 授權⽅式 填 `authorization_code` 來交換 AccessToken 交換 AuthorizationCode
  30. { "access_token": "a6cab...........Y1A", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "r9d77fa9..........gfYA", "id_token":

    "eyJraW.................jMA" } • API Endpoint POST https://appleid.apple.com/auth/token • 回傳結果 • access_token - token,⽬前沒作⽤ • expires_in - 過期時間 • id_token - IdentityToken • refresh_token - 更新 access_token 時使⽤ 交換 AuthorizationCode 回傳結果
  31. • invalid_request • 通常是連參數都弄錯 • invalid_client • 可能是 client_id 弄錯

    • 可能是 client_secret (JWT 格式) 裡的 sub 參數跟 client_id 參數不吻合 Troubleshooting
  32. • invalid_grant • 可能是 client_secret (JWT 格式) 加解密算錯 ⚠ •

    可能是 Authorization Code 過期 ⚠ (Authorization Code 效期超短,通常只有 1-2 分鐘,不超過 5 分鐘) • 可能是 Authorization Code 已被使⽤,變成無效的 code ⚠ (API 帶相同參數重複打就會出現) • 其他錯誤訊息,請參考蘋果  API ⽂件: https://developer.apple.com/documentation/sign_in_with_apple/errorresponse Troubleshooting
  33. { "payload": "eyJraWQxxxxxxxxxxxxxxiUlMyNTYifQ.eyJpcxxxxxxxxx xxxxc0fSJ9.IUFWxPxxxxxxxxxxxxxxxxxbL3olA" } Server notification 我也是⼀個 JWT 哦

    ❤ 範例值: - API Endpoint: https://your.awesome.domain/handle_signin_notification.php 接收範例值 註:截稿至現在, Apple 還沒有⽂件,只有在 WWDC 2020 影⽚上提到
  34. - Introducing Sign In with Apple - WWDC2019 https://developer.apple.com/videos/play/wwdc2019/706/ -

    Get the most out of Sign in with Apple - WWDC2020 https://developer.apple.com/videos/play/wwdc2020/10173 - JWT 簽名算法 HS256、RS256 及 ES256 及密鑰⽣成 https://www.cnblogs.com/kirito-c/p/12402066.html - 驗證 JSON Web Token https://docs.aws.amazon.com/zh_tw/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html - Sign in with Apple http://uirate.net/?p=10363 - Ensure mail delivery & prevent spoofing (SPF) https://support.google.com/a/answer/33786 - DNS 設定 spf 記錄 - Sender Policy Framework https://blog.xuite.net/tolarku/blog/233356505 - Generate and Validate Tokens https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens - 簡介 JWK 與 JWA https://ithelp.ithome.com.tw/articles/10225590 - Sign in with Apple Tutorial, Part 4: Web and Other Platforms https://sarunw.com/posts/sign-in-with-apple-4/ - JWT 概觀 https://ithelp.ithome.com.tw/articles/10224787 - [Note] OAuth2.0 學習筆記 | PJCHENder 未整理筆記 https://pjchender.github.io/2017/11/16/note-oauth2-0-%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98/ 參考資料 (1/3)
  35. - 簡單易懂的 OAuth 2.0 https://speakerdeck.com/chitsaou/jian-dan-yi-dong-de-oauth-2-dot-0 - 關於OAuth 2.0-以Facebook為例 https://medium.com/@justinlee_78563/%E9%97%9C%E6%96%BCoauth-2-0-%E4%BB%A5facebook%E7%82%BA%E4%BE%8B-6f78a4a55f52 -

    [PHP] OAuth / Sign in with Apple JS - 使⽤ Apple JS SDK 讓網站⽀援 Apple ID 登入 http://blog.changyy.org/2019/11/php-jwt-oauth-sign-in-with-apple-js.html - What the Heck is Sign In with Apple? https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple - 如何整合 Sign in with Apple 到⾃⼰的 iOS App 上 (iOS & Backend) https://medium.com/@tuzaiz/%E5%A6%82%E4%BD%95%E6%95%B4%E5%90%88-sign-in-with-apple- %E5%88%B0%E8%87%AA%E5%B7%B1%E7%9A%84-ios-app-%E4%B8%8A-ios-backend-e64d9de15410 - Sign in with Apple(蘋果授權登陸) https://blog.csdn.net/wpf199402076118/article/details/99677412 - Sign in with Apple 登錄詳解 https://ihtcboy.com/2019/09/16/2019-09-16_Sign-in-with-Apple/ - iOS13 Sign In With Apple 適配 http://jerryliu.org/ios%20programming/iOS13-Sign-With-Apple%E6%96%B0%E7%89%B9%E6%80%A7%E9%80%82%E9%85%8D - 蘋果Sign In with Apple爆可被劫持帳號的漏洞 https://www.ithome.com.tw/news/137972 - 蘋果重磅推出「Sign in with Apple」登入,背後忽略的隱私盲點 https://www.bnext.com.tw/article/53765/what-is-the-meaning-of-sign-in-with-apple 參考資料 (2/3)
  36. - [筆記] 透過 JWT 實作驗證機制 https://medium.com/ %E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E7%AD%86%E8%A8%98- %E9%80%8F%E9%81%8E-jwt-%E5%AF%A6%E4%BD%9C%E9%A9%97%E8%AD%89%E6%A9%9F%E5%88%B6-2e64d72594f8 - 是誰在敲打我窗?什麼是

    JWT ? https://5xruby.tw/posts/what-is-jwt/ - 基礎密碼學-對稱式與非對稱式加密技術 https://medium.com/@RiverChan/%E5%9F%BA%E7%A4%8E%E5%AF%86%E7%A2%BC%E5%AD%B8- %E5%B0%8D%E7%A8%B1%E5%BC%8F%E8%88%87%E9%9D%9E%E5%B0%8D%E7%A8%B1%E5%BC%8F%E5%8A%A0%E5%AF%86%E6%8A%80%E8%A1 %93-de25fd5fa537 參考資料 (3/3)