Flutter와 Firebase로 Android iOS 둘 다 만들기 25 사용자 권한 처리하기

3 분 소요

사용자 권한 별로 다른 화면을 표시합니다.

개요

로그인해서 사용자별 다른 액션을 취하려면 firebase auth user로는 정보가 부족합니다.

firebase auth user는 권한, 그룹등의 관리용 데이터를 넣을 수 없습니다.

그래서 파이어베이스 유저의 uid를 사용해서 사용자 관리 데이터베이스를 만들어야합니다.

그러려면 파이어스토어에 정보를 기입해야되는데 동기화 문제와 관리 문제가 있습니다.

final user = await _googleSignIn(); 
await Firestore.instance.collection('users').document(user.uid).setData({ 'email': user.email, 'level': 3 });

예를 들어 위와 같이 회원가입과 동시에 uid로 정보를 넣을 때 파이어스토어에 잘 들어가기를 희망하지만.. 인터넷 상황과 메모리부족등의 다양한 이유로 데이터가 못 들어갈 수도 있습니다.

그렇게 될 경우가 있기 때문에 확인 로직등이 구차하게 많이 들어가야합니다.

특히 사용자를 삭제할 때 파이어베이스 사용자는 지웠는데 파이어스토어 데이터는 못지울 경우도 있기 때문에 사용자의 생성, 삭제 이벤트를 받아서 서버에서 처리하는 것이 좋습니다.

관리웹

사용자 관리를 하려면 웹이 가장 적절하다고 생각합니다.

관리웹을 만드실 거면 아래 링크로 만들어보면 됩니다.

Vue와 Firebase로 모던웹사이트 만들기

관리웹이 어렵다면 간단하게 백그라운드 이벤트 처리가 가능한 functions만 사용하면 됩니다.

계획

  • 사용자 권한(level) 낮을 수록 높은 권한
  • 사용자 생성시 권한 초기값은 5
  • 관리자만 사용자 권한을 변경 가능

기호에 맞게 하시면 됩니다.

firebase functions 사용하기

파이어베이스 펑션스는 기본적으로 REST API를 처리하는 용도로 사용하지만 다양한 이벤트 처리도 할 수 있습니다.(파이어스토어의 변경 감지, 사용자 생성, 삭제 이벤트등)

설치는 Vue와 Firebase로 모던웹사이트 만들기 1 개발환경 구축하기 에서 설명했기 때문에 사용자 이벤트 부분만 간단히 요약합니다.

펑션스 이벤트 만들기

참고: https://firebase.google.com/docs/auth/extend-with-functions?authuser=0

펑션스는 사용자가 생성, 삭제 될 때 사용자 정보를 얻을 수 있습니다.

이때 파이어스토어에 필요한 기본 정보를 넣어줍니다.

functions/index.js

const functions = require('firebase-functions')
const admin = require('firebase-admin')

const serviceAccount = require('./test-ff6-firebase-adminsdk-u40fa-d02d4e5a01.json')

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://test-ff6.firebaseio.com'
})

exports.createUser = functions.auth.user().onCreate(async (user) => {
  const data = {
    email: user.email,
    level: 5
  }
  admin.firestore().collection('users').doc(user.uid).set(data)
})

exports.deleteUser = functions.auth.user().onDelete(async (user) => {
  admin.firestore().collection('users').doc(user.uid).delete()
})

이제 어떤 플랫폼에서든 생성과 삭제시 알아서 파이어스토어에 생성되고 삭제됩니다.

배포

$ firebase deploy --only functions

룰 설정하기

파이어스토어는 자유롭게 쓸 수 있는 만큼 위험합니다.

항상 보안에 각별히 신경 써야합니다.

참고: https://firebase.google.com/docs/firestore/security/insecure-rules?authuser=0

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow read: if uid == request.auth.uid || get(/databases/$(database)/documents/users/$(request.auth.uid)).data.level < 5;
      allow create: if false;
      allow update: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.level < 4;
      allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.level == 0;
    }
  }
}
  • read: 본인이 읽거나 레벨이 5보다 작은 계정(최소한 승인은 된 계정 4)
  • create: 막아놓음
  • update: 레벨이 4보다 작은 계정이 수정가능(승인도 되고 해당 그룹등을 통제할 수 있는 계정 3)
  • delete: 최고권한자만 삭제 가능

이 보안 설정은 클라이언트에 해당되는 것입니다.

펑션스는 admin이므로 이런 보안 따위는 무시합니다.

get은 결국 해당 데이터를 읽는 것이기 때문에 read 횟수가 1증가합니다. 룰 작성시 과금도 신경써야합니다.

배포

$ firebase deploy --only firestore

홈페이지에서 표시

pages/home.dart

Widget _buildBody() {
  return StreamBuilder<DocumentSnapshot>(
    stream: Firestore.instance.collection('users').document(user.uid).snapshots(),
    initialData: null,
    builder: (context, snapshot) {
      if (!snapshot.hasData) return Center(child: CircularProgressIndicator());
      Map<String, dynamic> u = snapshot.data.data;
      if (u['level'] > 4) return Text('등급업이 필요합니다');
      return Text('환영합니다');
    },
  );
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('HomePage'),
      actions: <Widget>[
        _buildProfile(context),          
      ],
    ),
    body: _buildBody(),
  );
}

이제 파이어스토어의 변화를 감지해서 화면에 표시할 수 있습니다.

사용자 클래스 만들기

사용자 클래스를 만들어서 나중을 도모합니다.

참고

models/user.dart

import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';

@JsonSerializable()

class User {
  final String email;
  int level;
  User(this.email, this.level);
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this); 
}

직렬화해서 사용하면 됩니다.

$ flutter pub run build_runner build

적용하기

pages/home.dart

User u = User.fromJson(snapshot.data.data);
if (u.level > 4) return Text('등급업이 필요합니다');

공용 위젯 만들기

자주 쓰는 위젯을 따로 빼두었습니다.

widgets/loading.dart

import 'package:flutter/material.dart';

Widget widgetLoading() {  
  return Center(
    child: CircularProgressIndicator(),
  );
}

widgets/message.dart

import 'package:flutter/material.dart';

Widget widgetMessage(String message, IconData icon) {  
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(message),
        Icon(icon),
      ],
    ),
  );
}

완성

Widget _buildBody() {
  return StreamBuilder<DocumentSnapshot>(
    stream: Firestore.instance.collection('users').document(user.uid).snapshots(),
    initialData: null,
    builder: (context, snapshot) {
      if (!snapshot.hasData) return widgetLoading();
      User u = User.fromJson(snapshot.data.data);
      if (u.level > 4) return widgetMessage('등급업이 필요합니다', Icons.error_outline);
      return widgetMessage('환영합니다', Icons.check_circle_outline);
    },
  );
}

alt fin

마치며

웹까지 해야되니 괴로울 수도 있지만.. 회원관리를 하려면 어쩔 수 없는 것입니다.

원하는 기능만 사용한다면 웹도 충분히 해볼만합니다.

나중에 푸시를 생각해서라도 모바일을 개발하려면 서버는 꼭 필요합니다.

앱을 출시할 경우 이용 약관 같은 것도 어짜피 url로 넘기게 되어 있는데 vue&firebase를 조금만 익혀도 손쉽게 해결할 수 있는 문제들입니다.

소스

functions: https://github.com/fkkmemi/ff2

flutter: https://github.com/fkkmemi/ff2

영상

댓글남기기