パスワードの強度をチェックするシンプルなObjective-Cモジュールをつくった


PWSResult result = [PWStrength validateWithPassword:@"Myp@ssw0rd!"];
result.complexity; // 0.0f-1.0f
result.valid; // YES or NO

https://github.com/laiso/PWStrength

これ。

モバイルアプリファーストで作ったシステムってなんかアカウント作成フローが簡便化されてるのが多いと思う。

メール到達確認ないものとか、IDパスのみでアカウント作成してすぐ使えるとか。

パスワードを二回入力させて整合性チェックとかやっぱりモバイルで使うのはめんどくさいんだけど、このへん各社どうしているんだろう。

今までパスワード入力時には禁止文字とか文字数とかチェックして画面にエラー出してたり、サーバーサイドでチェックしてたりしたんだけど、やっぱりナウいウェブアプリみたいにクライアントサイドでリアルタイムに出したいと思った。

主にjQuery ComplexifyっていうjQueryプラグインを参考にしてiPhoneアプリとかから見ていらなそうなところ削ったり、UIKit用に好みでロジック追加したりした。

実は先行するComplexify-ObjCていうモジュールもあって、それでもいいんだけどjQueryプラグイン版とインターフェイス揃えてたりAPIが好みじゃなかったりでフォークで解決できそうになかったので自分でつくった。

パスワード強度の算出は

  • 文字数を長くする
  • 小文字、大文字、数字、記号を使う
  • 文字の種類を多く使う

で上るはず。頻出単語を辞書データでチェックして警告とかは別にしない。

とりあえず以下にテストで示した。

https://github.com/laiso/PWStrength/blob/master/PWStrengthTests/PWStrengthTests.m

チェックをパスする閾値の設定とか作ってあるが必要なのかどうなのか、どうしたものかという感じ。

デモアプリで一応プログレスバーで視覚化するサンプルを作っておいた(GIF画像のやつ)。

該当箇所の実装はこんな感じ。ViewControllerに余計な責務持たせないようにしているのに注目して欲しい(UITextFieldのインスタンスはStoryboard上のみで保持してdelegateでつなぐ、とか)。

https://github.com/laiso/PWStrength/blob/master/iOSDemo/ViewController.m

#pragma mark - UITextFieldDelegate

- (BOOL)textFieldShouldClear:(UITextField *)textField
{
  textField.text = @"";
  return [self textField:textField shouldChangeCharactersInRange:NSMakeRange(0, textField.text.length) replacementString:textField.text];
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
  NSString *subString = [textField.text stringByReplacingCharactersInRange:range withString:string];
  
  PWSResult result = [PWStrength validateWithPassword:subString];
  
  [self.progressView setProgress:result.complexity animated:YES];
  self.progressView.progressTintColor = (result.valid)? [UIColor greenColor]: [UIColor redColor];
  
  NSUInteger displayValue = result.complexity * 100;
  NSString *displayValid = (result.valid)? @"YES": @"NO";
  self.statusLabel.text = [NSString stringWithFormat:@"Complexity: %lu %%, Valid: %@", (unsigned long)displayValue, displayValid];
  
  return YES;
}

デモなので含めてるけど、progressViewとstatusLabelに代入する値を取得するUI非依存(テスタブルにもなる)なViewModelクラスに逃がせばもっとスキニーViewControllerになるのでいつもはそうしてる。

つまり、こうしたい

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
  NSString *subString = [textField.text stringByReplacingCharactersInRange:range withString:string];
  // ViewModel作成。クラスワイドに使う用途があるならinit時にメンバーに保持する
  ViewModel *viewModel = [ViewModel new];
  [viewModel  validateWithPassword:subString];
  
  [self.progressView setProgress:[viewModel progress] animated:YES];
  self.progressView.progressTintColor = [viewModel progressTintColor];
  
  self.statusLabel.text = [viewModel statusText];;
  
  return YES;
}

RAC化すればもっと柔軟な感じのインターフェイスにできそうだけどまだ本格導入してないのでよく知らない。