通用技术 React Native Android - 简单的用户登陆和 auth 处理 (Django RESTful)

andward_xu · 2016年01月05日 · 1463 次阅读

原帖:http://andward.gitcafe.io/js/android/%E5%89%8D%E7%AB%AF/django/2016/01/05/react-native-android-3.html

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)

@authentication_classes 中的参数是 auth 的类型,@permission_classes用来与 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={{uri: apiList.resourceWrapper('menu_white.png')}}
            title={title}
            titleColor="white"
            style={styles.toolbar}
            onIconClicked={() => this.refs[DRAWER_REF].openDrawer()}
            onActionSelected={this.onActionSelected} />
          <Navigator 
          initialRoute={{name: NAV_NAME.todo}}
          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 了。

共收到 0 条回复 时间 点赞
1楼 已删除
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册