## 憑證存儲
如果你使用的是 SSH 方式連接遠端,并且設置了一個沒有口令的密鑰,這樣就可以在不輸入用戶名和密碼的情況下安全地傳輸數據。 然而,這對 HTTP 協議來說是不可能的 —— 每一個連接都是需要用戶名和密碼的。 這在使用雙重認證的情況下會更麻煩,因為你需要輸入一個隨機生成并且毫無規律的 token 作為密碼。
幸運的是,Git 擁有一個憑證系統來處理這個事情。 下面有一些 Git 的選項:
* 默認所有都不緩存。 每一次連接都會詢問你的用戶名和密碼。
* “cache” 模式會將憑證存放在內存中一段時間。 密碼永遠不會被存儲在磁盤中,并且在15分鐘后從內存中清除。
* “store” 模式會將憑證用明文的形式存放在磁盤中,并且永不過期。 這意味著除非你修改了你在 Git 服務器上的密碼,否則你永遠不需要再次輸入你的憑證信息。 這種方式的缺點是你的密碼是用明文的方式存放在你的 home 目錄下。
* 如果你使用的是 Mac,Git 還有一種 “osxkeychain” 模式,它會將憑證緩存到你系統用戶的鑰匙串中。 這種方式將憑證存放在磁盤中,并且永不過期,但是是被加密的,這種加密方式與存放 HTTPS 憑證以及 Safari 的自動填寫是相同的。
* 如果你使用的是 Windows,你可以安裝一個叫做 “winstore” 的輔助工具。 這和上面說的 “osxkeychain” 十分類似,但是是使用 Windows Credential Store 來控制敏感信息。 可以在?[*https://gitcredentialstore.codeplex.com*](https://gitcredentialstore.codeplex.com/)?下載。
你可以設置 Git 的配置來選擇上述的一種方式
~~~
$ git config --global credential.helper cache
~~~
部分輔助工具有一些選項。 “store” 模式可以接受一個?`--file <path>`?參數,可以自定義存放密碼的文件路徑(默認是`~/.git-credentials`)。 “cache” 模式有?`--timeout <seconds>`?參數,可以設置后臺進程的存活時間(默認是 “900”,也就是 15 分鐘)。 下面是一個配置 “store” 模式自定義路徑的例子:
~~~
$ git config --global credential.helper store --file ~/.my-credentials
~~~
Git 甚至允許你配置多個輔助工具。 當查找特定服務器的憑證時,Git 會按順序查詢,并且在找到第一個回答時停止查詢。 當保存憑證時,Git 會將用戶名和密碼發送給?**所有**?配置列表中的輔助工具,它們會按自己的方式處理用戶名和密碼。 如果你在閃存上有一個憑證文件,但又希望在該閃存被拔出的情況下使用內存緩存來保存用戶名密碼,`.gitconfig`?配置文件如下:
~~~
[credential]
helper = store --file /mnt/thumbdrive/.git-credentials
helper = cache --timeout 30000
~~~
## 底層實現
這些是如何實現的呢? Git 憑證輔助工具系統的命令是?`git credential`,這個命令接收一個參數,并通過標準輸入獲取更多的參數。
舉一個例子更容易理解。 我們假設已經配置好一個憑證輔助工具,這個輔助工具保存了?`mygithost`的憑證信息。 下面是一個使用 “fill” 命令的會話,當 Git 嘗試尋找一個服務器的憑證時就會被調用。
~~~
$ git credential fill
protocol=https
host=mygithost
protocol=https
host=mygithost
username=bob
password=s3cre7
$ git credential fill
protocol=https
host=unknownhost
Username for 'https://unknownhost': bob
Password for 'https://bob@unknownhost':
protocol=https
host=unknownhost
username=bob
password=s3cre7
~~~
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-1)這是開始交互的命令。
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-2)Git-credential 接下來會等待標準輸入。 我們提供我們所知道的信息:協議和主機名。
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-3)一個空行代表輸入已經完成,憑證系統應該輸出它所知道的信息。
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-4)接下來由 Git-credential 接管,并且將找到的信息打印到標準輸出。
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-5)如果沒有找到對應的憑證,Git 會詢問用戶的用戶名和密碼,我們將這些信息輸入到在標準輸出的地方(這個例子中是同一個控制臺)。
憑證系統實際調用的程序和 Git 本身是分開的;具體是哪一個以及如何調用與`credential.helper`?配置的值有關。 這個配置有多種格式:
| 配置值 | 行為 |
| --- | --- |
| `foo`| 執行?`git-credential-foo` |
| `foo -a --opt=bcd` | 執行?`git-credential-foo -a --opt=bcd` |
| `/absolute/path/foo -xyz` | 執行?`/absolute/path/foo -xyz` |
| `!f() { echo "password=s3cre7"; }; f` |`!`?后面的代碼會在shell執行 |
上面描述的輔助工具可以被稱做?`git-credential-cache`、`git-credential-store`?之類,我們可以配置它們來接受命令行參數。 通常的格式是 “git-credential-foo [args] .” 標準輸入/輸出協議和 git-credential 一樣,但它們使用的是一套稍微不太一樣的行為:
* `get`?是請求輸入一對用戶名和密碼。
* `store`?是請求保存一個憑證到輔助工具的內存。
* `erase`?會將給定的證書從輔助工具內存中清除。
對于?`store`?和?`erase`?兩個行為是不需要返回數據的(Git 也會忽略掉)。 然而對于?`get`,Git 對輔助工具的返回信息十分感興趣。
如果輔助工具沒有任何有用的信息,它可以直接退出而不需要輸出任何東西,但如果它有這些信息,它在提供的信息后面增加它所擁有的信息。 這些輸出會被視為一系列的賦值語句;每一個提供的數據都會將 Git 已有的數據替換掉。
這有一個和上面一樣的例子,但是跳過了 git-credential 這一步,直接到 git-credential-store:
~~~
$ git credential-store --file ~/git.store store
protocol=https
host=mygithost
username=bob
password=s3cre7
$ git credential-store --file ~/git.store get
protocol=https
host=mygithost
username=bob
password=s3cre7
~~~
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO3-1)我們告訴?`git-credential-store`?去保存憑證:當訪問?`https://mygithost`?時使用用戶名 “bob”,密碼是 “s3cre7”。
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO3-2)現在我們取出這個憑證。 我們提供連接這部分的信息(`https://mygithost`)以及一個空行。
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO3-3)`git-credential-store`?輸出我們之前保存的用戶名和密碼。
`~/git.store`?文件的內容類似:
~~~
https://bob:s3cre7@mygithost
~~~
僅僅是一系列包含憑證信息URL組成的行。?`osxkeychain`?和?`winstore`?輔助工具使用它們后端存儲的原生格式,而?`cache`?使用它的內存格式(其他進程無法讀取)。
## 自定義憑證緩存
已經知道?`git-credential-store`?之類的是和 Git 是相互獨立的程序,就不難理解 Git 憑證輔助工具可以是?*任意*?程序。 雖然 Git 提供的輔助工具覆蓋了大多數常見的使用場景,但并不能滿足所有情況。 比如,假設你的整個團隊共享一些憑證,也許是在部署時使用。 這些憑證是保存在一個共享目錄里,由于這些憑證經常變更,所以你不想把它們復制到你自己的憑證倉庫中。 現有的輔助工具無法滿足這種情況;來看看我們如何自己實現一個。 這個程序應該擁有幾個核心功能:
1. 我們唯一需要關注的行為是?`get`;`store`?和?`erase`?是寫操作,所以當接受到這兩個請求時我們直接退出即可。
2. 共享的憑證文件格式和?`git-credential-store`?使用的格式相同。
3. 憑證文件的路徑一般是固定的,但我們應該允許用戶傳入一個自定義路徑以防萬一。
我們再一次使用 Ruby 來編寫這個擴展,但只要 Git 能夠執行最終的程序,任何語言都是可以的。 這是我們的憑證輔助工具的完整代碼:
~~~
#!/usr/bin/env ruby
require 'optparse'
path = File.expand_path '~/.git-credentials'
OptionParser.new do |opts|
opts.banner = 'USAGE: git-credential-read-only [options] <action>'
opts.on('-f', '--file PATH', 'Specify path for backing store') do |argpath|
path = File.expand_path argpath
end
end.parse!
exit(0) unless ARGV[0].downcase == 'get'
exit(0) unless File.exists? path
known = {}
while line = STDIN.gets
break if line.strip == ''
k,v = line.strip.split '=', 2
known[k] = v
end
File.readlines(path).each do |fileline|
prot,user,pass,host = fileline.scan(/^(.*?):\/\/(.*?):(.*?)@(.*)$/).first
if prot == known['protocol'] and host == known['host'] then
puts "protocol=#{prot}"
puts "host=#{host}"
puts "username=#{user}"
puts "password=#{pass}"
exit(0)
end
end
~~~
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO4-1)我們在這里解析命令行參數,允許用戶指定輸入文件,默認是?`~/.git-credentials`.
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO4-2)這個程序只有在接受到?`get`?行為的請求并且后端存儲的文件存在時才會有輸出。
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO4-3)這個循環從標準輸入讀取數據,直到讀取到第一個空行。 輸入的數據被保存到?`known`?哈希表中,之后需要用到。
[](http://git-scm.com/book/zh/v2/ch00/co_git____CO4-4)這個循環讀取存儲文件中的內容,尋找匹配的行。 如果?`known`?中的協議和主機名與該行相匹配,這個程序輸出結果并退出。
我們把這個輔助工具保存為?`git-credential-read-only`,放到我們的?`PATH`?路徑下并且給予執行權限。 一個交互式會話類似:
~~~
$ git credential-read-only --file=/mnt/shared/creds get
protocol=https
host=mygithost
protocol=https
host=mygithost
username=bob
password=s3cre7
~~~
由于這個的名字是 “git-” 開頭,所以我們可以在配置值中使用簡便的語法:
~~~
$ git config --global credential.helper read-only --file /mnt/shared/creds
~~~
正如你看到的,擴展這個系統是相當簡單的,并且可以為你和你的團隊解決一些常見問題。
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 2.8 總結
- 3. Git 分支
- 3.1 分支簡介
- 3.2 分支的新建與合并
- 3.3 分支管理
- 3.4 分支開發工作流
- 3.5 遠程分支
- 3.6 變基
- 3.7 總結
- 4. 服務器上的 Git
- 4.1 協議
- 4.2 在服務器上搭建 Git
- 4.3 生成 SSH 公鑰
- 4.4 配置服務器
- 4.5 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令