RN的官网并没有专门处理用户登陆的doc。对于IOS,已经有大神写了一个cookie的插件:react-cookie,然而并不支持android(⊙o⊙)…于是便手动搭建一套简易的用户登陆以及maintain auth的流程。大概的思路是登陆后生成一个token并把它传回。RN拿到token后存在本地,之后每一次请求都会在header中携带此token,如果auth fail或者token不存在则清掉本地数据,并跳回登陆页。
Server端Auth认证
后台用的是Django Restful API。之前没有加上auth的服务,这里快速记录一下。
首先在settings.py中引入auth模块和auth的配置。这里有三种auth的模式可以选择:
- BasicAuthentication是最基本的auth认证,需要每次都提供username和pwd,故不选择。
- SessionAuthentication最好是RN端有cookie机制,故也不选择。
- TokenAuthentication利用token key送到sever端与value匹配。
最后就选择了token。有兴趣的同学可以研究一下OAuth2,其由于有refresh token和expiry date更为安全。
INSTALLED_APPS = (
'rest_framework.authtoken',
)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',),
}
加好后需要python manage.py syncdb一下。Django版本小于1.7的需要用Southmigrate,具体请参考South的doc。完成后会生成与User关联的Auth table。
接着在serializers.py中添加User的serializer:
class userSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email')
然后在你需要auth的API上加上auth的decorators,例如下面:
@api_view(['POST'])
@authentication_classes((TokenAuthentication,))
@permission_classes((IsAuthenticated,))
def createComment(request, task_id=None):
created_time = datetime.datetime.now()
request.data['time'] = created_time
serializer = commentSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@authenticationclasses中的参数是auth的类型,@permissionclasses用来与request中的token进行authentication。
最后在urls.py中加上:
urlpatterns += patterns('',
(r'^api/token-auth$', views.obtain_auth_token),
)
这个url是RESTFul自带的url,用来generate token。只要auth pass就会返回一个token的JSON。
RN端搭建
这里存取token用的是RN的AsyncStorage,这里是其官方doc。引用原文:AsyncStorage is a simple, asynchronous, persistent, key-value storage system that is global to the app. It should be used instead of LocalStorage.
Token存取
我们先在顶层component中加入token存取的方法,这里我加在navigator的component中。直接贴项目里面code:
nav.js
const DRAWER_REF = 'drawer';
const NAV_NAME = {
todo: "TODO",
done: "DONE",
detail: "Detail",
newTodo: "New Todo",
signIn: "Sign In",
};
var NavBar = React.createClass({
getInitialState: function() {
return ({
title: null,
token: '',
});
},
getToken: async function() {
var new_token = await AsyncStorage.getItem('token');
this.setState({token: new_token});
},
updateToken: async function(value) {
await AsyncStorage.setItem('token', 'Token ' + value);
},
removeToken: async function() {
await AsyncStorage.removeItem('token');
},
authFail: async function() {
await this.removeToken();
await this._navigator.push({
name: NAV_NAME.signIn,
});
this.refs[DRAWER_REF].closeDrawer();
},
renderScene: function(router, navigator){
var Component = null;
this._navigator = navigator;
switch(router.name){
case NAV_NAME.done:
Component = DonePage;
break;
case NAV_NAME.todo: //default view
Component = TodoPage;
break;
case NAV_NAME.detail:
Component = DetailPage;
break;
case NAV_NAME.newTodo:
Component = NewTodoPage;
break;
case NAV_NAME.signIn:
Component = SignInPage;
break;
}
return <Component
navigator={navigator}
todo={router.todo}
token={this.state.token}
getToken={this.getToken}
updateToken={this.updateToken}
authFail={this.authFail}/>
},
...
switchNav: function(name) {
this.setState({title: name});
this._navigator.push({name: this.state.title});
this.refs[DRAWER_REF].closeDrawer();
},
...
navView: function() {
return (
<View style = {styles.nav}>
<View style = {styles.header}>
<Text style = {styles.headerText}>ANDWARD</Text>
<Text style = {styles.headerText}>.TODO</Text>
</View>
<View style = {styles.item}>
<Text style = {styles.text}
onPress= {() => {this.switchNav(NAV_NAME.todo);}}
>TODO</Text>
</View>
<View style = {styles.item}>
<Text style = {styles.text}
onPress= {() => {this.switchNav(NAV_NAME.done);}}
>DONE</Text>
</View>
<View style = {styles.item}>
<Text style = {styles.text}>SETTINGS</Text>
</View>
<View style = {styles.item}>
<Text style = {styles.text}
onPress= {() => {this.authFail();}}
>Log Out</Text>
</View>
</View>);
},
render: function() {
var title = this.state.title ? this.state.title : NAV_NAME.todo;
return (
<DrawerLayoutAndroid
ref={DRAWER_REF}
drawerWidth={Dimensions.get('window').width - 56}
drawerPosition={DrawerLayoutAndroid.positions.Left}
renderNavigationView={this.navView}>
<View style = {styles.container}>
<ToolbarAndroid
navIcon=
title={title}
titleColor="white"
style={styles.toolbar}
onIconClicked={() => this.refs[DRAWER_REF].openDrawer()}
onActionSelected={this.onActionSelected} />
<Navigator
initialRoute=
configureScene={this.configureScene}
renderScene={this.renderScene} />
</View>
</DrawerLayoutAndroid>
);
}
});
这里定义了存取删token的三个函数:getToken,updateToken,removeToken。注意所有的 AsyncStorage method都是异步的,需要用ES6的async/await,这三个函数都作为参数传入compnent中。
登录页面的验证以及获取token:
sign.js
var SignInPage = React.createClass({
getInitialState: function() {
return {
username: false,
password: false,
};
},
signIn: function() {
fetch(apiList.SIGN_IN, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password,
})
})
.then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
} else {
this.clearInput();
}
})
.then((repsonseData) => {
this.props.updateToken(repsonseData.token);
this.props.navigator.push({
name: 'TODO'
});
});
},
...
});
注:this.props.updateToken用来存generate的token。
todo.js
var TodoPage = React.createClass({
getInitialState: function() {
return {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
};
},
componentDidMount: async function() {
await this.props.getToken();
await this.fetchData();
},
fetchData: function() {
fetch(apiList.TODO_API, {
headers: {
'Authorization': this.props.token,
}
})
.then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
} else {
this.props.authFail();
}
})
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData),
loaded: true,
});
});
},
});
this.props.getToken()从storage里面拿到generate的token并在顶层component中setState。这样所有的navigator都可以通过this.props.token拿到token,并用于请求。在fetch的headers中加入:'Authorization': this.props.token,server端就可以对request进行token authentication了。
稍后附上效果图~