同じユーザー名とパスワードを使用した複数のログインを防止する必要があるアプリケーションを開発しています。
同じマシンで発生する場合、ユーザーセッションで何らかの処理を行う必要があることは明らかですが、同じユーザー名とパスワードを使用して別のマシンにログインすることも防ぐ必要があります。
以下の点に留意する必要があります。
これについて何か助けていただければ幸いです。
ユーザーがログアウトせずにブラウザーを閉じた場合
特に、このケースは検出が困難であり、信頼できません。 JavaScriptでbeforeunload
イベントを使用することもできますが、ブラウザでJSが有効になっているかどうか、および特定のブラウザがこの非標準イベントをサポートしているかどうかに完全に依存します(例Operaはこれも、ログインを防ぐのではなく、以前にログインしたユーザーをログアウトすることをお勧めする主な理由の1つです。これは、ユーザーがログアウトするのを「忘れた」場合に、よりユーザーフレンドリーで安全です。他のコンピュータ。
最も簡単な方法は、User
に_static Map<User, HttpSession>
_変数を持たせ、 HttpSessionBindingListener
(および Object#equals()
を実装することです。 および Object#hashCode()
)。
_public class User implements HttpSessionBindingListener {
// All logins.
private static Map<User, HttpSession> logins = new HashMap<User, HttpSession>();
// Normal properties.
private Long id;
private String username;
// Etc.. Of course with public getters+setters.
@Override
public boolean equals(Object other) {
return (other instanceof User) && (id != null) ? id.equals(((User) other).id) : (other == this);
}
@Override
public int hashCode() {
return (id != null) ? (this.getClass().hashCode() + id.hashCode()) : super.hashCode();
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
HttpSession session = logins.remove(this);
if (session != null) {
session.invalidate();
}
logins.put(this, event.getSession());
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
logins.remove(this);
}
}
_
次のようにUser
にログインすると、
_User user = userDAO.find(username, password);
if (user != null) {
request.getSession.setAttribute("user", user);
} else {
// Show error.
}
_
次に、valueBound()
を呼び出し、以前にログインしたユーザーをlogins
マップから削除し、セッションを無効にします。
次のようにUser
をログアウトすると、
_request.getSession().removeAttribute("user");
_
または、セッションがタイムアウトになると、valueUnbound()
が呼び出され、logins
マップからユーザーが削除されます。
データベースに1つのテーブルを作成します— [online_users]
と呼びましょう— 3つのフィールドがあります:
[online_users]
1. username
2. login_time
3. logout_time
ユーザーがログインするたびに、ユーザー名とログイン時刻を[online_users]
に挿入します。
ユーザーのログインを必要とするすべてのページで、次の条件を設定します。[online_users]
をチェックして、ユーザーのlogout_time
が空白かどうかを確認します。
ユーザーがログアウトボタンを押すたびに、そのユーザーの名前にlogout_time
の[online_users]
を設定します。
誰かがアクティブなユーザー名とパスワードでログインしようとした場合は、username
とlogout_time
を確認し、ユーザーがすでにログインしていることを示すメッセージを表示します。そして最も重要なのは、そのユーザーのlogout_time
からMULTIPLELOGIN
へ
そのユーザーが他のマシンにログインしている場合、ユーザーが更新または別のページに移動すると、サイトからログアウトされていることが通知されます。次に、ユーザーをサイトのホームページにリダイレクトできます。
列名が「IsLoggedIn」であるテーブルの1つの追加フィールドをビットフィールドとして取得し、ユーザーがログインするまでtrueに設定します。ユーザーがログアウトしたらすぐにfalseに設定します。これは、セッションの有効期限時間にも実行する必要があります。セッションが期限切れになるとすぐに、トリガーを使用するか、SP callを使用して、このフィールドを自動的にfalseに設定する必要があります。
良い解決策はまだ歓迎されています
セキュリティフレームワークを使用してこれらすべての詳細を処理することをお勧めします。 Spring Security たとえば、既存のプロジェクトに統合するのはかなり簡単で、必要に応じて大幅にカスタマイズできます-そして最も重要なのは、それが組み込まれていることです同時ログインの検出と制御のサポート。
必要のないときにホイールを作り直さないでください。さもないと、でこぼこのホイールを作成するためにかなりの時間を費やすことになります。
Shantanu Guptaのソリューションについてもアドバイスします。ユーザーが現在ログインしていることを示すデータベース列を用意し、それに応じてその列を更新します。
セッションの有効期限を「キャプチャ」するには、web.xml
で定義する必要があります。
<listener>
<listener-class>com.foo.MySessionListener</listener-class>
</listener>
ここで、MySessionListener
はHttpSessionListener
インターフェースの実装です(サーブレットAPIによって提供されます)。
多分過度に単純化されているかもしれませんが、ちょっと...それは私にとってWeb2Pyで機能します:
ログイン成功時のみ、auth_membershipテーブルにSessionID(response.session_id)を書き込んでいます。ランディングページ(インデックスページ)で、現在のresponse.session_idがDBからのSessionIDと等しいかどうかを確認します。もしそうなら-すべて大丈夫です。そうでない場合-(「古い」、最初に)ユーザーは丁寧にログアウトされます。
ログインごとに新しいresponse.session_idが作成され、DBに保存されるため、上記は機能します。チェックはランディングページ(私のアプリでは最も重要なもので、他の多くの機能を開始します)でのみ行われるため、上記の場合はDBヒットが多すぎません。上記は、ユーザーのログアウトに依存しません。 IPアドレスは関係ありません(他の人が言及しているように、独自の問題が発生しています)。一度に1人のユーザーのみがログインでき、「古い」ユーザーをログアウトします。
NeoTorenがお役に立てば幸いです
私は自分に可能な解決策を実装しました、
私が使用するloginFilterで、システムのユーザーレコード内にlastloggedin、userloggedin、およびuserSessionを設定しました。
user.setUser_lastlogged(new Date());
user.setUser_loggedin(true);
user.setSessionId(request.getSession().getId());
appService.saveUsers(user);
そのため、struts2アクションのいずれかに移動すると、prepareメソッドにコードのスニペットがあります。
@Override
public void prepare() throws Exception {
UsersBase usercheck = appservice.getUserByUsername((String)request.getSession().getAttribute("j_username"));
if(request.getSession().getId().equals(usercheck.getSessionId())){
request.getSession().invalidate();
}
}
これは、ユーザーが別のマシンにログインしたときにユーザーをログアウトさせるか、ログインしたくない場合は、loginFilterで次のように実行できます。
UsersBase userdto = appService.getUserByUsername(username);
if (userdto != null) {
if ((userdto.getUser_loggedin())) {
if (request.getSession().getId().equals(userdto.getSessionId())) {
authRequest.eraseCredentials();
request.getSession().setAttribute("error", "You are already logged in ");
}
}
}
セッションがある場合、これは簡単に適用できます。ブラウザのログインごとに、セッションDBにセッションレコードを作成する必要があります。セッションIDは認証Cookieとして使用できます。セッションDBには、ユーザー名を含むインデックスもあります。ログイン時に、データベースにクエリを実行して、セッションの数を確認できます。実際には、タイプごとに1つのセッションを許可しています。たとえば、ユーザーは携帯電話からログインし、別のブラウザからログインすることができます。ただし、2つのブラウザセッションを使用することはできません。
あなたが言及した問題を解決するために。 2つのオプションがあります。
非常に短いセッションタイムアウト(5分など)を使用し、使用するたびにセッションを延長します。このように、ログアウトせずに離れると、ユーザーは自動的にログアウトされます。
他のセッションをバンプします。新しいセッションは古いセッションにぶつかります。バンプされたセッションは、特別なフラグとともに24時間DBに残ります。他のセッションがバンプされていることをユーザーに知らせるメッセージを表示し、時間とIPを表示します。このようにして、ユーザーのアカウントが侵害された場合にユーザーに通知されます。
ブラウザがまだアクティブであるかどうかを識別する方法は?
1分ごとにダミーのajax呼び出しを行い、ユーザー、セッションID、および最後の呼び出しの時刻に対して、HttpSessionにステータスを記録します。同じユーザーが新しいセッションでログインする場合、HttpSessionでユーザーをチェックし、それを超えるかどうか時間を確認します
1分以上経過すると、以前のブラウザが閉じられている/アクティブではないことを意味します。
注:要件に応じた時間設定(私の場合は1分)。
上記の条件チェックに加えて、コメントに記載されているコードを追加します"ユーザーがログアウトせずにブラウザーを閉じた場合。"
public class User implements HttpSessionBindingListener
各ユーザーの最後の既知のIPアドレスと、そのIPを最後に使用したときのタイムスタンプを追跡します。次に、他のIPからのアクセスを5分、1時間、または好きなだけブロックすることができます。
IPアドレスが切り替わるたびに、a)ユーザーの古いセッションを期限切れにするため、ユーザーは再度ログインする必要があり、b)ユーザーごとのカウンター(1時間ごとにゼロにすることができます)を増分できます。カウンターが5(またはそれ以上)を超える場合は、ユーザーのアカウントへのすべてのアクセスを長期間ブロックすることができます。
ログイン時にユーザーのある種のセッションIDを保存できます。ユーザーがログアウトするか、セッションが期限切れになると、その情報を再度削除します。
ユーザーがログインしようとしたときに、このユーザーのセッションIDがすでに保存されている場合は、ユーザーに確認してから、古いセッションを無効にします。
ブラウザがクラッシュした場合などは、ユーザーがすぐに再度ログインすることを望んでいるため、セッションが期限切れになるまでユーザーに待機させるのは面倒です。
これはあなたのアプリケーションにとって意味がありますか?
トークンを使用する
ユーザーがログインに成功すると、サーバー側はトークン文字列をクライアント/ブラウザー側に返し、サーバー側はマップをuserID-トークンで保存します。クライアントは、そのトークンを使用してサーバーに対して繰り返しチェック/要求を行います。トークンが同じでない場合、このユーザーは複数回ログに記録します。
ログオフすると、トークンがクライアント側のCookieまたはファイルシステムに保存され、次回のログ時にこのトークンが使用されます。
テーブル:
userid:token:log_date