kingFisherでは、ユーザーIDとパスワードを用いてログイン認証を行いますが、招かざるお客様によるパスワードアタックに対処する必要がありますので、サンプル実装してみました。
前回 サンプル実装しましたが、Java.Util.Mapクラスはスレッドセーフで無いので、クラス変数として使用は注意が必要。ともう1つ、拡張For文の中で要素数を削除してはいけないという問題があったので、改修したものを掲載します。
仕様は、「連続3回以上ログインに失敗した場合、10分間はアクセスが制限される」(前回のまま)
まずはログイン失敗情報を記録するクラスを用意
/**
* 連続ログイン失敗制限用クラス
* @author naka
*/
public class LoginGuard {
//最後にアクセスした日時を数値で保持
private long lastAccess ;
//アクセス回数
private int accessCnt;
public long getLastAccess() {
return lastAccess;
}
public void setLastAccess(long lastAccess) {
this.lastAccess = lastAccess;
}
public int getAccessCnt() {
return accessCnt;
}
public void setAccessCnt(int accessCnt) {
this.accessCnt = accessCnt;
}
}
複数スレッドから共有されるようにservletのクラス変数に(ここはMapではなくスレッドセーフなConcurrentMapを使用)、上記クラスを格納できるようにします
@WebServlet("/S02")
public class Servlet02 extends HttpServlet {
/*
* Login連続失敗検証用変数
*/
public static ConcurrentMap<String,LoginGuard> loginGuardMap = new ConcurrentHashMap<String,LoginGuard>();
loginGuardCheckメソッドを作成します。
このメソッドは自分を含め誰かかログインすると指定時間(今回は10分)経過しているログイン失敗情報をクリア。続けてログイン試行回数が指定回数(今回は3回)以内かチェックします。
マルチスレッドでのアクセスを考慮し、Map(loginGuardMap)変数へのアクセスをsynchronized ブロックで囲み排他制御します。これはコストがかかりますが、ログイン処理にアクセスが集中することもあまり多くないのと、安全性を考慮した結果採用しようと思います。
それと、前回拡張Forの中で、Mapの要素を削除していましたが、ここはJava.Util.Setを使用し削除対象リストを作成した直後、Forループ外でremoveALLで一括削除しています。
このメソッドをログイン認証の前に呼び出し、falseが返った場合、「アクセスが制限されています」のエラーメッセージを出してログインを拒否します。
/**
* Login連続失敗検証
* @param loginid
* @return true:ログイン検証してよい false:制限回数オーバー
*/
private boolean loginGuardCheck(String loginid) {
boolean blnReturn = false;
String strKey = loginid; //ログインしようとしているユーザーID
//Mapのクリア準備
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("JST"),new Locale("ja","JP"));
long nowDateTime=0;
nowDateTime = (cal.get(Calendar.YEAR));
nowDateTime = nowDateTime *100000000;
nowDateTime = nowDateTime + ((cal.get(Calendar.MONTH)+1)*1000000);
nowDateTime = nowDateTime + (cal.get(Calendar.DATE)*10000);
nowDateTime = nowDateTime + (cal.get(Calendar.HOUR_OF_DAY)*100);
nowDateTime = nowDateTime + (cal.get(Calendar.MINUTE));
long chkDateTime=0;
cal.add(Calendar.MINUTE, -10); //10分前を算出
chkDateTime = (cal.get(Calendar.YEAR));
chkDateTime = chkDateTime *100000000;
chkDateTime = chkDateTime + ((cal.get(Calendar.MONTH)+1)*1000000);
chkDateTime = chkDateTime + (cal.get(Calendar.DATE)*10000);
chkDateTime = chkDateTime + (cal.get(Calendar.HOUR_OF_DAY)*100);
chkDateTime = chkDateTime + (cal.get(Calendar.MINUTE));
//トランザクション排他
synchronized (loginGuardMap) {
//Mapのクリア
Set<String> removeSet = new HashSet<String> ();
for (String key : loginGuardMap.keySet()) {
LoginGuard objGuard = loginGuardMap.get(key);
if (objGuard.getLastAccess() < chkDateTime) {
removeSet.add(key);
}
}
if(removeSet.size()>0) {
loginGuardMap.keySet().removeAll(removeSet);
}
//過去制限時間内のアクセスチェック
LoginGuard objGuard = loginGuardMap.get(strKey);
if (objGuard == null) {
//初回アクセス
blnReturn = true;
objGuard = new LoginGuard();
objGuard.setAccessCnt(1);
objGuard.setLastAccess(nowDateTime);
loginGuardMap.put(strKey, objGuard);
} else {
//2回目以降アクセス
int accessCnt = objGuard.getAccessCnt();
if (accessCnt < 3) {
//制限内回数アクセス
blnReturn = true;
objGuard.setAccessCnt(++accessCnt);
objGuard.setLastAccess(nowDateTime);
loginGuardMap.replace(strKey, objGuard);
}
}
}
return blnReturn;
}
最後にログインが成功したら、Mapからそのユーザー情報を削除するメソッドloginGuardResetを用意し、ログイン認証が成功したら、このメソッドを呼び出します
ここもMap(loginGuardMap)変数へのアクセスをsynchronized ブロックで囲み排他制御します。
/**
* Login連続失敗検証用Mapクリア
* @param loginid
* @return
*/
private void loginGuardReset(String orgid,String loginid) {
String strKey = loginid; //ログインしようとしているユーザーID
synchronized (loginGuardMap) {
loginGuardMap.remove(strKey);
}
}
サンプル実装の為、指定時間、指定回数を直接書きましたが、コンスタント定義又はパラメタとして外出ししたほうが良いでしょう。あとカレンダー日付を取得しlongに変換しているところ、冗長なので、メソッド分けた方がすっきりしますかね。時間を見て対応しよう。
今回はちょっとコードの量が多くなりましたが以上です。
0 件のコメント:
コメントを投稿