【Go入門】net/httpパッケージを使ってBasic認証を実装する

シェアする

こんにちは。Go入門ブログの第30回です。

これまでの記事で、net/httpパッケージの使用により、WEBサーバの構築やHTTPリクエストの送信をシンプルに実装できることを解説してきました。
詳しくは以下の記事をご参照ください。

【Go入門】net/httpパッケージを使ったWEBサーバの構築とHTTPリクエストハンドラの実装

【Go入門】net/httpパッケージを使ったHTTPクライアントの実装(1) ~ GETリクエストの送信

【Go入門】net/httpパッケージを使ったHTTPクライアントの実装(2) ~ POSTリクエストの送信

今回は、これらの技術の実践的な用法として、Basic認証の実装方法を解説したいと思います。

Basic認証

念のため、Basic認証について簡単に解説しておきます。

Basic認証は、WEBページに対して最も簡単に導入できる認証方法の一つです
ユーザ名とパスワードの組み合わせで認証を行います。

Basic認証の仕組みですが、クライアントからはユーザ名とパスワードの組み合わせをコロン “:” でつなぎ、Base64でエンコードして送信するだけです。
これをサーバ側で受け取り、事前に登録されたユーザ名とパスワードの組み合わせにマッチするかチェックします。

仕組みが単純である分、盗聴や改竄も容易で、セキュリティ的には脆弱です。
その代わり、実装も簡単で、またほとんどのWebサーバおよびブラウザで利用できるという利点もあります。

このため、一般公開するWEB会員サービスなどには向きませんが、セキュリティ要件次第で広く採用されている認証方式です。

Basic認証を要求するWEBサーバの実装

それでは、net/httpパッケージを使ってBasic認証を要求するWEBサーバを実装してみましょう。

サーバサイドでのBasic認証の取り扱いには、http.Request構造体に実装されているBasicAuthメソッドを使用します。

func (r *Request) BasicAuth() (username, password string, ok bool)

BasicAuthメソッドは、リクエストにより送信されたベーシック認証のユーザとパスワード戻り値として取得することができます。
3つ目の戻り値である論理値okは、ベーシック認証情報の取得に失敗した場合にfalseを返します。

さっそくサンプルコードをみてみましょう。

package main

import (
    "io"
    "net/http"
)

// 認証情報を定数で定義
const (
    authUser = "user"
    authPw   = "pass"
)

// 認証関数
func checkAuth(req *http.Request) bool {
    // req.BasicAuthメソッドで送信されたユーザ名・パスワードを取得
    user, pw, ok := req.BasicAuth()
    // 正しいユーザ名・パスワードと比較
    if !ok || user != authUser || pw != authPw {
        return false
    }
    return true
}

// Basic認証を行うハンドラ関数
func basicAuthHandler(w http.ResponseWriter, req *http.Request) {
    // 認証関数の実行
    if checkAuth(req) == false {
        // 認証に失敗した場合のヘッダ情報付与
        w.Header().Add("WWW-Authenticate", `Basic realm="my private area"`)
        w.WriteHeader(http.StatusUnauthorized) // 401コード
        // 認証失敗時の出力内容
        w.Write([]byte("401 認証失敗\n"))
        return
    }
    // 認証成功時の出力内容
    io.WriteString(w, `
    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
      <title>Go | net/httpパッケージ</title>
    </head>
    <body>
      <h1>認証成功!</h1>
      <p>Basic認証で正しいユーザ名とパスワードが送信されました。</p>
    </body>
    </html>
`)
}

func main() {
    // "/"へのリクエストをbasicAuthHandler関数で処理する
    http.HandleFunc("/", basicAuthHandler)
    // localhost:8080でサーバー処理開始
    http.ListenAndServe(":8080", nil)
}

15行目に定義されているcheckAuth関数は、認証の成否判定を行っています。
BasicAuthメソッドを実行して送信された認証情報をを取得し、あらかじめ定数で定義したユーザ名・パスワードと比較しています。

認証チェック後の処理は、26行目に定義されているbasicAuthHandler関数で行っています。
認証の成否によって出力内容を切り替えている他、認証に失敗した場合のみ、ヘッダに401コードとBasic認証を要求することを示すWWW-Authenticateプロパティ値をセットしています。

それではこのコードを実行し、ブラウザでhttp://localhost:8080/にアクセスしてみましょう。
以下の画像のように、ユーザ名・パスワードの入力を要求するダイアログが表示されるはずです。

Basic認証

コード内の定数で定めたユーザ名とパスワードを送信すれば、以下のように認証成功時の出力が得られます。

Basic認証

ユーザ名とパスワードが間違えていた場合、再入力を要求されます。
認証をキャンセルすると、以下のように認証失敗時の出力が得られます。

Basic認証

Basic認証リクエストの送信

ここまでで既にBasic認証を実装できたといえますが、クライアント側から認証情報を付与してリクエスト送信する方法もみておきましょう。
http.Request構造体に実装されているSetBasicAuthメソッドにより、送信リクエストのヘッダにBasic認証情報を設定できます。

func (r *Request) SetBasicAuth(username, password string)

使い方は簡単で、認証に用いるユーザ名とパスワードをそのまま引数に渡せばOKです。
SetBasicAuthは、渡されたユーザ名とパスワードを使用して、リクエストのAuthorizationヘッダに認証情報を設定します。

この際、引数に渡されたユーザー名とパスワードは暗号化されません。
平文のまま送信が行われることになりますので注意してください。

それではサンプルコードをみてみましょう。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httputil"
    "net/url"
    "time"
)

// 認証情報を定数で定義
const (
    authUser = "user2"
    authPw   = "pass2"
)

func main() {

    // URLを生成
    u := &url.URL{}
    u.Scheme = "http"
    u.Host = "localhost:8080"
    // url文字列
    uStr := u.String()

    // タイムアウトを30秒に指定してClient構造体を生成
    cli := &http.Client{Timeout: time.Duration(30) * time.Second}

    // 生成したURLを元にRequest構造体を生成
    req, _ := http.NewRequest("GET", uStr, nil)

    // SetBasicAuthメソッドでBasic認証情報をセット
    req.SetBasicAuth(authUser, authPw)

    // リクエストヘッダの内容を出力
    header, _ := httputil.DumpRequestOut(req, true)
    fmt.Println(string(header))

    // GETリクエスト発行
    rsp, err := cli.Do(req)
    if err != nil {
        fmt.Println(err)
        return
    }
    // 関数を抜ける際に必ずresponseをcloseするようにdeferでcloseを呼ぶ
    defer rsp.Body.Close()

    // レスポンスを取得し出力
    body, _ := ioutil.ReadAll(rsp.Body)
    fmt.Println(string(body))
}

先ほどのserver.goを実行しWEBサーバを起動した状態で、このclient.goを実行してください。
以下の出力が得られるはずです。

$ go run client.go
GET / HTTP/1.1
Host: localhost:8080
User-Agent: Go-http-client/1.1
Authorization: Basic dXNlcjpwYXNz
Accept-Encoding: gzip

    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
      <title>Go | net/httpパッケージ</title>
    </head>
    <body>
      <h1>認証成功!</h1>
      <p>Basic認証で正しいユーザ名とパスワードが送信されました。</p>
    </body>
    </html>

レスポンスボディの出力が、サーバサイドで定義された認証成功時の内容であることが確認できますね。

また、ヘッダ情報にAuthorizationプロパティに、”Basic dXNlcjpwYXNz”という値がセットされていますが、これがコード34行目のSetBasicAuthによる設定値です。
このうち”Basic”は、認証法方がBasic認証であることを示しています。
“dXNlcjpwYXNz”は、ユーザー名とパスワードをコロンで接続した文字列をbase64エンコーディングしたものです。

なお、コード内の定数authUserとauthPwを不正な値に書き換えてclient.goを実行すると、以下のようにきちんと認証失敗時の内容が得られます。

$ go run client.go
GET / HTTP/1.1
Host: localhost:8080
User-Agent: Go-http-client/1.1
Authorization: Basic dXNlcjI6cGFzczI=
Accept-Encoding: gzip

401 認証失敗

終わりに

以上、Goの標準パッケージのみでBasic認証を実装する手順を解説しました。
実際にはユーザ名とパスワードの組み合わせを複数持つ必要がありますので、それに応じた設計が必要になりますが、基本的な認証ロジックは当記事の内容を流用できると思います。
その他、認証の要否ごとにハンドラを共通化するなど、運用にあわせて工夫してみてください。

スポンサーリンク
スポンサーリンク
スポンサーリンク

シェアする

フォローする

スポンサーリンク
スポンサーリンク