[インデックス 16296] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet/http
パッケージにおいて、Basic認証でパスワードが空の場合の挙動を修正するものです。具体的には、RFC 2617で定義されているBasic認証の仕様に従い、ユーザー名とパスワードを区切るコロン(:
)が、パスワードが空の場合でもエンコードされた文字列に含まれるように変更されました。これにより、空のパスワードを持つBasic認証が正しく機能するようになります。また、この変更を検証するための新しいテストケースも追加されています。
コミット
commit d535bc7af3290c6f09eeb391e0ef00f374f9b743
Author: Alberto García Hierro <alberto@garciahierro.com>
Date: Tue May 14 15:33:46 2013 -0700
net/http: Fix basic authentication with empty password
The encoded string must include the : separating the username
and the password, even when the latter is empty. See
http://www.ietf.org/rfc/rfc2617.txt for more information.
R=golang-dev, bradfitz, adg
CC=golang-dev
https://golang.org/cl/8475043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d535bc7af3290c6f09eeb391e0ef00f374f9b743
元コミット内容
net/http
パッケージにおけるBasic認証の実装において、パスワードが空の場合に認証文字列のエンコードがRFC 2617の仕様に準拠していなかった問題を修正します。具体的には、ユーザー名とパスワードを区切るコロンが、パスワードが空の場合に省略されてしまう不具合がありました。このコミットは、パスワードが空であってもコロンが必ず含まれるように修正し、関連するテストを追加します。
変更の背景
HTTP Basic認証は、ユーザー名とパスワードをコロンで結合し、その文字列をBase64エンコードしてAuthorization
ヘッダーに含めることで行われます。RFC 2617のセクション2(4ページ目末尾)には、「認証を受けるために、クライアントはユーザーIDとパスワードを、単一のコロン(:
)文字で区切って、Base64エンコードされた文字列として資格情報内に送信する」と明記されています。
しかし、Goのnet/url.User
型が提供するString()
メソッドは、パスワードが設定されていない場合にコロンを含まない文字列を返していました。例えば、ユーザー名が"gopher"でパスワードが空の場合、u.String()
は"gopher"を返していました。RFCの仕様では"gopher:"のようにコロンを含める必要があるため、この不一致が問題となっていました。
この不具合により、パスワードが空のBasic認証を要求するサーバーに対して、GoのHTTPクライアントが正しく認証情報を送信できないという問題が発生していました。このコミットは、このRFCの要件を満たすために、パスワードが空の場合でも明示的にコロンを追加するように修正することで、相互運用性の問題を解決することを目的としています。
前提知識の解説
HTTP Basic認証
HTTP Basic認証は、HTTPプロトコルでクライアントを認証するための最もシンプルな方法の一つです。
- サーバーからの要求: サーバーは、認証が必要なリソースへのアクセス要求を受け取ると、
WWW-Authenticate
ヘッダーにBasic
スキームとレルム(認証領域)を含んだ401 Unauthorized
レスポンスを返します。 - クライアントの応答: クライアントは、ユーザー名とパスワードをコロン(
:
)で結合した文字列(例:username:password
)を作成します。 - Base64エンコード: この文字列をBase64エンコードします。
- Authorizationヘッダー: エンコードされた文字列を
Authorization
ヘッダーにBasic
プレフィックスを付けて含め、再度リクエストを送信します(例:Authorization: Basic <base64_encoded_string>
)。
RFC 2617
RFC 2617は「HTTP認証:基本認証とダイジェスト認証」を定義する標準ドキュメントです。このRFCは、HTTP/1.1における認証メカニズムの詳細を規定しており、特にBasic認証のエンコード形式に関する厳密なルールが含まれています。このコミットの背景にある問題は、RFC 2617のセクション2に記載されている「ユーザーIDとパスワードは単一のコロンで区切られる」という要件に直接関連しています。
Go言語のnet/http
パッケージ
net/http
パッケージは、Go言語でHTTPクライアントとサーバーを実装するための主要なパッケージです。このパッケージは、HTTPリクエストの作成、レスポンスの処理、ヘッダーの操作など、HTTP通信に必要な基本的な機能を提供します。
url.User
とUserInfo.String()
Go言語のnet/url
パッケージには、URLのユーザー情報(ユーザー名とパスワード)を扱うためのUser
型とUserInfo
型があります。
url.User(username string)
: ユーザー名のみを持つUserInfo
を生成します。url.UserPassword(username, password string)
: ユーザー名とパスワードを持つUserInfo
を生成します。UserInfo.String()
:UserInfo
オブジェクトの文字列表現を返します。このメソッドの以前の挙動が、パスワードが空の場合にコロンを含まない原因でした。UserInfo.Password()
: パスワードと、パスワードが設定されているかどうかを示すブール値を返します。
base64.URLEncoding.EncodeToString
encoding/base64
パッケージは、Base64エンコードとデコードの機能を提供します。URLEncoding
は、URLセーフなBase64エンコード(+
を-
に、/
を_
に、パディング文字=
を省略)を行うためのエンコーダです。EncodeToString
メソッドは、バイトスライスをBase64エンコードされた文字列に変換します。
技術的詳細
このコミットの技術的な核心は、HTTP Basic認証の仕様(RFC 2617)とGo言語のnet/url.UserInfo.String()
メソッドの挙動の間の不一致を解消することにあります。
従来のnet/http
クライアントの実装では、リクエストのURL.User
フィールドから認証情報を取得し、そのString()
メソッドの結果を直接Base64エンコードしていました。
req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String())))
ここで、u.String()
はUserInfo.String()
を呼び出します。UserInfo.String()
は、パスワードが設定されている場合にのみユーザー名とパスワードをコロンで区切って返します(例: username:password
)。しかし、パスワードが空の場合(例: url.User("username")
で作成された場合)、UserInfo.String()
は単にユーザー名のみを返していました(例: username
)。
RFC 2617の仕様では、パスワードが空であっても、ユーザー名とパスワードを区切るコロンは必須です。つまり、"username"ではなく"username:"という形式でBase64エンコードされるべきでした。
このコミットでは、この問題を解決するために、u.String()
の結果を直接使用するのではなく、一度変数auth
に格納し、その後にパスワードが空であるかどうかをu.Password()
メソッドで確認するロジックを追加しました。
auth := u.String()
if _, hasPassword := u.Password(); !hasPassword {
auth += ":"
}
req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(auth)))
u.Password()
は、パスワード文字列と、パスワードが設定されているかどうかのブール値(hasPassword
)を返します。!hasPassword
が真の場合、つまりパスワードが空の場合にのみ、明示的にコロンをauth
文字列に追加します。これにより、base64.URLEncoding.EncodeToString
に渡される文字列が常にRFC 2617の要件を満たすようになります。
この修正により、GoのHTTPクライアントは、パスワードが空のBasic認証を要求するサーバーに対しても、正しく認証情報を送信できるようになり、より広範なHTTPサーバーとの互換性が確保されました。
コアとなるコードの変更箇所
src/pkg/net/http/client.go
--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -161,7 +161,18 @@ func send(req *Request, t RoundTripper) (resp *Response, err error) {
}\n
if u := req.URL.User; u != nil {
- req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String())))\n
+ auth := u.String()
+ // UserInfo.String() only returns the colon when the
+ // password is set, so we must add it here.
+ //
+ // See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt
+ // "To receive authorization, the client sends the userid and password,
+ // separated by a single colon (\":\") character, within a base64
+ // encoded string in the credentials."
+ if _, hasPassword := u.Password(); !hasPassword {
+ auth += ":"
+ }
+ req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(auth)))
}
resp, err = t.RoundTrip(req)
if err != nil {
src/pkg/net/http/client_test.go
--- a/src/pkg/net/http/client_test.go
+++ b/src/pkg/net/http/client_test.go
@@ -10,6 +10,7 @@ import (
"bytes"
"crypto/tls"
"crypto/x509"
+ "encoding/base64"
"errors"
"fmt"
"io"
@@ -700,3 +701,37 @@ func TestClientHeadContentLength(t *testing.T) {
}
}
}
+
+func TestEmptyPasswordAuth(t *testing.T) {
+ defer afterTest(t)
+ gopher := "gopher"
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ auth := r.Header.Get("Authorization")
+ if strings.HasPrefix(auth, "Basic ") {
+ encoded := auth[6:]
+ decoded, err := base64.StdEncoding.DecodeString(encoded)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected := gopher + ":"
+ s := string(decoded)
+ if expected != s {
+ t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected)
+ }
+ } else {
+ t.Errorf("Invalid auth %q", auth)
+ }
+ }))
+ defer ts.Close()
+ c := &Client{}
+ req, err := NewRequest("GET", ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req.URL.User = url.User(gopher)
+ resp, err := c.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer resp.Body.Close()
+}
コアとなるコードの解説
src/pkg/net/http/client.go
の変更
このファイルでは、HTTPリクエストを送信するsend
関数内のBasic認証処理が変更されています。
-
変更前:
req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String())))
ここでは、
req.URL.User
(u
)のString()
メソッドが返す文字列を直接Base64エンコードしていました。前述の通り、u.String()
はパスワードが空の場合にコロンを含まないため、RFCの要件を満たしていませんでした。 -
変更後:
auth := u.String() // UserInfo.String() only returns the colon when the // password is set, so we must add it here. // ... (RFC 2617の引用コメント) ... if _, hasPassword := u.Password(); !hasPassword { auth += ":" } req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(auth)))
auth := u.String()
: まず、u.String()
の結果をauth
変数に格納します。if _, hasPassword := u.Password(); !hasPassword { ... }
:u.Password()
メソッドを呼び出し、パスワードが設定されているかどうかを示すブール値hasPassword
を取得します。!hasPassword
が真の場合(つまり、パスワードが空の場合)に条件ブロックが実行されます。auth += ":"
: パスワードが空の場合、auth
文字列の末尾に明示的にコロンを追加します。req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(auth)))
: 最後に、修正されたauth
文字列をBase64エンコードし、Authorization
ヘッダーに設定します。
この変更により、パスワードが空の場合でも、エンコードされる文字列が常にusername:
の形式となり、RFC 2617の仕様に準拠するようになりました。
src/pkg/net/http/client_test.go
の変更
このファイルでは、新しいテスト関数TestEmptyPasswordAuth
が追加されています。
import "encoding/base64"
: 新しいテストでBase64デコードを行うために、encoding/base64
パッケージがインポートされています。TestEmptyPasswordAuth
関数:gopher := "gopher"
: テスト用のユーザー名を定義します。ts := httptest.NewServer(...)
: テスト用のHTTPサーバーをセットアップします。このサーバーは、受信したリクエストのAuthorization
ヘッダーを検証します。- サーバー側のハンドラでは、
Authorization
ヘッダーからBasic
プレフィックスを取り除き、残りのBase64エンコードされた部分をデコードします。 - デコードされた文字列が
gopher:
(ユーザー名とコロン)と一致するかどうかを検証します。一致しない場合はエラーを報告します。
- サーバー側のハンドラでは、
c := &Client{}
: 新しいHTTPクライアントインスタンスを作成します。req, err := NewRequest("GET", ts.URL, nil)
: テストサーバーへのGETリクエストを作成します。req.URL.User = url.User(gopher)
: ここが重要です。url.User(gopher)
を使用して、パスワードが空のユーザー情報をリクエストのURLに設定します。resp, err := c.Do(req)
: 作成したリクエストをクライアントで実行します。defer resp.Body.Close()
: レスポンスボディを閉じます。
このテストは、パスワードが空のユーザー情報を持つリクエストが、修正されたnet/http
クライアントによって正しくBasic認証ヘッダー(gopher:
がBase64エンコードされたもの)を生成し、送信することを確認します。これにより、修正が意図通りに機能していることが保証されます。
関連リンク
- RFC 2617 - HTTP Authentication: Basic and Digest Access Authentication: http://www.ietf.org/rfc/rfc2617.txt
- Go CL 8475043: https://golang.org/cl/8475043
参考にした情報源リンク
- RFC 2617 - HTTP Authentication: Basic and Digest Access Authentication: http://www.ietf.org/rfc/rfc2617.txt
- Go言語の
net/http
パッケージドキュメント - Go言語の
net/url
パッケージドキュメント - Go言語の
encoding/base64
パッケージドキュメント