使用 Flask 和 rauth 进行 Github Oauth 登陆

lihua184 8年前

来自: http://python.jobbole.com/84283/

最近我研究了一下Flask的OAuth库。情况并不乐观,大部分的包都已过时并且不再被支持了。大家熟知的Flask-Oauth基本上已经被废弃了,我也不推荐依赖这个库。有趣的是OAuth本身其实是很简单的,认证之后你会得到一个访问token,你只需将其作为GET参数或者作为request的特殊的header即可。也许其他的OAuth提供者需要更复杂的逻辑,但是Github仅此就足够。

我在StackOverflow上请教了如何解决Flask-OAuth的问题 ,从rauth的作者那里得到回答,他建议试一下他写的库。本文就是对他及开源社区的回报。我写了这个真实的例子,告诉你如何使用Flask、rauth、Github和会话来对你的站点的用户进行认证。

首先你需要设置好本地的环境。你需要安装好python、pip和virtualenv。接下来对环境进行设置:

$ virtualenv venv  $ source venv/bin/activate   (venv)$ pip install rauth Flask Flask-SQLAlchemy
$ virtualenvvenv  $ sourcevenv/bin/activate   (venv)$ pipinstallrauthFlaskFlask-SQLAlchemy

如果要运行我的例子,你还需要安装Sqlite的python绑定,SQLAlchemy会需要它:

(venv)$ pip install pysqlite
(venv)$ pipinstallpysqlite

pip会安装所有需要的依赖,所以你的环境已经就绪了。

现在你需要到Github设置页面里找到 Applications sections ,为你设置好一个新的应用。下图显示了在我的配置区域。

下面是代码,主模块如下:

# github.py  from flask import (Flask, flash, request, redirect,      render_template, url_for, session)  from flask.ext.sqlalchemy import SQLAlchemy    from rauth.service import OAuth2Service    # Flask config  SQLALCHEMY_DATABASE_URI = 'sqlite:///github.db'  SECRET_KEY = '\xfb\x12\xdf\xa1@i\xd6>V\xc0\xbb\x8fp\x16#Z\x0b\x81\xeb\x16'  DEBUG = True    # Flask setup  app = Flask(__name__)  app.config.from_object(__name__)  db = SQLAlchemy(app)    # Use your own values in your real application   github = OAuth2Service(      name='github',      base_url='https://api.github.com/',      access_token_url='https://github.com/login/oauth/access_token',      authorize_url='https://github.com/login/oauth/authorize',      client_id= '477151a6a9a9a25853de',      client_secret= '23b97cc6de3bea712fddbef70a5f5780517449e4',  )    # models  class User(db.Model):      id = db.Column(db.Integer, primary_key=True)      login = db.Column(db.String(80), unique=True)      name = db.Column(db.String(120))        def __init__(self, login, name):          self.login = login          self.name = name        def __repr__(self):          return '<User %r>' % self.login        @staticmethod      def get_or_create(login, name):          user = User.query.filter_by(login=login).first()          if user is None:              user = User(login, name)              db.session.add(user)              db.session.commit()          return user    # views  @app.route('/')  def index():      return render_template('login.html')    @app.route('/about')  def about():      if session.has_key('token'):          auth = github.get_session(token = session['token'])          resp = auth.get('/user')          if resp.status_code == 200:              user = resp.json()            return render_template('about.html', user = user)      else:          return redirect(url_for('login'))    @app.route('/login')  def login():      redirect_uri = url_for('authorized', next=request.args.get('next') or          request.referrer or None, _external=True)      print(redirect_uri)      # More scopes http://developer.github.com/v3/oauth/#scopes      params = {'redirect_uri': redirect_uri, 'scope': 'user:email'}      print(github.get_authorize_url(**params))      return redirect(github.get_authorize_url(**params))    # same path as on application settings page  @app.route('/github/callback')  def authorized():      # check to make sure the user authorized the request      if not 'code' in request.args:          flash('You did not authorize the request')          return redirect(url_for('index'))        # make a request for the access token credentials using code      redirect_uri = url_for('authorized', _external=True)        data = dict(code=request.args['code'],          redirect_uri=redirect_uri,          scope='user:email,public_repo')        auth = github.get_auth_session(data=data)        # the "me" response      me = auth.get('user').json()        user = User.get_or_create(me['login'], me['name'])        session['token'] = auth.access_token      session['user_id'] = user.id        flash('Logged in as ' + me['name'])      return redirect(url_for('index'))    if __name__ == '__main__':      db.create_all()      app.run()
# github.py  fromflaskimport (Flask, flash, request, redirect,      render_template, url_for, session)  fromflask.ext.sqlalchemyimportSQLAlchemy     fromrauth.serviceimportOAuth2Service     # Flask config  SQLALCHEMY_DATABASE_URI = 'sqlite:///github.db'  SECRET_KEY = '\xfb\x12\xdf\xa1@i\xd6>V\xc0\xbb\x8fp\x16#Z\x0b\x81\xeb\x16'  DEBUG = True     # Flask setup  app = Flask(__name__)  app.config.from_object(__name__)  db = SQLAlchemy(app)     # Use your own values in your real application  github = OAuth2Service(      name='github',      base_url='https://api.github.com/',      access_token_url='https://github.com/login/oauth/access_token',      authorize_url='https://github.com/login/oauth/authorize',      client_id= '477151a6a9a9a25853de',      client_secret= '23b97cc6de3bea712fddbef70a5f5780517449e4',  )     # models  class User(db.Model):      id = db.Column(db.Integer, primary_key=True)      login = db.Column(db.String(80), unique=True)      name = db.Column(db.String(120))         def__init__(self, login, name):          self.login = login          self.name = name         def__repr__(self):          return '<User %r>' % self.login         @staticmethod      defget_or_create(login, name):          user = User.query.filter_by(login=login).first()          if useris None:              user = User(login, name)              db.session.add(user)              db.session.commit()          return user     # views  @app.route('/')  defindex():      return render_template('login.html')     @app.route('/about')  defabout():      if session.has_key('token'):          auth = github.get_session(token = session['token'])          resp = auth.get('/user')          if resp.status_code == 200:              user = resp.json()             return render_template('about.html', user = user)      else:          return redirect(url_for('login'))     @app.route('/login')  deflogin():      redirect_uri = url_for('authorized', next=request.args.get('next') or          request.referreror None, _external=True)      print(redirect_uri)      # More scopes http://developer.github.com/v3/oauth/#scopes      params = {'redirect_uri': redirect_uri, 'scope': 'user:email'}      print(github.get_authorize_url(**params))      return redirect(github.get_authorize_url(**params))     # same path as on application settings page  @app.route('/github/callback')  defauthorized():      # check to make sure the user authorized the request      if not 'code' in request.args:          flash('You did not authorize the request')          return redirect(url_for('index'))         # make a request for the access token credentials using code      redirect_uri = url_for('authorized', _external=True)         data = dict(code=request.args['code'],          redirect_uri=redirect_uri,          scope='user:email,public_repo')         auth = github.get_auth_session(data=data)         # the "me" response      me = auth.get('user').json()         user = User.get_or_create(me['login'], me['name'])         session['token'] = auth.access_token      session['user_id'] = user.id         flash('Logged in as ' + me['name'])      return redirect(url_for('index'))     if __name__ == '__main__':      db.create_all()      app.run()

模板:

<!-- templates/about.html -->  <!doctype html>  <title>Login</title>    <h1>About you</h1>  Welcome , and your email is  <br />  <a href ="">Main page</a>
<!-- templates/about.html -->  <!doctypehtml>  <title>Login</title>     <h1>Aboutyou</h1>  Welcome , and youremailis  <br />  <a href ="">Mainpage</a>
<!-- templates/login.html -->  <!doctype html>  <title>Login</title>    <h1>Login</h1>  <a href ="">Login with OAuth2</a>  <h1>About</h1>  <a href="">About</a> If you are not logged in then you will be redirected to login page.
<!-- templates/login.html -->  <!doctypehtml>  <title>Login</title>     <h1>Login</h1>  <a href ="">LoginwithOAuth2</a>  <h1>About</h1>  <a href="">About</a> If youarenot loggedin then youwillberedirectedto loginpage.

启动后将创建数据库:

(venv)$ python github.py
(venv)$ pythongithub.py

在浏览器打开网站后,将在 session 保存 login.access_token,这是不安全的,但作为例子已经足够了。实际的网站中你可以这样使用:

# Probably your User model placed in different blueprint  from authmodule.models import User    @app.before_request  def before_request():      g.user = None      if 'user_id' in session:          if session['user_id']:              user = User.query.get(session['user_id'])              if user:                  g.user = user              else:                  del session['user_id']
# Probably your User model placed in different blueprint  fromauthmodule.modelsimportUser     @app.before_request  defbefore_request():      g.user = None      if 'user_id' in session:          if session['user_id']:              user = User.query.get(session['user_id'])              if user:                  g.user = user              else:                  delsession['user_id']
</div>