使用身份验证来确定用户是否已登录

5

我的问题是:

我有一个完整的网站,使用 react 构建,并通过 Nodejs 连接到该站点,基本上所有站点信息都来自服务器端。

对于聊天功能,无法使用服务器端,因为需要使用称为监听器的东西,在 React 中也需要配置 firebase 才能使监听器正常工作。对于聊天,您需要允许连接的用户发送消息,这不会向所有人开放。要知道谁连接了,您需要使用称为 auth 的选项。

我需要auth,对于 firebase 的规则,我只能允许连接的用户访问聊天室,而不是将规则开放给所有人。

当我在 firebase 中设置规则为 true 时,聊天功能可以正常工作,但我希望仅允许注册用户使用聊天功能,因此我进行了规则设置。 enter image description here

我遇到了以下错误:

Uncaught Error in snapshot listener: FirebaseError: Missing or insufficient permissions.

问题在于我已经通过服务器端进行连接,但需要以某种方式通过React进行连接。我还需要通过React进行连接以查看谁已连接,并且只允许已连接的用户使用聊天功能。需要通过React连接,因为只有通过React才能获得firebase提供的所有功能。如果直接通过React连接到firebase,才能获取授权。但是需要在React中连接聊天室。 (需要监听器,这样用户可以立即收到其他人发来的信息,并且需要授权以允许仅注册用户访问聊天室)
为了做到这一点,我需要通过React进行连接,并使用称为“auth”的东西来查看谁已连接。但整个网站本身都是围绕着通过nodejs登录构建的,在nodejs中不可能使用auth。 nodejs无法跟踪auth,它只返回JSON中的信息。
我不知道如何在React中启用与auth的连接,因为正如我所说,将所有连接传输到React将导致填充站点上的更改。但是,通过nodejs登录不允许使用auth,因为它是服务器端,我的服务器端仅返回JSON数据而不是像auth之类的功能。
我考虑过通过React和nodejs两次进行登录,但我担心这样做行不通,因为它可能会创建2个不同的令牌,并且只允许其中一个工作。
我尝试过其他解决方案,例如privateRoute,但仍然无济于事,因为必须以某种方式获取auth,以便知道谁已连接。如果有人可以帮我解决这个问题或提供解决方案,我会很高兴。我认为应该有一些方法可以解决这个问题。
如何告诉firebase一个注册用户想要访问聊天室?
迄今为止我所做的内容的代码:
通过nodejs登录

app.post('/login', login);


exports.login = (req, res) => {
  const user = {
    email: req.body.email,
    password: req.body.password,
  };

  const { valid, errors } = validateLoginData(user);

  if (!valid) return res.status(400).json(errors);

  firebase
    .auth()
    .signInWithEmailAndPassword(user.email, user.password)
    .then((data) => {
      console.log(JSON.stringify(data));
      return data.user.getIdToken();
    })
    .then((token) => {
      db.collection('users')
        .where("email", "==", user.email)
        .get()
        .then((data) => {
          data.forEach((doc) => {
            let userHandle = doc.data().handle;
            db.collection("users")
              .doc(userHandle)
              .get()
              .then((doc) => {
                let user = doc.data();
                db.collection('users').doc(userHandle)
                  .set(user)
                  .then(() => {
                    return res.json({ token: token});
                  })
                  .catch((err) => {
                    console.error(err);
                    return res.status(500).json({ error: err.code });
                  });

              }).catch((error) => {
                console.error(error);
              });


          });
        })
        .catch((err) => {
          console.error(err);
          res.status(500).json({ error: err.code });
        });
    })
    .catch((err) => {
      console.error(err);
      return res
        .status(403)
        .json({ general: "Wrong credentials, please try again" });
    });
};

使用 nodejs 登录函数

export const loginUser = (userData, history) => (dispatch) => {
//userData contains mail and password  
axios
    .post('/login', userData)
    .then((res) => {
      setAuthorizationHeader(res.data.token);
      //maybe add the auth here somehow
      dispatch(getUserData());
      dispatch
        ({
          type: LOGIN_USER,
          payload: res.data.handle
        });

      history.push('/');
    })
    .catch((err) => {
      dispatch({
        type: SET_ERRORS,
        payload: err.response.data
      });
    });
};

React中的PrivateRoute

import React from 'react';
import { Route, Redirect } from 'react-router-dom';


const PrivateRoute = ({component: Component, ...rest}) => {
  return(
    <Route {...rest} component={(props) => {
        const user = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : null;

        if(user){
            return <Component {...props} />
        }else{
            return <Redirect to={`/login`} />
        }

    }} />
   )

 }

export default PrivateRoute

React中的身份验证连接,但不包括聊天,我不确定如何处理聊天部分。

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.css';
import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider';
import createMuiTheme from '@material-ui/core/styles/createMuiTheme';
import jwtDecode from 'jwt-decode';
// Redux
import { Provider } from 'react-redux';
import store from './redux/store';
import { SET_AUTHENTICATED } from './redux/types';
import { logoutUser, getUserData } from './redux/actions/userActions';
// Components
import Navbar from './components/layout/Navbar';
import themeObject from './util/theme';
import AuthRoute from './util/AuthRoute';
import PrivateRoute from './util/PrivateRoute';

// Pages
import home from './pages/home';
import login from './pages/login';
import signup from './pages/signup';
import chat from './pages/chat';

import axios from 'axios';

const theme = createMuiTheme(themeObject);
axios.defaults.baseURL =
  'https://europe-west1-projectdemourl-b123c.cloudfunctions.net/api';

const token = localStorage.FBIdToken;
if (token) {
  const decodedToken = jwtDecode(token);
  if (decodedToken.exp * 1000 < Date.now()) {
    store.dispatch(logoutUser());
    window.location.href = '/login';
  } else {
    store.dispatch({ type: SET_AUTHENTICATED });
    axios.defaults.headers.common['Authorization'] = token;
    store.dispatch(getUserData());
  }
}

class App extends Component {
  render() {
    return (
      <MuiThemeProvider theme={theme}>
        <Provider store={store}>
          <Router>
            <Navbar />
            <div className="container">
              <Switch>
                <Route exact path="/" component={home} />
                <AuthRoute exact path="/login" component={login} />
                <AuthRoute exact path="/signup" component={signup} />
                <PrivateRoute path="/chat" exact component={chat} />
              </Switch>
            </div>
          </Router>
        </Provider>
      </MuiThemeProvider>
    );
  }
}

export default App;

登陆Firebase,客户端

import firebase from "firebase/app";
import "firebase/firestore";

const firebaseConfig = {
    apiKey: process.env.REACT_APP_FIREBASE_KEY,
    authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
    projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
    storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
    appId: process.env.REACT_APP_FIREBASE_APP_ID,
    measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID
};

firebase.initializeApp(firebaseConfig);
export default firebase;

我用于聊天的功能,这些功能中我使用了Firebase。


export const getRealtimeUsers = (handle) => {

    return async (dispatch) => {
        const db = firebase.firestore();
        const unsubscribe = db.collection("users")
            .onSnapshot((querySnapshot) => {
                db.collection('friends')
                    .get()
                    .then((data) => {
                        let friends = [];
                        data.forEach((doc) => {
                            if (doc.data().isFriends) {
                                if (doc.data().userHandleReceive == handle) {
                                    friends.push(doc.data().userHandleSend);
                                }
                                else {
                                    friends.push(doc.data().userHandleReceive);
                                }
                            }
                        });
                        const users = [];
                        querySnapshot.forEach(function (doc) {
                            if (doc.data().handle != handle && (friends.indexOf(doc.data().handle) > -1) ) {
                                users.push(doc.data());
                            }
                        });
                    })
                    .catch((err) => {
                        console.error(err);
                    });
            });
        return unsubscribe;
    }
}

export const updateMessage = (msgObj) => {

    return async dispatch => {

        const db = firebase.firestore();
        db.collection('conversations')
            .add({
                ...msgObj,
                isView: false,
                createdAt: new Date()
            })
            .then((data) => {
                console.log(data);
            })
            .catch((error) => {
                console.error(error);
            });

    }
}



export const getRealtimeConversations = (user) => {
    return async dispatch => {

        const db = firebase.firestore();
        db.collection('conversations')
            .where('user_uid_1', 'in', [user.uid_1, user.uid_2])
            .orderBy('createdAt', 'asc')
            .onSnapshot((querySnapshot) => {

                const conversations = [];

                querySnapshot.forEach(doc => {
                    if (
                        (doc.data().user_uid_1 == user.uid_1 && doc.data().user_uid_2 == user.uid_2)
                        ||
                        (doc.data().user_uid_1 == user.uid_2 && doc.data().user_uid_2 == user.uid_1)
                    ) {
                        conversations.push(doc.data())
                    }

                });

                dispatch({
                    type: userConstants.GET_REALTIME_MESSAGES,
                    payload: { conversations }
                })
            })
    }
}


这是代码的主要部分,需要注意的是我需要在身份验证中添加loginUser函数的React选项。
我想澄清的是,我通过Nodejs创建了一个经过身份验证的用户,但React并未看到它的身份验证。
我只是漏掉了一些东西,在这里问题非常小,但我无法找出问题所在以及如何修复它。

2
你可能需要考虑使用 console.log(req.body) 和 body-parser。你确定你的 req.body 不是空的吗? - Someone Special
1
其次,如果您想在Firestore/RTDB中使用身份验证,则需要用户在客户端进行身份验证。要么 - 允许用户在客户端使用电子邮件/密码登录。要么 - 如果您想要使用nodejs进行身份验证,则需要将JWT令牌传递到客户端并允许客户端使用JWT进行登录,然后您可以在Firestore或RTDB中查看身份验证信息。 - Someone Special
由于您正在使用Firebase,因此在React端或Node.js端登录基本相同。 https://firebase.google.com/docs/auth/web/firebaseui 实际上,我们大多数人都使用客户端登录,而不是Node.js登录。 - Someone Special
1
你只展示了你的节点端代码,我不确定你如何在客户端进行身份验证。 - Someone Special
1
@SomeoneSpecial 你说得对,感谢评论,我刚刚添加了身份验证过程,但现在聊天中还没有起作用。 - ish
显示剩余10条评论
1个回答

0

你的推理中存在几个缺陷,代码中也有几个错误。

基本上: 为什么要在服务器端和客户端都登录?

如果您希望用户登录到数据库以进行读取和/或写入操作,则可以在客户端或服务器端执行此操作。两者提供的选项几乎相同。

在服务器端和客户端都执行此操作没有太多意义。这引出了一个问题:为什么您想要同时执行两者?...

解决方案:

既然您正在收集用户的登录信息,请为用户立即在客户端登录,为什么不呢?

使用以下简单的HTML,其中包含两个输入并加载三个Firebase脚本:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script src="https://www.gstatic.com/firebasejs/8.4.1/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.4.1/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.4.1/firebase-firestore.js"></script>
</head>
<body>
    <input name="email" id="email" type="text">
    <input name="password" id="password" type="text">
    <button type="button" id="login">Login</button>
    <script src="index.js"></script>
</body>
</html>

index.js文件添加到同一个文件夹中:

var firebaseConfig = {
    apiKey: "AIzaSyAg2{.....}z7ObRoXg-goy9jc",
    authDomain: "s{.....}3.firebaseapp.com",
    projectId: "st{.....}40f63",
    storageBucket: "stacksh{.....}pspot.com",
    messagingSenderId: "537{.....}09",
    appId: "1:537{.....}04409:web:2{.....}7a6b67c14fa48"
};
firebase.initializeApp(firebaseConfig);
var db = firebase.firestore();
/* We listen for button click and then log in the user */
var button = document.getElementById('login');
button.addEventListener('click',function(){
    var email = document.getElementById('email').value;
    var password = document.getElementById('password').value;
    firebase
        .auth()
        .signInWithEmailAndPassword(email, password)
        .then((data) => {
            console.log(data);
            alert("logged in");
            getRealtimeUsers();
            
        }).catch((err) => {
            res.send(JSON.stringify(err));
            return err;
        });
    
});
function getRealtimeUsers(){
    const db = firebase.firestore();
    db.collection("users").get().then((querySnapshot) => {
        querySnapshot.forEach((doc) => {
            // doc.data() is never undefined for query doc snapshots
            console.log(doc.id, " => ", doc.data());
        });
    });
}

如果您也需要在服务器端登录,出于任何原因,您可以使用您的服务器端登录信息进行登录。

注意 您的服务器端代码存在一个重大漏洞。它应该等待 Promise 解决:
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const config =  {
    apiKey: "AIzaSyAg2GWsL04akpMNezuuz7ObRoXg-goy9jc",
    authDomain: "stackshop-40f63.firebaseapp.com",
    projectId: "stackshop-40f63",
    storageBucket: "stackshop-40f63.appspot.com",
    messagingSenderId: "537745304409",
    appId: "1:537745304409:web:2a8bb1f007a6b67c14fa48"
};

const firebase = require("firebase");
firebase.initializeApp(config);

// Log user in
async function login(req, res)  { 
    const user = {
        email: req.body.email,
        password: req.body.password,
    };
    await firebase
        .auth()
        .signInWithEmailAndPassword(user.email, user.password)
        .then((data) => {
            
            firebase.auth().currentUser.getIdToken(false).then(function(idToken) {
                      // Send token to your backend via HTTPS
                      // ...
                    res.send(idToken);
                    //signInWithCustomToken()
                    
                }).catch(function(error) {
                  // Handle error
                });
            return data;
        }).catch((err) => {
            res.send(JSON.stringify(err));
            return err;
        });
}
app.post('/login', login);

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