digest認証
digest認証とはまずブラウザからサーバへユーザ名やパスワードなどをそのまま送らずに、その場で生成したランダムな文字列などとともにハッシュ値に変換してから送る。
サーバ側では予め登録されたユーザ名・パスワードなどから同様の手順でハッシュ値を計算し、両者が一致すれば認証成功とみなす認証方法のこと。
ハッシュ値に変換して送信する処理
function getHash(pw) { const salt = 'xxxxxxxxxx' const crypto = require('crypto') const hashsum = crypto.createHash('sha512') hashsum.update(pw + salt) return hashsum.digest('hex') }
入力されたパスワードにsaltを付加してハッシュ関数SHA-2のバリエーションのうちの1つであるSHA-512方式で暗号化した後、16進数でハッシュを返却。
ソルト
パスワードの前後に付加することで予め任意の文字列をハッシュ値に変換しておき、標的のパスワードのハッシュ値と比較することでパスワードを推察するレインボー攻撃の対策となる。
crypto
暗号化標準モジュール
sha512
暗号学的ハッシュ関数。
入力値に対し、常に同じ値を出力する。
入力値から同じ値を算出するのは簡単だが、その逆、結果から元の値を導き出すのは難しい。
HA-2はSHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256の6つのバリエーションを持ち、ハッシュ長は224、256、384、512ビットのいずれか。
function addUser(userid, passwd, callback) { const hash = getHash(passwd) const token = getAuthToken(userid) const regDoc = {userid, hash, token, friends: {}} userDB.insert(regDoc, (err, newdoc) => { if (err) return callback(null) callback(token) }) } function login(userid, passwd, callback) { const hash = getHash(passwd) const token = getAuthToken(userid) getUser(userid, (user) => { if (!user || user.hash !== hash) { return callbask(new Error('認証エラー'), null) } user.token = token updateUser(user, (err) => { if (err) return callback(err, null) callback(null, token) }) }) }
ユーザーを登録する際にハッシュとトークンをDBに保存しておく。
ログインする際にはユーザーのハッシュが一致しなければ認証エラーとする。
ハッシュが一致した場合はトークンを更新する。
ハッシュはログイン後の行動の中で認証が必要になった際に使用する。
PEGを使ってWikiシステム作ってみた
JavaScript用のパーサージェネレーターのPEG.jsを使ってWiki記法をReactオブジェクトに変換してWikiシステムを作ってみた。
PEG文法で記法のルールを書いた`pegjs`という拡張子のファイルを用意し、
`pegjs -o src/wiki_parser.js src/wiki_parser.pegjs`コマンドを打つと文章を変換する処理をJavaScriptで書き直したファイル(wiki_parser.js)がsrc配下に生成される。
martiniからnegroniが生まれた背景とリフレクション
弊社ではGoのWebフレームワークの中でnegroniを採用している。
negroni
net/httpと直接結びついて動作する、ミドルウェアにフォーカスされたライブラリ。
セッション管理したりユーザ認証の際に利用されることが多い。
negorniでは `gin`というコマンドを使う。
ginコマンドではソースコードをビルドしてバイナリをたちあげる作業を行う。
negroniは変更をlistenしているのでソースコードを変更して保存したタイミングで更新してくれるために開発で便利。
negroniの誕生背景
negroniの作者はnegroniを作る前にmartiniというライブラリを作っていた。
martiniはリフレクションを良く使うライブラリで、そのためにGoにしては処理が遅いが、裏でよろしくやってくれて便利だった。
これはauto magicと称された。
しかし、Goの文化的に利用する側が何が行われているのか把握できないブラックボックスはよくないという批判があった。
これをうけ、作者が新しく作ったのがnegroni。
フレームワークを比較している記事
Go Web Frameworks 比較qiita.com
型
型には名前を持つ型と名前を持たない型がある。
名前を持つ型
type Team struct
名前を持たない型
`[]byte` や `*int` など型の名前と記号によって表される。
map[string]string interface{}
複数の型をうけいれる型
型には複数の型を受け入れるインターフェース型がある。
fmt.Prinflnが例として挙げられる。
func Println(a ...interface{})(n int, err error)
型の中でも静的な型と動的な型があるが、インターフェース型はどちらも受け入れることができる。
var n int = 42 // nの(静的な)型はint var s string = "hello" // sの(静的な)型はstring var v interface{} // vの(静的な)型はinterface{} v = n // vの(静的な)型はinterface{}、動的な型はint v = s // vの(静的な)型はinterface{}、動的な型はstring
reflect機能
reflectパッケージを使用すると変数の型についての情報を取得することができる。
package main import ( "fmt" "reflect" ) func main() { type MyInt int var x MyInt v := reflect.ValueOf(x) // ValueOfでreflect.Value型のオブジェクトを取得 fmt.Println(v.Type()) // Typeで変数の型を取得 fmt.Println(v.Kind()) // Kindで変数に格納されている値の種類を取得 } // main.MyInt // int
インターフェース型の場合は動的な型の情報も取得することができる。
値を格納していない場合にはメソッドがpanicを起こすので値が格納されているか否かはIsValidメソッドでチェックする。
func HasField(v interface{}, name string) bool { rv := reflect.ValueOf(v) // ポインタ型であるか if rv.Kind() == reflect.Ptr { // ポインタの要素の型を返す rv = rv.Elem() } // 構造体であるか if rv.Kind() != reflect.Struct { return false } // 引数に渡された項目がオブジェクトの項目に値があるか判定して返す return rv.FieldByName(name).IsValid() }
reflectについて詳しいサイト
reflect パッケージ - golang.jp
A Tour of Go vol.2
types
論理値型(ゼロ値:false)
・bool
文字列型(ゼロ値:"")
・string
数値型(ゼロ値:0)
・unit
・uint8 符号なし 8-ビット 整数 (0 to 255)
・uint16 符号なし 16-ビット 整数 (0 to 65535)
・uint32 符号なし 32-ビット 整数 (0 to 4294967295)
・uint64 符号なし 64-ビット 整数 (0 to 18446744073709551615)
・int
・int8 符号あり 8-ビット 整数 (-128 to 127)
・int16 符号あり 16-ビット 整数 (-32768 to 32767)
・int32 符号あり 32-ビット 整数 (-2147483648 to 2147483647)
・int64 符号あり 64-ビット 整数 (-9223372036854775808 to 9223372036854775807)
・float32 IEEE-754 32-ビット 浮動小数値
・float64 IEEE-754 64-ビット 浮動小数値
・complex64 float32の実数部と虚数部を持つ複素数
・complex128 float64の実数部と虚数部を持つ複素数
・byte uint8の別名(エイリアス)
・rune int32の別名(エイリアス)
使い分け方は記事によってはuintを使えと書かれていたり、整数は基本intだ!などバラバラです。
自社のコードでは整数ではInt64, 浮動小数ではflat64を使っていたのであわせます。
Type conversions
型変換を行う際は新しい変数を用意し、その変数に変換する先の型を宣言する。
var x, y int = 3, 4 var f float64 = math.Sqrt(float64(x*x + y*y)) var z uint = uint(f)
int -> float64 -> uint
goでは型を明示しない場合は型推論が行われる。
Constants
constは":="を使った宣言はできない。
SQLインジェクションとplaceholder機能を使った対応処理
SQLインジェクション
r.GET("/1/users/:Id"
上記のAPIをWebで叩くとき、WebではURLがブラウザの上部に表示され、下記のようにURLが表示される。
"https://domain.jp/users/id"
パラメーター部分(id)はURLを直接書き換えることで別の値に置き換えることができる。
データベースと連携して動作するWebサイトでは、外部から入力された値を元にSQL文を組み立てるため、
入力された値によって意図しないSQLが生成され、結果として不正にDBのデータが読み取られたり、データが改ざんまたは削除される恐れがある。
この手法の攻撃を"SQLの注入"という意味でSQLインジェクションと呼ぶ。
placeholder機能
SQLインジェクションの対応として、安全なパラメーターを使ってSQL文を生成するplaceholder機能を使うことができる。
Routes
func GetUser(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { // パラメータをチェック Id, err := strconv.ParseInt(ps.ByName("id"), 10, 0) helpers.CheckErr(err, "Invalid Id") // 問題なければモデルの処理へ受けわたす R.JSON(w, http.StatusOK, models.GetUser(Id)) }
Models
func GetUser(Id int64) UserView { query := UserQuery + ` WHERE s.user_id = ? AND s.user_deleted_date IS NULL ` var user UserView _, err := DbMap.Select(&user, query, id) helpers.CheckErr(err, "Executing query failed") return user }
"SELECT s.user_id = ? "の?には"_, err := DbMap.Select(&user, query, id) "によりidで渡ってきた値が代入されてSQL文が生成される。
A Tour of Go vol.1
Packages
goのプログラムはパッケージで構成され、mainパッケージから開始される。
ソースコードの冒頭で自身を表すパッケージを記載し、
続いてインポートするパッケージを記述する。
package main import { "fmt" "net/http" "tmhub/helpers" }
自社のソースコードではpackageの後にくるものは下記5つ
- main
- helpers
- models
- config
- routes
これはトップレベルに配置されているフォルダと一部一致する。
インポートのパスで"tmhub/helpers"と呼び出されているパッケージは冒頭がpackage helpersステートメントで始まる。
Goでは構成するファイルを全てpackageという要素とみなす。
Exported names
パッケージをインポートすると、そのパッケージがエクスポートしている名前を参照することができる。
Goでは外部のパッケージから参照できるエクスポートされた名前は大文字から始まる。
func main() {
fmt.Println(math.Pi)
}
上記のコードで `math.pi` とすると参照できずエラーが出力される。
cannot refer to unexported name math.pi
Functions
引数では変数名の後に型を記述する。
この時引数の型が同じ場合は変数を先に記述して最後に型を記述する記法もある。
func add(x int, y int) int { return x + y } func add(x, y int) int { return x + y }
関数は複数の戻り値を返すこともできる。
また戻り値は引数に続いて宣言することができる。
func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return }
(sum int)に続く(x, y int)が戻り値
上記のコードの結果は7 10が返る
戻り値を引数の後に並べて宣言する。
return は記述必須。個人的にはreturnが記述必須なら戻り値はreturnに続けて書くか
もしくは戻り値を先頭に記述するなら当然返却するものとしてreturn記述省略可とかにして欲しかった。。
GoのMVC
最近業務でGoの修正を行う機会があり、クライアント側しか触ってこなかった自分には未知のことがたくさんあった。
まずは大枠としてMVCから学ぶ。
MVCはレイヤーアーキテクチャという設計手法に基づいている。
レイヤーアーキテクチャ
アプリケーションを責務に応じたいくつかの層としてとらえる設計手法。
View | ユーザーインターフェース層 | モデルのデータを取り出してユーザが見るのに適した形で表示する要素 |
---|---|---|
Controller | アプリケーション層 | ユーザからの入力(通常イベントとして通知される)をモデルへのメッセージへと変換してモデルに伝える要素 |
Model | ドメイン層 | データと手続きを表現し、データの変更をViewに通知する要素 |
- 各オブジェクトはいずれかの層に属し、複数の層にまたがることはない
- 層の関係は一方通行であり、相互参照する関係は層をまたがない
ModelはControllerやViewでどう呼び出されるか知るべきではないし、ControllerはViewがどのように描画するか知るべきでない。
互いの層を一方的に利用するようにすることで、オブジェクト間の結合を疎に保ち、ドメインロジックの凝集度を高める。
これは作り手により異なるところかもしれないが、Goの勉強なので自社で扱っているプロダクトのGoの構造をこれにあてはめた。
Controller
- routes: アプリからユーザーが入力したデータを受け取り、どの処理をするかをモデルへ通知する。
- handlers: モデルへ通知する前にセッションのチェックなど毎回実施されるチェック処理を行う。
- helpers: 便利道具。毎回実施するチェック、とはいかないがさまざまなところで使う処理群。
Model
- models: ビジネスロジック。
routes
func New (r * httprouter.Router) { r.GET('users/:id', handlersLoginRequired(GetUser)) }
handlersであるhandlersLoginRequired()でユーザーがログインしていることを確認し、modelのGetUser()へ通知する。