Meteor開發(fā)指南 — 基于Meteor實(shí)現(xiàn)React Native用戶認(rèn)證

原文來自Meteor Authentication from React Native戚啥,這是Meteor React Native系列的第二篇,第一篇在這里,第二部分的Repo會在稍后放出。

這篇文章是上篇如何輕松連接一個React Native應(yīng)用到Meteor服務(wù)器的后續(xù)拓萌。我們將討論下一個你會接觸到的東西,也就是用戶認(rèn)證系統(tǒng)升略。我們會討論如何通過用戶名密碼微王,email密碼或通過一個恢復(fù)令牌(resume token)來進(jìn)行登錄。

創(chuàng)建應(yīng)用

在上一篇文章中已經(jīng)寫到了如何連接一個React Native應(yīng)用到Meteor服務(wù)器上品嚣,所以在此就不在贅述炕倘。如果你需要幫助,請參見上一篇文章翰撑。

作為開始罩旋,我們只需要clone上次的Github repo:

git clone https://github.com/spencercarli/quick-meteor-react-native

我們會采用這個倉庫的代碼作為起始代碼,但是我們需要做出一些小修改:

cd meteor-app && meteor add accounts-password

首先打開這個項(xiàng)目,然后添加accounts-password這個包涨醋。

然后瓜饥,創(chuàng)建RNApp/app/ddp.js:

import DDPClient from 'ddp-client'; 
let ddpClient = new DDPClient();

export default ddpClient; 

然后打開RNApp/app/index.js,將如下代碼進(jìn)行替換:

import DDPClient from 'ddp-client'; 
let ddpClient = new DDPClient(); 

替換為

import ddpClient from './ddp'; 

我們這么做是為了把注冊登錄邏輯放到index.js文件之外浴骂,讓項(xiàng)目結(jié)構(gòu)更清晰規(guī)范乓土。

創(chuàng)建用戶

在深入到登錄之前,我們需要了解如何創(chuàng)建用戶溯警。我們將借助Meteor核心方法createUser趣苏。我們將使用它來完成email和password的認(rèn)證。你可以在(Meteor docs)[http://docs.meteor.com/#/full/accounts_createuser]中查看這個方法有哪些參數(shù)和選項(xiàng)愧膀。

RNApp/app/ddp.js中拦键,添加如下代碼:

import DDPClient from 'ddp-client';  
let ddpClient = new DDPClient();

ddpClient.signUpWithEmail = (email, password, cb) => {  
  let params = {
    email: email,
    password: password
  };

  return ddpClient.call('createUser', [params], cb);
};

ddpClient.signUpWithUsername = (username, password, cb) => {  
  let params = {
    username: username,
    password: password
  };

  return ddpClient.call('createUser', [params], cb);
};

export default ddpClient;  

接下來我們會為它創(chuàng)建相應(yīng)UI。

探索Meteor方法login

Meteor核心提供了一個方法login檩淋,我們可以使用它來處理DDP連接的認(rèn)證。這意味著this.userId在Meteor方法和發(fā)布中可用萄金,你可以使用它來認(rèn)證蟀悦。這個login方法可以處理Meteor所有的登錄服務(wù),包括通過email氧敢,username日戈,resume token還有Oauth登錄(盡管這里并不涉及Oauth)。

使用login方法你傳遞一個object作為單一參數(shù)到函數(shù)中—object的形式?jīng)Q定了你如何登錄孙乖,下面是各種登錄形式:

For Email and Password:

{ user: { email: USER_EMAIL }, password: USER_PASSWORD }

For Username and Password:

{ user: { username: USER_USERNAME }, password: USER_PASSWORD }

For Resume Token:

{ resume: RESUME_TOKEN }

使用Email和Password登錄

RNApp/app/ddp.js中浙炼,添加如下代碼:

/*
 * Removed from snippet for brevity
 */

ddpClient.loginWithEmail = (email, password, cb) => {  
  let params = {
    user: {
      email: email
    },
    password: password
  };

  return ddpClient.call("login", [params], cb)
};

export default ddpClient; 

使用Username和Password登錄

RNApp/app/ddp.js中,添加如下代碼:

/*
 * Removed from snippet for brevity
 */

ddpClient.loginWithUsername = (username, password, cb) => {  
  let params = {
    user: {
      username: username
    },
    password: password
  };

  return ddpClient.call("login", [params], cb)
};

存儲用戶數(shù)據(jù)

我們將使用React Native中的AsyncStorage
API來存儲登錄令牌(login token)唯袄,令牌失效期(login token expiration)和用戶ID(userId)弯屈。這些數(shù)據(jù)會在成功登錄或者創(chuàng)建賬戶后返回。

RNApp/app/ddp.js中恋拷,添加如下代碼:

import DDPClient from 'ddp-client';  
import { AsyncStorage } from 'react-native';

/*
 * Removed from snippet for brevity
 */

ddpClient.onAuthResponse = (err, res) => {  
  if (res) {
    let { id, token, tokenExpires } = res;

    AsyncStorage.setItem('userId', id.toString());
    AsyncStorage.setItem('loginToken', token.toString());
    AsyncStorage.setItem('loginTokenExpires', tokenExpires.toString());
  } else {
    AsyncStorage.multiRemove(['userId', 'loginToken', 'loginTokenExpires']);
  }
}

export default ddpClient;  

這會將我們的憑證持久化存儲资厉,在下次重新打開app時就可以自動登錄了。

使用Resume Token登錄

存儲了用戶數(shù)據(jù)之后蔬顾,我們就可以用Resume Token進(jìn)行登錄了宴偿。

RNApp/app/ddp.js中,添加如下代碼:

/*
 * Removed from snippet for brevity
 */

ddpClient.loginWithToken = (loginToken, cb) => {  
  let params = { resume: loginToken };

  return ddpClient.call("login", [params], cb)
}

export default ddpClient;  

登出

RNApp/app/ddp.js中诀豁,添加如下代碼:

/*
 * Removed from snippet for brevity
 */

ddpClient.logout = (cb) => {  
  AsyncStorage.multiRemove(['userId', 'loginToken', 'loginTokenExpires']).
    then((res) => {
      ddpClient.call("logout", [], cb)
    });
}

export default ddpClient;  

先刪除AsyncStorage中的三個憑證窄刘,然后調(diào)用logout方法。

UI部分

First thing I want to do is break up RNApp/app/index
a bit. It'll make it easier to manage later on.

First, create RNApp/app/loggedIn.js:

import React, {  
  View,
  Text
} from 'react-native';

import Button from './button';

import ddpClient from './ddp';

export default React.createClass({  
  getInitialState() {
    return {
      posts: {}
    }
  },

  componentDidMount() {
    this.makeSubscription();
    this.observePosts();
  },

  observePosts() {
    let observer = ddpClient.observe("posts");
    observer.added = (id) => {
      this.setState({posts: ddpClient.collections.posts})
    }
    observer.changed = (id, oldFields, clearedFields, newFields) => {
      this.setState({posts: ddpClient.collections.posts})
    }
    observer.removed = (id, oldValue) => {
      this.setState({posts: ddpClient.collections.posts})
    }
  },

  makeSubscription() {
    ddpClient.subscribe("posts", [], () => {
      this.setState({posts: ddpClient.collections.posts});
    });
  },

  handleIncrement() {
    ddpClient.call('addPost');
  },

  handleDecrement() {
    ddpClient.call('deletePost');
  },

  render() {
    let count = Object.keys(this.state.posts).length;
    return (
      <View>
        <Text>Posts: {count}</Text>
        <Button text="Increment" onPress={this.handleIncrement}/>
        <Button text="Decrement" onPress={this.handleDecrement}/>
      </View>
    );
  }
});

你會發(fā)現(xiàn)上面的代碼和RNApp/app/index.js基本雷同舷胜。是的娩践,我們基本上就是把整個現(xiàn)有的app代碼移到了loggedIn.js文件中。下一步,我們將修改RNApp/app/index.js來使用新創(chuàng)建的loggedIn.js文件欺矫。

修改RNApp/app/index.js代碼如下:

import React, {  
  View,
  StyleSheet
} from 'react-native';

import ddpClient from './ddp';  
import LoggedIn from './loggedIn';

export default React.createClass({  
  getInitialState() {
    return {
      connected: false
    }
  },

  componentDidMount() {
    ddpClient.connect((err, wasReconnect) => {
      let connected = true;
      if (err) connected = false;

      this.setState({ connected: connected });
    });
  },

  render() {
    let body;

    if (this.state.connected) {
      body = <LoggedIn />;
    }

    return (
      <View style={styles.container}>
        <View style={styles.center}>
          {body}
        </View>
      </View>
    );
  }
});

const styles = StyleSheet.create({  
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#F5FCFF',
  },
  center: {
    alignItems: 'center'
  }
});

可以看到纱新,這里我們在index.js中使用了loggedIn中定義的<LoggedIn />組件。

UI部分:登錄

我們來創(chuàng)建一些登錄用的UI穆趴。我們只創(chuàng)建email登錄用的脸爱,但是使用username登錄完全可以。

創(chuàng)建RNApp/app/loggedOut.js

import React, {  
  View,
  Text,
  TextInput,
  StyleSheet
} from 'react-native';

import Button from './button';  
import ddpClient from './ddp';

export default React.createClass({  
  getInitialState() {
    return {
      email: '',
      password: ''
    }
  },

  handleSignIn() {
    let { email, password } = this.state;
    ddpClient.loginWithEmail(email, password, (err, res) => {
      ddpClient.onAuthResponse(err, res);
      if (res) {
        this.props.changedSignedIn(true);
      } else {
        this.props.changedSignedIn(false);
      }
    });

    // Clear the input values on submit
    this.refs.email.setNativeProps({text: ''});
    this.refs.password.setNativeProps({text: ''});
  },

  handleSignUp() {
    let { email, password } = this.state;
    ddpClient.signUpWithEmail(email, password, (err, res) => {
      ddpClient.onAuthResponse(err, res);
      if (res) {
        this.props.changedSignedIn(true);
      } else {
        this.props.changedSignedIn(false);
      }
    });

    // Clear the input values on submit
    this.refs.email.setNativeProps({text: ''});
    this.refs.password.setNativeProps({text: ''});
  },

  render() {
    return (
      <View>
        <TextInput
          style={styles.input}
          ref="email"
          onChangeText={(email) => this.setState({email: email})}
          autoCapitalize="none"
          autoCorrect={false}
          placeholder="Email"
        />
        <TextInput
          style={styles.input}
          ref="password"
          onChangeText={(password) => this.setState({password: password})}
          autoCapitalize="none"
          autoCorrect={false}
          placeholder="Password"
          secureTextEntry={true}
        />

        <Button text="Sign In" onPress={this.handleSignIn} />
        <Button text="Sign Up" onPress={this.handleSignUp} />
      </View>
    )
  }
});

const styles = StyleSheet.create({  
  input: {
    height: 40,
    width: 350,
    padding: 10,
    marginBottom: 10,
    backgroundColor: 'white',
    borderColor: 'gray',
    borderWidth: 1
  }
});

現(xiàn)在我們需要在index中展示我們的登出組件未妹。

RNApp/app/index.js添加和修改如下代碼:

/*
 * Removed from snippet for brevity
 */
import LoggedOut from './loggedOut';

export default React.createClass({  
  getInitialState() {
    return {
      connected: false,
      signedIn: false
    }
  },

  componentDidMount() {
    ddpClient.connect((err, wasReconnect) => {
      let connected = true;
      if (err) connected = false;

      this.setState({ connected: connected });
    });
  },

  changedSignedIn(status = false) {
    this.setState({signedIn: status});
  },

  render() {
    let body;

    if (this.state.connected && this.state.signedIn) {
      body = <LoggedIn changedSignedIn={this.changedSignedIn} />; // Note the change here as well
    } else if (this.state.connected) {
      body = <LoggedOut changedSignedIn={this.changedSignedIn} />;
    }

    return (
      <View style={styles.container}>
        <View style={styles.center}>
          {body}
        </View>
      </View>
    );
  }
});

快要完成了簿废!只剩下最后兩步啦。下面络它,我們要讓用戶能夠登出族檬。

RNApp/app/loggedIn.js中:

/*
 * Removed from snippet for brevity
 */

export default React.createClass({  
  /*
   * Removed from snippet for brevity
   */
  handleSignOut() {
    ddpClient.logout(() => {
      this.props.changedSignedIn(false)
    });
  },

  render() {
    let count = Object.keys(this.state.posts).length;
    return (
      <View>
        <Text>Posts: {count}</Text>
        <Button text="Increment" onPress={this.handleIncrement}/>
        <Button text="Decrement" onPress={this.handleDecrement}/>

        <Button text="Sign Out" onPress={() => this.props.changedSignedIn(false)} />
      </View>
    );
  }
});

最后一步!我們將實(shí)現(xiàn)自動登錄功能化戳。如果一個用戶在其AsyncStorage中有合法的loginToken单料,我們幫他自動登錄:

In RNApp/app/loggedOut.js:

import React, {  
  View,
  Text,
  TextInput,
  StyleSheet,
  AsyncStorage // Import AsyncStorage
} from 'react-native';

import Button from './button';  
import ddpClient from './ddp';

export default React.createClass({  
  getInitialState() {
    return {
      email: '',
      password: ''
    }
  },

  componentDidMount() {
    // Grab the token from AsyncStorage - if it exists then attempt to login with it.
    AsyncStorage.getItem('loginToken')
      .then((res) => {
        if (res) {
          ddpClient.loginWithToken(res, (err, res) => {
            if (res) {
              this.props.changedSignedIn(true);
            } else {
              this.props.changedSignedIn(false);
            }
          });
        }
      });
  },

  handleSignIn() {
    let { email, password } = this.state;
    ddpClient.loginWithEmail(email, password, (err, res) => {
      ddpClient.onAuthResponse(err, res);
      if (res) {
        this.props.changedSignedIn(true);
      } else {
        this.props.changedSignedIn(false);
      }
    });

    // Clear the input values on submit
    this.refs.email.setNativeProps({text: ''});
    this.refs.password.setNativeProps({text: ''});
  },

  /*
   * Removed from snippet for brevity
   */
});

一切完成!現(xiàn)在我們就能夠使用Meteor作為后端為React Native應(yīng)用提供用戶認(rèn)證点楼。它給你在Meteor Methods和Meteor Publications中提供了this.userId扫尖。我們可以更新meteor-app/both/posts.js文件中的addPost方法來測試一下:

'addPost': function() {  
  Posts.insert({
    title: 'Post ' + Random.id(),
    userId: this.userId
  });
},

看看userId是不是出現(xiàn)在新創(chuàng)建的post中了?

結(jié)論

我想在這里談一下安全性的問題掠廓,也是本篇文章所沒有涉及到的换怖。當(dāng)在生產(chǎn)環(huán)境下時,用戶傳輸?shù)氖撬麄兊恼鎸?shí)數(shù)據(jù)蟀瞧,請確保啟用SSL(對于Meteor應(yīng)用來說也是一樣)沉颂。同樣,我們也沒有在客戶端做密碼的hash悦污,所以密碼是以明文的形式傳輸?shù)闹搿_@同樣對SSL提出了需求。但是這里談及密碼hash會使文章變得冗長塞关。我們會在下篇文章中談及它抬探。

你可以在Github上查看本項(xiàng)目完整代碼:
https://github.com/spencercarli/meteor-react-native-authentication

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市帆赢,隨后出現(xiàn)的幾起案子小压,更是在濱河造成了極大的恐慌,老刑警劉巖椰于,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怠益,死亡現(xiàn)場離奇詭異,居然都是意外死亡瘾婿,警方通過查閱死者的電腦和手機(jī)蜻牢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門烤咧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抢呆,你說我怎么就攤上這事煮嫌。” “怎么了抱虐?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵昌阿,是天一觀的道長。 經(jīng)常有香客問我恳邀,道長懦冰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任谣沸,我火速辦了婚禮刷钢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乳附。我一直安慰自己内地,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般辜膝。 火紅的嫁衣襯著肌膚如雪涤垫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天清焕,我揣著相機(jī)與錄音并蝗,去河邊找鬼。 笑死秸妥,一個胖子當(dāng)著我的面吹牛滚停,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粥惧,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼键畴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了突雪?” 一聲冷哼從身側(cè)響起起惕,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咏删,沒想到半個月后惹想,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡督函,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年嘀粱,在試婚紗的時候發(fā)現(xiàn)自己被綠了激挪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡锋叨,死狀恐怖垄分,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情娃磺,我是刑警寧澤薄湿,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站豌鸡,受9級特大地震影響嘿般,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涯冠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一炉奴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛇更,春花似錦瞻赶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掌逛,卻和暖如春师逸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背豆混。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工篓像, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人皿伺。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓员辩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸵鸥。 傳聞我的和親對象是個殘疾皇子奠滑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容