
文章同步發表在我的個人 Blog
簽章到期之後,手機上的 App 還打得開嗎?重新安裝,為什麼還是失敗?
今天是雙十國慶日,中華民國生日快樂。昨天 Day 25 把匿名帳號綁定了 Google,今天遇到免費帳號必經的一關:簽章 7 天到期,要重新安裝。另外把昨天按下「綁定 Google」之後跳出來的授權畫面看懂,那一頁背後就是 OAuth。
先講結果:簽章是 10/9 下午 2:33 到期的,但之前四次重新安裝,用的都是 10/2 建立的同一份 profile,覆蓋安裝本來就不會延長期限。今天重新安裝時又遇到兩個錯誤:Xcode 裡沒有登入 Apple ID,以及 Mac 找不到太太的手機。在 Xcode 重新登入 Apple ID、兩支手機都接上 Mac 之後,重新安裝在兩支手機都成功,新的 profile 是 10/10 建立,10/17 到期。
免費的 Personal Team 簽章有效 7 天。要知道實際的期限,不用猜,可以直接讀 App 裡的 provisioning profile:
security cms -D -i Teammate.app/embedded.mobileprovision > profile.plist
plutil -extract ExpirationDate raw profile.plist
這個專案的 profile 內容:
| 欄位 | 值 |
|---|---|
| 名稱 | iOS Team Provisioning Profile: com.gooliya.teammate |
| 建立 | 2026-10-02 06:33:34Z |
| 到期 | 2026-10-09 06:33:34Z(台灣時間 10/9 下午 2:33) |
有效天數(TimeToLive) |
7 |
| 允許安裝的裝置 | 2 支 |
| entitlements | application-identifier、get-task-allow、keychain-access-groups 等 |
到期時間是 profile 建立時就決定的,跟 App 有沒有被重新安裝無關。
重新安裝腳本的紀錄裡,過去幾天的安裝時間是:
| 時間 | 動作 |
|---|---|
| 10/3 19:57 | 重新安裝 |
| 10/3 22:02 | 重新安裝 |
| 10/7 22:29 | 重新安裝 |
| 10/9 00:45 | 重新安裝 |
四次安裝,但 Xcode 存 profile 的資料夾裡,到 10/10 上午為止,這個專案只有兩份 profile(App 本體與分享擴充各一份),建立時間都是 10/2。也就是說,每一次安裝只是用同一份 profile 重新簽章,沒有產生新的。這個資料夾在今天重新安裝開始的同一分鐘被清空了,我沒有動它;看起來是 Xcode 發現 profile 已經過期,把它清掉。
所以「每隔幾天重新安裝一次」不會讓簽章保持有效,要等 profile 過期,Xcode 才會重新向 Apple 要一份新的。是否可以不等過期、提前換新,我還沒有驗證。
專案裡有一個 launchd 排程範本:每週三、週日凌晨 3:30 自動執行重新安裝腳本,檔案開頭寫「間隔 < 7 天」。我去檢查 ~/Library/LaunchAgents/,裡面沒有這個檔案,launchctl list 也找不到,所以重新安裝一直是手動做的。
就算排程裝了,也有同一個問題:它每 3、4 天重新安裝一次,用的還是同一份 profile,到期那一刻照樣失效。所以排程要解決的其實是「過期後自動換新」,不是「定期重新安裝」。
先看 scripts/reinstall-devices.sh 對每支手機做的事:
xcrun devicectl list devices 把 Config/devices.txt 裡的識別碼換成硬體 UDID。xcodebuild 建置 Release 版本,指定那支裝置為目的地,並帶上兩個旗標:-allowProvisioningUpdates 讓 Xcode 可以更新 profile,-allowProvisioningDeviceRegistration 讓 Xcode 可以把裝置註冊進 profile。xcrun devicectl device install app 安裝到手機。今天跑出兩個不同的錯誤。
你的手機:Xcode 沒有帳號
error: No Accounts: Add a new account in Accounts settings.
error: No profiles for 'com.gooliya.teammate' were found
** BUILD FAILED **
-allowProvisioningUpdates 要有帳號才能向 Apple 要新的 profile。Xcode 的 Settings → Accounts 裡沒有 Apple ID,所以建置失敗。這是 profile 過期後才會暴露的問題:過期之前,Xcode 用現成的 profile 就能簽章,用不到帳號。
太太的手機:Mac 找不到裝置
xcodebuild: error: Unable to find a destination matching the provided destination specifier
Available destinations for the "Teammate" scheme:
列出來的可用裝置裡只有我的手機,沒有她的。腳本的錯誤訊息提示三件事:同一個 Wi-Fi、已解鎖、開發者模式已開。
處理結果
在 Xcode 的 Settings → Accounts 重新登入 Apple ID,兩支手機都接上 Mac 之後重新執行腳本,兩支都安裝成功。新的 profile 是 Xcode 登入後,由 -allowProvisioningUpdates 向 Apple 要來的,有效期從今天重新算 7 天。
簽章過期,除了 App 打不開,還會影響登入狀態嗎?兩個地方要看:
profile 的 entitlements 裡有 keychain-access-groups,這就是簽章和 Keychain 的連結。覆蓋安裝同一個 App ID,Keychain 的資料理論上會保留,所以重新安裝之後逐項檢查:
| 檢查 | 為什麼 |
|---|---|
| App 能不能開啟 | 簽章過期時 iOS 不會執行這個 App |
| 設定頁的帳號區是不是「已綁定 Google」 | 登入狀態存在 Keychain |
| 家庭與卡片還在不在 | 資料在 Firebase,不在手機 |
| 登出再用 Google 登入 | 確認 userHouseholds 對照還在,找得回家庭 |
重新安裝之後的實測結果:
| 手機 | App 能開啟 | 登入狀態 |
|---|---|---|
| 我的手機 | 正常 | 這次沒有逐項檢查 |
| 太太的手機 | 正常 | 帳號區仍是已綁定 Google |
覆蓋安裝同一個 App ID,Keychain 裡的登入狀態有保留,這一點在太太的手機上確認。我的手機只確認 App 能開啟,「登出再用 Google 登入」這項今天沒有測。
昨天按下「綁定 Google」之後,跳出來的是這個畫面(email 已遮蓋):

先把從按下按鈕到回到 App 的過程拆開:
| 步驟 | 誰做 | 對應 Day 25 的哪一個設定或程式 |
|---|---|---|
| 1. 準備設定 | App | GIDConfiguration(clientID:),client id 從 Firebase 的設定讀 |
| 2. 打開登入畫面 | SDK | 系統的網頁驗證視窗(ASWebAuthenticationSession)開啟 accounts.google.com,就是畫面上方的網址 |
| 3. 授權 | 使用者 | 選帳號、看權限、按「繼續」 |
| 4. 回到 App | 系統 | Google 導向 redirect_uri,系統依 URL scheme 把網址交給 App,onOpenURL 再交給 GIDSignIn.handle |
| 5. 換 token | SDK | 依官方文件的流程,用 authorization code 換 access token 與 ID token |
| 6. 交給 Firebase | App | GoogleAuthProvider.credential 組好憑證,link 或 signIn |
畫面上有三件事,各自對應 OAuth 的一個概念。
權限(scope)。 畫面只要求兩項:名稱與個人資料相片、電子郵件地址,對應 openid、email、profile 三個基本身份權限。官方文件說,請求包含這類身份權限時,回應會帶一個 ID token,是用 JWT 表達、有數位簽章的使用者身份資訊。
token。 SDK 拿到的有三種,這個 App 用到的只有前兩種:
| token | 用途 | 這個 App |
|---|---|---|
idToken |
OpenID Connect 的 ID token,證明「這是誰」 | 交給 Firebase 當憑證 |
accessToken |
呼叫 Google API 用的短效憑證 | 一併交給 Firebase,沒有用它呼叫任何 Google API |
refreshToken |
換新 access token | SDK 內部保管 |
redirect_uri。 這是授權完成後,Google 把使用者送回 App 的位置。官方文件的 native app 流程,iOS 的格式是 com.googleusercontent.apps.<數字>:/<路徑>,也就是 client id 的網域反過來寫。Day 25 設定的 URL scheme 就是這個,沒設的話,登入完成後回不到 App。文件同時說明,自訂網址在 Android 與 Chrome 應用程式已經不支援。文件還建議在授權請求帶 code_challenge(PKCE);GoogleSignIn-iOS 相依 AppAuth 與 GTMAppAuth,但我沒有逐行驗證 SDK 實際送出的請求,所以這裡只寫文件的建議。
授權畫面標題顯示的是 project-<專案編號>,這是 Google Cloud 專案的預設名稱。要改成「神隊友」,到 Cloud Console 的 Branding 頁設定,官方文件的流程是:
發佈狀態也會影響使用者看到的畫面:
| 發佈狀態 | 誰能登入 | 畫面 |
|---|---|---|
| 測試中 | 名單內的測試使用者,上限 100 人;只要求基本身份權限時,任何人都可以 | 顯示測試中的警告 |
| 已發佈、未驗證 | 任何 Google 帳號 | 不顯示 app 名稱與 logo |
| 已發佈、已驗證 | 任何 Google 帳號 | 顯示 app 名稱與 logo |
這個專案實際是哪一種狀態,我還沒有到 Cloud Console 確認。
| 項目 | 結果 |
|---|---|
| 簽章期限 | 10/9 到期;10/10 重新登入後換新,再 7 天有效 |
| 重新安裝 | 兩支手機都成功 |
| 自動排程 | 範本在專案裡,沒有安裝;而且沒有解決「過期才換新」的問題 |
| 提前換新 profile | 還沒驗證 |
| 授權畫面的應用程式名稱 | 還沒設定 |
| 發佈狀態 | 還沒確認 |
Day 27 加入英文語系:String Catalog 為 288 個字串補上英文與 comment、日期與時間格式改成跟隨語言、既有測試先固定語言、英文版在 ax3 字級下的版面檢查。
官方文件
idToken、accessToken、refreshToken