Firebase认证限制同一用户登录

4

在Firebase中,有没有一种方法可以限制同一用户同时登录的次数?例如,我想为单个帐户设置同时只能使用3个设备的限制,该如何实现?

3个回答

3
这在Firebase中是不支持的。最好的做法是跟踪令牌auth_time,这是登录时间。您应该为此保留一个队列,有3个条目。每次登录用户时,发送其ID令牌进行验证,将auth_time添加到队列中(如果它不存在),如果队列超过最大大小(3),则弹出最早的auth_time。只允许ID令牌在该队列中具有auth_times以访问数据。

我认为这并不能解决问题,因为令牌在一小时后会过期,所以如果客户在那之后打开他的应用程序,他将需要更新他的令牌,而比较将失败。 - Eloise85
令牌刷新只会更新 iat 字段。auth_time 是用户登录的时间,无论令牌刷新多少次,它都将保持不变。 - bojeil

1
我曾遇到类似的情况,然后我采用了bojeil的方法来限制使用一个用户凭证同时登录的数量。
我在用户文档中维护了以下数组字段和数字字段。
{
    allowed_auth_times : [
    
    ],
    max_granted_login : 3
}

在用户注册时将条目添加到此数组中。

  1. 如果另一个人使用相同的凭据登录系统(无论是在同一设备上还是在不同设备上),如果 size(allowed_auth_times) < max_granted_login

如果 size(allowed_auth_times) == max_granted_login,则删除最旧的身份验证时间(allowed_auth_times[0]),然后添加新的身份验证时间条目。

这样,您将确保在任何给定时间点,有限的用户能够使用该系统。我已经使用 Angular + AngularFire2 实现了这一点。如果有人能提供改进建议,我将非常乐意。提前感谢。

认证组件:

trySignInWithEmailAndPassword() {
    
    // Actual Signin process starts here.
    this._firebaseAuthenticationService.signInUserWithEmailAndPassword(this.userCredentials.value)
      .then((user) => {
        this._firebaseAuthenticationService.OTCheckAndUpdateDBAuthTime().then((result) => {
          bootbox.hideAll();
          if (result === true) {
            this._router.navigate(['/home']);
          }
        }, (err) => {
          bootbox.hideAll();
          bootbox.alert("Error Occured. Please contact support team with screenshot. " + err);
        });
      }).catch((err) => {
        console.error("Login failed. Redirecting user to authentication page.");
        bootbox.alert("Some Error Occured While Authenticating " + this.userCredentials.get("email").value);
        this._router.navigate(['/authentication']);
      });
  }

FirebaseAuthService
import { Injectable, OnInit } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import * as firebase from 'firebase/app';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AngularFirestore } from '@angular/fire/firestore';
import { environment } from 'src/environments/environment';
import { first, take } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class FirebaseAuthenticationService implements OnInit {
  ngOnInit(): void {
  }

  uid: string = "";
  constructor(private _angularFireAuth: AngularFireAuth,
    private db: AngularFirestore,
    private _router: Router) { }
    
  trySignOut() {
    return new Promise<any>((resolve, reject) => {
      this._angularFireAuth.auth.signOut().then(() => {
        resolve();
      }, (error) => {
        reject(error);
      }
      );
    });
  }

  getCurrentUserUID(): string {
    if (this._angularFireAuth.auth.currentUser != undefined || this._angularFireAuth.auth.currentUser != null) {
      let uid = this._angularFireAuth.auth.currentUser.uid;
      if (uid == undefined || uid == null) {
        return "";
      } else {
        return uid;
      }
    }
    return this._angularFireAuth.auth.currentUser.uid;
  }

  getCurrentUserUID2(): Observable<firebase.User> {
    return this._angularFireAuth.authState.pipe(first());
  }

  createUserWithEmailAndPassword(userCredentials: any) {
    return new Promise<any>((resolve, reject) => {
      this._angularFireAuth.auth.createUserWithEmailAndPassword(userCredentials.email, userCredentials.password)
        .then((userData) => {
          userData.user.getIdTokenResult().then((a) => {
            console.log("Auth Time : " + a.authTime);
            resolve(userData.user);
          });
        });
    });
  }

  signInUserWithEmailAndPassword(userCredentials: any) {
    return new Promise<any>((resolve, reject) => {
      this._angularFireAuth.auth.signInWithEmailAndPassword(userCredentials.email, userCredentials.password)
        .then((userData) => {
          console.log(userData);
          // If email is not verified then send verification email every time.
          if (this._angularFireAuth.auth.currentUser.emailVerified == false) {
            this.sendEmailVerification();
          }
          userData.user.getIdTokenResult().then((a) => {
            console.log("Auth Time : " + a.authTime);
            resolve(userData.user);
          });
        }, err => {
          console.error("Error Occured During Signin user with email and password in auth service.");
          console.error(err);
          reject(err);
        });
    });
  }

  // This function checks entry in allowed_auth_times [] depending on max_logins : in user profile.
  OTCheckAndUpdateDBAuthTime() {
    return new Promise<any>((resolve, reject) => {
      this._angularFireAuth.authState.subscribe((userAuthState) => {
        if (userAuthState.uid != null || userAuthState.uid != undefined || userAuthState.uid != "") {
          const user_doc_id = userAuthState.uid;
          this.db.collection("users").doc(user_doc_id).snapshotChanges().subscribe((docData) => {
            let doc: any = docData.payload.data();
            let allowed_auth_times_arr: string[] = doc.allowed_auth_times;
            let max_granted_login: number = parseInt(doc.max_granted_login);
            this.getAuthTime().then((currentUserAuthTime) => {
              if (allowed_auth_times_arr && allowed_auth_times_arr.includes(currentUserAuthTime)) {
                resolve(true);
              } else {
                if (allowed_auth_times_arr) {
                  if (allowed_auth_times_arr.length == max_granted_login) {
                    allowed_auth_times_arr.splice(0, 1);      // Delete Oldest Entry
                  }
                  allowed_auth_times_arr.push(currentUserAuthTime);
                  this.updateDBAuthTimesArr(userAuthState.uid, allowed_auth_times_arr).then(() => {
                    resolve(true);
                  }, (error) => {
                    bootbox.alert("Error Occured While Updating Auth Times. Please take screenshot of this and contact June Support team. " + error);
                    reject(false);
                  });
                }
              }
            });
          });
        }else{
          console.error("Authentication Service > OTCheckAndUpdateDBAuthTime > userAuthState is blank");
        }
      });
    });
  }

// Update 

  updateDBAuthTimesArr(uid: string, allowed_auth_times_arr: string[]) {
    return this.db.collection(environment.collctn_users).doc(uid).update({
      allowed_auth_times: allowed_auth_times_arr
    });
  }

// Get Auth Time of Currently Signed in User

 getAuthTime() {
    return new Promise<any>((resolve, reject) => {
      try {
        this._angularFireAuth.authState.pipe(take(1)).subscribe((userAuthState) => {
          if (userAuthState) {
            userAuthState.getIdTokenResult().then((tokenResult) => {
              console.log("Token result obtained. ");
              console.log("Auth time obtained : " + tokenResult.authTime);
              resolve(tokenResult.authTime);
            });
          } else {
            console.error("Blank UserAuthState Captured.");
            reject(null);
          }
        });
      } catch (err) {
        console.error("Error Occured while obtaining the auth time of the user.");
        reject(null);
      }
    });
  }

**// You will be using this in other components. If there is entry of authTime in allowed_auth_times array then keep this user signed in, otherwise signout forcefully.**
  validateAuthTime(uid: string): Observable<boolean> {
    return new Observable<any>((observer) => {
      this.db.collection(environment.collctn_vendor_list).doc(uid).snapshotChanges().subscribe((docData) => {
        let doc: any = docData.payload.data();
        this.getAuthTime().then((currentUserAuthTime) => {
          if (doc.allowed_auth_times.includes(currentUserAuthTime)) {
            observer.next(true);
          } else {
            console.log("ValidateAuthTime > else : Observer returning false.");
            observer.next(false);
            //this.trySignOut().then(() => {
              //this._router.navigate(['/authentication']);
            //});
          }
        });
      });

    });
  }


}

0
你应该使用数据库(实时数据库或Firestore)来保存用户的设备,并从那里检查他是否可以登录。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接