C# + MongoDBでログイン認証

 ASP.NET MVC + MongoDB環境でこのブログを作っている。今回はパスワード認証あたりが平文だったので、データベース直いじりで登録済みのパスワードをSHA256ハッシュにして、認証ロジックをそれに対応させた。

参考:http://dobon.net/vb/dotnet/string/md5.html

 パスワードにさらにユーザー名を付加し、それをなにかしらの文字列を使ってハッシュ化する。下記のようなメソッドを用意した。
private static string GetSHA256 (string userName, string pw)

{
//HMAC-SHA1を計算する文字列
var s = $"{userName}@{pw}";
//キーとする文字列
var key = "somestring";

//文字列をバイト型配列に変換する
byte[] data = System.Text.Encoding.UTF8.GetBytes(s);
byte[] keyData = System.Text.Encoding.UTF8.GetBytes(key);

byte[] bs;
//HMACSHA256オブジェクトの作成
using (var hmac = new HMACSHA256(keyData))
{
//ハッシュ値を計算
bs = hmac.ComputeHash(data);
}

//byte型配列を16進数に変換
var result = BitConverter.ToString(bs).ToLower().Replace("-", "");

return result;
}


 ハッシュ値を得るメソッドは用意したので、MongoDBに保存してあるユーザー情報と照会する。
public static bool Login(string id, string pw, IResponseCookies cookies)

{
var userCollection = DbConnection.db.GetCollection<BsonDocument>("user");
var filter = Builders<BsonDocument>.Filter.Eq("_id", id);
var doc = userCollection.Find<BsonDocument>(filter).FirstOrDefault<BsonDocument>();
if (doc == null)
{
return false;
}
else
{
var sha256 = GetSHA256(doc.GetValue("_id").AsString, pw);
if (sha256 != doc.GetValue("pw").AsString)
{
return false;
}
else
{
// 認証処理が続く
comment: 0

ASP.NET MVC: ユニットテストを用意してみる

 ASP.NET MVCでユニットテストを作成してみる。訳を作ったMSのドキュメントを参考にしながら。
https://github.com/hMatoba/ASP.NET-Core-doc/blob/master/ユニットテスト.rst

 まずVisualStudioでWebアプリのソリューションを作っておく。そしてルートの下にsrcとtestというディレクトリがあることを確認する。


 testディレクトリに入り、下記のコマンドを実行する。
dotnet new -t xunittest


 するとtestディレクトリ下に二つのファイルが生成される。このうちのproject.jsonを、VisualStudioのソリューションエクスプローラから、ソリューションに既存の項目として追加する。するとVisualStudioがファイルをさらに追加してくれる。これでテストプロジェクトがソリューションに加わったことになる。


 あとはガリガリとテストを追加していくだけ。テストはテストエクスプローラから実行できる。


comment: 0

ASP.NET MVC: ASP.NET MVCアプリを.NET Coreへ移植

 このブログのエンジンのフレームワークとしてASP.NET MVCを使っているが、.NET Framework上で動作するものを使っていた。今年の春だったか夏にLinuxなどへも移植を可能にした.NET Coreがリリースされており、もともとこれで動かすつもりだったのでブログエンジンをon .NET Frameworkからon .NET Coreへ移植した。

 
 実際の移植作業としてやったのは、
・プロジェクト立て直し
・依存ライブラリ入れ(.NET Core対応のものにする)
・ビュー、コントローラ、モデルもろもろコピペ移動
・動かなくなっている組み込みクラスの代替(一つだけだった)
という程度だった。VSで新しいプロジェクト作って、そこでNugetでライブラリ入れて、ファイルをコピペでプロジェクトに入れて、動かなくなったクラスを一つ書き換えた。30分程度で済んだ。
 動かなくなったのはセキュアなトークンの発行メソッドである。なので・・・
using System.Security.Cryptography;

を入れて、
private static string GetToken()

{
var rng = RandomNumberGenerator.Create();
var buff = new byte[25];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}

のようなトークン生成関数を用意した。もともとあったのは下記のメソッド。
private static string GetToken()

{
var token = "";
using (var rng = new RNGCryptoServiceProvider())
{
byte[] tokenData = new byte[32];
rng.GetBytes(tokenData);

token = Convert.ToBase64String(tokenData);
}

return token;
}



 あとproject.jsonでランタイムの指定が必要かも。テストする環境やデプロイするサーバの環境をターゲットに指定する。
"runtimes": {

"win8-x86": {},
"win10-x64": {}
},


 .NET Coreへのアプリ移植は案の定、楽に進んだ。大筋問題ないようなので、あとは細部がおかしなことになっていないか確認していく。
comment: 0

ASP.NET MVC: コントローラ未定義のパスへのアクセス時に404を返す

 Homeコントローラ内のIActionResultの末尾に下記を追加。
[Route("{*path}")]

public IActionResult NotFound(string path)
{
Response.StatusCode = 404;
return View("NotFound");
}

 返すべきものがあるなら上記の場所に達するまでのIActionResultのどれかに引っかかっている。引っかからずに末尾に来たということは、そのパスに該当するものは未定義なので404を返しておけということになるだろう。Viewは自作のものがある場合のコード。

 いつかのASP.NET MVCはController.HandleUnknownActionというメソッドをオーバーライドすればよかったらしいが、この記事を書いている時点での.NET Coreのベータで動かしているものでは該当メソッドが見つからなかった。
comment: 0

ASP.NET MVC: MVCをする(モデルとコントローラ)

 MVCフレームワークをまじめに使うのは、このブログのASP.NET MVCが初めてだった。それまではPythonでDjangoをスルーしてGAE、FlaskやTornadoを使ってた。
 なんでMVCかってのは、メンテナブルなものにしたかったからである。かつてPythonで書いていたとき、MVCもなんも知らずにまず動かそうとコードを書いていた。そのせいでいわゆるコントローラが妙な肥大化をしたものを書いていた。

 MVCの自分なりの理解。WebでMVCはこうだろ的な記事を読んでいて、ファットコントローラはMVCの本来じゃないという指摘を見た。グサッと来た。
 ちょうどGAEのサンプルに、モデルに静的メソッドを書いてコントローラを軽量にまとめているものを参考にし始めた時期だった。そういう方法をとることで、コントローラに散在したDBデータ取得ロジックが、きれいに特定の箇所にまとめられた。そんな体験をしていたタイミングでMVCがどんなものかというのを読んだので、そのままそれを学びたいと思った。
 コントローラを太らすMVCもあるらしい。個人的にはそれをやるとコントローラ毎にDBへのアクセスを書いてしまいがちになり、DBアクセスのメンテナンスが面倒になってくる。そこでじゃあDBアクセスのロジックをどこに持ってくるのが妥当かと考えると、とってきたいデータのモデルを持つクラスに静的メソッドとして加えるのが一番しっくりくる。というわけでファットモデルがいい。

 コントローラの一例が下記のコード。このブログの各記事ページのためのコントローラ。
[Route("Article/{id:int}")]

public IActionResult Article(int id)
{
var entry = Entry.GetEntry(id);
if (entry != null)
{
ViewBag.entry = entry;
return View();
}
else
{
Response.StatusCode = 404;
return View("NotFound");
}
}

 パスから記事のIDを取得。記事があればその時点で持ってる情報をビューに渡す。あとはビュー側でモデルをごにょごにょして情報を取得しつつ、それを表示してもらう。IDに該当する記事がなければ404ページのビューへ。
 ビューからモデルを見るので、モデルはDBデータの各種取得方法が静的メソッドになったクラスになっており、それなり肥大化している。
 これでいいんかなと思いつつ考えたりいろいろ読んでいる。それでも以前のファットコントローラなものからすると、だいぶメンテナンスしやすい仕組みだと感じる。
comment: 0