Firebase предлагает широкий спектр решений, которые помогут вам легко интегрировать такие функции, как аутентификация пользователей и запросы к базе данных, в ваше мобильное и веб-приложение. Хотя интеграция Cloud Firestore непосредственно в клиент вашего мобильного приложения может показаться беспроблемной для взаимодействия с базой данных, это может представлять угрозу безопасности, поскольку ваше приложение может быть манипулировано для получения несанкционированного доступа к вашей коммерческой тайне, которая находится внутри вашего приложения. Интеграция Cloud Firestore непосредственно в ваше мобильное приложение также может привести к тому, что вы потратите больше времени, если захотите расшириться до веб-приложения и нуждаетесь в том же сервисе. Исходя из этих причин, может быть хорошей идеей разместить ваши серверные службы в облаке.
К счастью, облачные функции позволяют это сделать. Вы можете развернуть серверные функции, такие как запросы к базе данных, тяжелые вычисления, и их можно запустить с помощью метода ответа на http-запрос.
Я создал простое приложение для Android и скрипт облачной функции, чтобы показать вам концепцию, стоящую за ним.
Во-первых, нам нужно установить Firebase CLI:
npm install -g firebase-tools
Войдите и аутентифицируйтесь через браузер с помощью команды:
firebase login
Инициализируйте проект и начните установку всех зависимостей:
firebase init functions
Во время инициализации вам будет предложено выбрать новый или существующий проект Firebase. Будет возможность выбора между Javascript или Typescript для вашего языка сценариев и возможность включения ESLint, который помогает искать потенциальные проблемы с вашим сценарием. Для этого примера я выбрал Javascript и отключил ESLint.
После завершения инициализации перейдите к функциям папки, и вы будете писать свой внутренний скрипт в index.js.
Чтобы развернуть скрипт, просто запустите:
firebase deploy --only functions
Из терминала вы можете увидеть логи функций в развертываемом скрипте:
Project Console: https://console.firebase.google.com/project/testfire-6b175/overview DAVIDs-MBP:functions davidcheah$ firebase deploy --only functions === Deploying to 'testfire-6b175'... i deploying functions i functions: ensuring necessary APIs are enabled... ✔ functions: all necessary APIs are enabled i functions: preparing functions directory for uploading... i functions: packaged functions (42.53 KB) for uploading ✔ functions: functions folder uploaded successfully i functions: creating Node.js 6 function getAccountInfo(us-central1)... i functions: updating Node.js 6 function register(us-central1)... i functions: updating Node.js 6 function login(us-central1)... i functions: updating Node.js 6 function destroySession(us-central1)... ✔ functions[getAccountInfo(us-central1)]: Successful create operation. Function URL (getAccountInfo): https://us-central1-testfire-6b175.cloudfunctions.net/getAccountInfo ✔ functions[destroySession(us-central1)]: Successful update operation. ✔ functions[login(us-central1)]: Successful update operation. ✔ functions[register(us-central1)]: Successful update operation. ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/testfire-6b175/overview DAVIDs-MBP:functions davidcheah$ firebase deploy --only functions
В этом примере пользователю будет показан экран входа/регистрации. Сначала пользователь регистрируется в Firestore, а внутренний скрипт проверяет наличие любой существующей созданной учетной записи.
exports.register = functions.https.onRequest((req, res) => { if (req.method === 'PUT') { res.status(403).send('Forbidden!'); return; } cors(req, res, () => { let name = req.query.name; //validations if (!name) { res.status(200).send("Please enter name."); return; } let email = req.query.email; if (!email) { res.status(200).send("Please enter email."); return; } let password = req.query.password; if (!password) { res.status(200).send("Please enter password."); return; } if (!validator.isLength(password, 3)) { res.status(200).send("Please enter valid password."); return; } if(!validator.isEmail(email)){ res.status(200).send("Please enter valid email."); return; } //check if user already exists in firestore var userRef = admin.firestore().collection('users') var userExists; userRef.where('email', '==', email).get() .then(snapshot => { userExists = snapshot.size; console.log(`user by email query size ${userExists}`); //send error if user exists if(userExists && userExists > 0){ res.status(200).send("Account exists with same email Id."); return; } //add user to database admin.firestore().collection('users').add({ name: name, email: email, password: password }).then(ref => { console.log('add user account', ref.id); res.status(200).send("User account created."); return; }); }) .catch(err => { console.log('error getting user by email', err); res.status(200).send("System error, please try again."); }); }); });
В случае успеха он направит пользователя на экран входа в систему, где пользователь вводит адрес электронной почты и пароль. Когда серверная часть проверяет совпадение, она отправляет ответ с уникальной строкой токена, которую приложение сохраняет в общей памяти настроек. Этот токен также хранится в Firestore.
exports.login = functions.https.onRequest((req, res) => { if (req.method === 'PUT') { res.status(403).send('Forbidden!'); return; } cors(req, res, () => { let email = req.query.email; //validation if (!email) { res.status(200).send("Please enter email."); return; } if(!validator.isEmail(email)){ res.status(200).send("Please enter valid email."); return; } let password = req.query.password; if (!password) { res.status(200).send("Please enter password."); return; } if (!validator.isLength(password, 3)) { res.status(200).send("Please enter valid password."); return; } //get password from db and match it with input password var userRef = admin.firestore().collection('users') userRef.where('email', '==', email).get() .then(snapshot => { if(snapshot.size > 1){ res.status(200).send("Invalid account."); return; } snapshot.forEach(doc => { console.log(doc.id, '=>', doc.data().name); var userPass = doc.data().password; //if password matches, generate token, save it in db and send it if(userPass && password == userPass){ const tokgenGen = new TokenGenerator(256, TokenGenerator.BASE62); const tokenStr = tokgenGen.generate(); //save token in db to use for other client request's authentication verification var tokenData = { email: doc.data().email}; admin.firestore().collection('tokens').doc(tokenStr).set(tokenData); res.status(200).send("token:"+tokenStr ); }else{ res.status(200).send("Invalid email/password."); } }); }) .catch(err => { console.log('error getting user by email', err); res.status(200).send("System error, please try again."); }); }); });
После успешного входа в систему пользователь перенаправляется на экран информации об учетной записи, где будет отображаться баланс. В фоновом режиме приложение извлекает уникальную строку токена и отправляет серверному сценарию запрос на получение информации об учетной записи. Бэкэнд-скрипт получает токен и проверяет соответствие в Firestore. Как только это совпадение, информация об учетной записи пользователя будет выпущена.
exports.getAccountInfo = functions.https.onRequest((req, res) => { if (req.method === 'PUT') { res.status(403).send('Forbidden!'); return; } cors(req, res, () => { let token = req.query.token; var tokenData = token; //validation if (!token) { res.status(200).send("Please login"); return; } else { var tokenDoc = admin.firestore().collection('tokens').doc(token); tokenDoc.get() .then(doc => { //if token exists then send data otherwise error response if (!doc.exists) { console.log('Invalid token'); res.status(200).send("Invalid token"); } else { console.log('valid token'); // TODO: Add code to get information from db // Lets assume we get account balance from db var accountBal = '$200'; res.status(200).send(accountBal); } }); } }); });
После того, как пользователь завершит работу, можно выйти из системы, чтобы очистить любой токен в памяти общих настроек приложения, а также в Firestore.
exports.destroySession = functions.https.onRequest((req, res) => { if (req.method === 'PUT') { res.status(403).send('Forbidden!'); return; } cors(req, res, () => { let token = req.query.token; var tokenData = token; //validation if (!token) { res.status(200).send("Please login"); return; } else { // Delete token entry from db var tokenDoc = admin.firestore().collection('tokens').doc(token).delete(); tokenDoc.delete(); res.status(200).send("Delete success"); } }); });
Вот как это выглядит в приложении и в Firestore:
Исходный код Android и облачных функций доступен на моем github: