正規表現によるパスワードのバリデーションについて
正規表現、難しい。
現在、railsのユーザー登録時のバリデーションで苦戦中。
rails は devise があるのだけれどそのままではパスワードの安全性はないに等しいのでせめてもう少しバリデーションを付け足したい。
ということでこちら。
validates :password, format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,}+\z/i }
これはざっくり言うとパスワードが英数混合で8文字以上で設定されているかチェックする記述。
8文字未満やそれ以外の文字だと登録できないようにしている。
/\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,}+\z/i
そしてこの部分。何を書いてるのか分解してみる。
① "/ "正規表現 "/ "
この2つのピンクのスラッシュ。
正規表現は二つのスラッシュを囲むことで効果を発揮する宣言みたいなもの?だから、必ずつける。
② \A
これは文字列の最初にマッチする。
つまりパスワードの最初からチェックするぞと言うこと。
③(?=.*?[a-z])
これはさらに分解する。
■ (? = 〇〇 )
これは先読みもしくは look-ahead といわれ、〇〇の文字列の直前の位置を指している。
■ .
このドットは改行を除く任意の1文字である。
■ *?
このアスタリスクと赤ハテナは最小量指定子という難しい名前がついている。
これは具体例を見た方が早いので下記を見てみる。
irb(main):012:0> word = "aassddffaassddff"
=> "aassddffaassddff"
irb(main):013:0> word.match(/a.*s/)
=> #<MatchData "aassddffaass">
irb(main):014:0> word.match(/a.*?s/)
=> #<MatchData "aas">
irb(main):015:0>
まず word = "aassddffaassddff" を定義する。
次に /a.*s/ という正規表現で文字をマッチさせると"aassddffaass" が戻ってくる。
まず a.*s は始まりの文字が a で終わりは s の文字列を指し、さらに a と s の間には .*があるため a〇〇sという文字列をマッチさせる記述となる。
( * はこれだけだと直前の文字を0回以上繰り返すという正規表現。つまり .* だと任意の文字の繰り返しとなる)
つまり今回マッチするのは、
aas, aass, aassddffaas, aassddffaass
となる。
a.*s のようにするとマッチする最大の文字列を戻す。(これは最大量指定子と呼ばれている)
さて、/a.*?s/ はどうかというと"aas"が戻ってくる。
今回はマッチする最小の文字が戻されている。
* だけだとマッチする最大の文字列を戻すけれど、 *? だとマッチする最小の文字列を戻すという意味になる。
■ [a-z]
そもそも [ ] はこの角括弧の中の文字のいずれか一つを指すというもので、今回は小文字のアルファベッド a から z までのいずれかの文字を意味している。
ということでまず .*?[a-z] の意味は任意の文字0回以上と a ~ z の文字の組み合わせということになる。(例えば、任意の文字は0回も該当するので a だけでも成立する)
結果、(?=.*?[a-z]) は任意の文字0回以上と a ~ z の文字の組み合わせた文字列の直前の空白を指すことになる。(すなわちこの場合はa ~ zのアルファベッドが含まれていないとダメと言うことになる)
④ (?=.*?\d)
まず /d は数字を指している。
あとは③と同じで .*?\d は任意の文字0回以上と数字の組み合わせとなる。
つまり(?=.*?\d)は任意の文字0回以上と数字の組み合わせた文字列の直前の空白を差すこととなる。
⑤ [a-z\d]
これは角括弧の中のa~zまたは数字のいずれか一つを意味している。
⑥ {8,}
これは本来の形は { x, y }という形で、この直前の文字が少なくても x 回、多くても y 回出現するとマッチすると言う意味になる。
(例えば、[a-z]{5,10} は小文字アルファベッドa-zが5回以上10回以下でマッチすると言うことなので、skduf, audshgg, jsdkirotuq, 374ghjuijsa31,kseuf1923845 にはマッチするが、asd, TRddTU, er567865, 1111111111111111 はマッチしない)
そして{x,} の形になるとこの直前の文字が少なくても x 回以上出現すればマッチすると言うことになるので、{8,} は直前の文字が少なくても 8回以上出現すればマッチするということになる。
⑦ +
直前の文字が1回以上繰り返すものにマッチするいう意味。
⑧ /z
これは文字列の最後にマッチする。
つまりパスワードの最後までチェックするぞと言うこと。
⑨ i
最後のこの i はオプションで大文字・小文字を区別しないというものなので[a-z]も記述もこのオプションで大文字小文字アルファベットのいずれかの一文字となる。
/\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,}+\z/i
①〜⑨をつなげて考えてみると、
・文字列の最初から始めるよ。(\A)
・その最初の文字列は任意の文字0回以上とa ~ z の文字一つ、もしくは数字一つの文字列の直前の位置からスタートした文字列だよ((?=.*?[a-z])(?=.*?\d))
・a-z、もしくは数字がいずれか一つ([a-z\d])
と
・直前の文字(a-z、もしくは数字がいずれか一つ)の繰り返し(+)出現して
・全部で8文字以上の文字列が出現し、(\z)
・最後の文字まa-z、もしくは数字がいずれか一つだよ。
・そして大文字小文字の区別はないよ。
⇩
文字列の最初は任意の文字0回以上とa ~ z の文字一つ、もしくは数字一つの文字列の直前の位置からスタートした文字列で、a-z もしくは数字がいずれか一つと直前の文字(a-z、もしくは数字がいずれか一つ)の繰り返しが8回最後まで出現すると({8,}+\z)マッチし、大文字小文字は区別しない。
(スラッシュで正規表現だと宣言し、
(?=.*?[a-z])(?=.*?\d)は始まりの位置を、
\Aはその位置からスタートした時の最初の文字からマッチしていくと言う宣言を、
[a-z\d]で文字は英数どちらかを指定し、
{8,}+\z でそれが8回以上最後まで繰り返されるとマッチする。
i そして大文字小文字は区別しないというオプションがついている)
他にも大文字小文字区別をつけるなら、i オプションを消して
/\A(?=.*?[a-zA-Z])(?=.*?\d)[a-zA-Z\d]{8,}+\z/
英小文字大文字数字をそれぞれ1種類以上含む5文字以上とするなら
/\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{5,}+\z/
記号を加えたいならこちらのページを参照させていただくと良いでしょう。
https://qiita.com/mpyw/items/886218e7b418dfed254b
また他にも下記を参考にさせていただきました。
・基本的にRuby 2.7.0 リファレンスマニュアル
・いろいろ検証するのに使わせていただいたこちらのページ
・先読みに関しては主にこちらを
https://techracho.bpsinc.jp/hachi8833/2018_11_20/63564
・最小量指定子の理解はこちらをhttps://www.javadrive.jp/perl/regex/repeat/index8.html
もし見ていただいた方がいたら読みにくくてごめんなさい。
さて、また頑張ろう。