如何实现单管理员登录限制?多设备同账号登录拦截方案求助
Alright, let's implement the single-device-only login for your admin account using Firebase (since that's what your code is already using). The core idea is to track active sessions in Firestore, so we can check if the admin is already logged in when a new login attempt happens.
Step 1: Set Up Firestore Collection
First, create a Firestore collection named activeSessions (you can name it anything, just keep it consistent). Each document in this collection will represent an active admin session, using the user's UID as the document ID (so we can quickly look up if a session exists).
Step 2: Modify Your Login Component Code
Here's the updated Login component with the single-session logic integrated:
import React, { Component, PropTypes } from 'react'; import ReactDOM from 'react-dom'; import firebase from './config/database'; import Config from './config/app'; import * as firebaseCLASS from 'firebase'; require("firebase/firestore"); class Login extends Component { constructor(props) { super(props); this.state = { username: '', password: '', error: '', }; this.handleChangeUsername = this.handleChangeUsername.bind(this); this.handleChangePassword = this.handleChangePassword.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.authenticate = this.authenticate.bind(this); this.endActiveSession = this.endActiveSession.bind(this); } componentDidMount() { // Listen for auth state changes to clean up session on logout firebase.auth().onAuthStateChanged(user => { if (!user) { // User logged out, remove their active session from Firestore this.endActiveSession(); } }); } handleChangeUsername(event) { this.setState({ username: event.target.value }); } handleChangePassword(event) { this.setState({ password: event.target.value }); } handleSubmit(event) { this.authenticate(this.state.username, this.state.password); event.preventDefault(); } async authenticate(username, password) { const displayError = (error) => { this.setState({ error: error }); } // First check if user is allowed if (Config.adminConfig.allowedUsers != null && Config.adminConfig.allowedUsers.indexOf(username) === -1) { displayError("This user doesn't have access to this admin panel!"); return; } try { // Step 1: Attempt Firebase Auth login const userCredential = await firebase.auth().signInWithEmailAndPassword(username, password); const user = userCredential.user; // Step 2: Check Firestore for existing active session const sessionDocRef = firebase.firestore().collection('activeSessions').doc(user.uid); const sessionDoc = await sessionDocRef.get(); if (sessionDoc.exists) { // Session exists: log out current attempt and show error await firebase.auth().signOut(); displayError("用户已登录,无法访问该账号"); return; } // Step 3: No existing session, create new active session await sessionDocRef.set({ userId: user.uid, email: user.email, loginTime: firebase.firestore.FieldValue.serverTimestamp(), sessionId: user.refreshToken // Use refresh token as unique session identifier }); // Login successful - redirect or do your post-login logic here console.log("Yes, user is logged in (single session)"); // Example: this.props.history.push('/dashboard'); } catch (error) { console.log(error.message); displayError(error.message); } } async endActiveSession() { const currentUser = firebase.auth().currentUser; if (currentUser) { try { await firebase.firestore().collection('activeSessions').doc(currentUser.uid).delete(); } catch (error) { console.error("Error ending session:", error); } } } authWithGoogle() { // Keep your existing Google auth code here, but you'll want to add the same session check logic var provider = new firebaseCLASS.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider).then(async function (result) { const user = result.user; // Add session check logic here similar to authenticate() method }).catch(function (error) { console.log(error.message); }); } showGoogleLogin() { if (Config.adminConfig.allowedUsers != null && Config.adminConfig.allowedUsers.length > 0 && Config.adminConfig.allowGoogleAuth) { return (<div> <p className="category text-center"> <a onClick={this.authWithGoogle} className="btn btn-social btn-fill btn-google"> <i className="fa fa-google"></i> Login with google </a> </p> <br /> <p className="category text-center">Or login using email</p> </div>) } else { return (<div></div>) } } render() { // Keep your existing render code here return ( <div> <nav className="navbar navbar-primary navbar-transparent navbar-absolute"> <div className="container"> <div className="navbar-header"> <button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#navigation-example-2"> <span className="sr-only">Toggle navigation</span> <span className="icon-bar"></span> <span className="icon-bar"></span> <span className="icon-bar"></span> </button> <a className="navbar-brand" href="#"> {Config.adminConfig.appName} </a> </div> <div className="collapse navbar-collapse"> <ul className="nav navbar-nav navbar-right"> <li className=" active "> <a> <i className="material-icons">fingerprint</i>Login </a> </li> </ul> </div> </div> </nav> <div className="wrapper wrapper-full-page"> <div className="full-page login-page" data-image="assets/img/lock.jpeg"> <div className="content"> <div className="container"> <div className="row"> <div className="col-md-4 col-sm-6 col-md-offset-4 col-sm-offset-3"> <form onSubmit={this.handleSubmit}> <div className="card card-login card-hidden"> <div className="card-header text-center" data-background-color="rose"> <h4 className="card-title">Login</h4> </div> <div className="card-content"> <h4>{this.state.error}</h4> <div className="input-group"> <span className="input-group-addon"> <i className="material-icons">email</i> </span> <div className="form-group label-floating"> <label className="control-label">Email address</label> <input type="email" value={this.state.username} onChange={this.handleChangeUsername} className="form-control" /> </div> </div> <div className="input-group"> <span className="input-group-addon"> <i className="material-icons">lock_outline</i> </span> <div className="form-group label-floating"> <label className="control-label">Password</label> <input type="password" value={this.state.password} onChange={this.handleChangePassword} className="form-control" /> </div> </div> </div> <div className="footer text-center"> <input type="submit" className="btn btn-rose btn-simple btn-wd btn-lg" value="Login" /> </div> </div> </form> </div> </div> </div> </div> <footer className="footer"> <div className="container"> <nav className="pull-left"> <ul> </ul> </nav> <p className="copyright pull-right"> © <script> document.write(new Date().getFullYear()) </script> {Config.adminConfig.appName} </p> </div> </footer> </div> </div> </div> ); } } export default Login;
Key Changes Explained:
authenticateMethod: Converted to async/await for cleaner async flow. After successful Firebase Auth login, we check Firestore for an existingactiveSessionsdocument for the user. If it exists, we log out the new attempt and show your desired error message. If not, we create the session document.endActiveSessionMethod: Deletes the user's session from Firestore when they log out (triggered by theonAuthStateChangedlistener incomponentDidMount).- Auth State Listener: Ensures that if the user logs out (either intentionally or due to token expiration), their active session is cleaned up in Firestore.
Additional Notes:
- Session Expiry: If you want to handle cases where the admin's session ends abruptly (like closing the browser without logging out), you can add a TTL (Time-To-Live) policy on the
activeSessionscollection in Firestore. This will automatically delete old sessions after a set time (e.g., 24 hours). - Google Auth: I left your Google auth code in place, but you'll need to add the same session check logic to the
authWithGooglemethod if you want to enforce single sessions for Google logins too. - Security Rules: Don't forget to set Firestore security rules to ensure only the logged-in user can modify their own session document. Example rule:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /activeSessions/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; } } }
That should do it! Now your admin account will only allow one active login at a time.
内容的提问来源于stack exchange,提问作者Farha khan

