server.py
# coding: utf-8
from datetime import datetime, timedelta
from flask import Flask
from flask import session, request
from flask import render_template, redirect, jsonify
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import gen_salt
from flask_oauthlib.provider import OAuth2Provider
from flask_restful import Api, Resource, fields, marshal_with
app = Flask(__name__, template_folder='templates')
app.debug = True
app.secret_key = 'secret'
app.config.update({
'SQLALCHEMY_DATABASE_URI': 'sqlite:///db.sqlite',
})
db = SQLAlchemy(app)
oauth = OAuth2Provider(app)
# 存储用户信息的ORM
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
# 存储客户端信息的ORM
class Client(db.Model):
client_id = db.Column(db.String(40), primary_key=True)
client_secret = db.Column(db.String(55), nullable=False)
user_id = db.Column(db.ForeignKey('user.id'))
user = db.relationship('User')
_redirect_uris = db.Column(db.Text)
_default_scopes = db.Column(db.Text)
@property
def client_type(self):
return 'public'
@property
def redirect_uris(self):
if self._redirect_uris:
return self._redirect_uris.split()
return []
@property
def default_redirect_uri(self):
return self.redirect_uris[0]
@property
def default_scopes(self):
if self._default_scopes:
return self._default_scopes.split()
return []
# 存储授权码信息的ORM
class Grant(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
client_id = db.Column(
db.String(40), db.ForeignKey('client.client_id'),
nullable=False,
)
client = db.relationship('Client')
code = db.Column(db.String(255), index=True, nullable=False)
redirect_uri = db.Column(db.String(255))
expires = db.Column(db.DateTime)
_scopes = db.Column(db.Text)
def delete(self):
db.session.delete(self)
db.session.commit()
return self
@property
def scopes(self):
if self._scopes:
return self._scopes.split()
return []
# 存储token信息的ORM
class Token(db.Model):
id = db.Column(db.Integer, primary_key=True)
client_id = db.Column(
db.String(40), db.ForeignKey('client.client_id'),
nullable=False,
)
client = db.relationship('Client')
user_id = db.Column(
db.Integer, db.ForeignKey('user.id')
)
user = db.relationship('User')
# currently only bearer is supported
token_type = db.Column(db.String(40))
access_token = db.Column(db.String(255), unique=True)
refresh_token = db.Column(db.String(255), unique=True)
expires = db.Column(db.DateTime)
_scopes = db.Column(db.Text)
@property
def scopes(self):
if self._scopes:
return self._scopes.split()
return []
def current_user():
if 'id' in session:
uid = session['id']
return User.query.get(uid)
return None
# 相当于/
@app.route('/', methods=('GET', 'POST'))
def home():
if request.method == 'POST':
username = request.form.get('username')
user = User.query.filter_by(username=username).first()
if not user:
user = User(username=username)
db.session.add(user)
db.session.commit()
session['id'] = user.id
return redirect('/')
user = current_user()
return render_template('home.html', user=user)
@app.route('/client')
def client():
'''
为登录用户注册一个新的客户端
:return:
'''
user = current_user()
if not user:
return redirect('/')
item = Client(
client_id=gen_salt(40),
client_secret=gen_salt(50),
_redirect_uris=' '.join([
'http://localhost:8000/authorized',
'http://127.0.0.1:8000/authorized',
'http://127.0.1:8000/authorized',
'http://127.1:8000/authorized',
]),
_default_scopes='email',
user_id=user.id,
)
db.session.add(item)
db.session.commit()
return jsonify(
client_id=item.client_id,
client_secret=item.client_secret,
)
@oauth.clientgetter
def load_client(client_id):
return Client.query.filter_by(client_id=client_id).first()
@oauth.grantgetter
def load_grant(client_id, code):
return Grant.query.filter_by(client_id=client_id, code=code).first()
@oauth.grantsetter
def save_grant(client_id, code, request, *args, **kwargs):
# decide the expires time yourself
expires = datetime.utcnow() + timedelta(seconds=100)
grant = Grant(
client_id=client_id,
code=code['code'],
redirect_uri=request.redirect_uri,
_scopes=' '.join(request.scopes),
user=current_user(),
expires=expires
)
db.session.add(grant)
db.session.commit()
return grant
@oauth.tokengetter
def load_token(access_token=None, refresh_token=None):
if access_token:
return Token.query.filter_by(access_token=access_token).first()
elif refresh_token:
return Token.query.filter_by(refresh_token=refresh_token).first()
@oauth.tokensetter
def save_token(token, request, *args, **kwargs):
toks = Token.query.filter_by(
client_id=request.client.client_id,
user_id=request.user.id
)
# make sure that every client has only one token connected to a user
for t in toks:
db.session.delete(t)
expires_in = token.pop('expires_in')
expires = datetime.utcnow() + timedelta(seconds=expires_in)
tok = Token(
access_token=token['access_token'],
refresh_token=token['refresh_token'],
token_type=token['token_type'],
_scopes=token['scope'],
expires=expires,
client_id=request.client.client_id,
user_id=request.user.id,
)
db.session.add(tok)
db.session.commit()
return tok
# 相当于oauth
@app.route('/oauth/token', methods=['GET', 'POST'])
@oauth.token_handler
def access_token():
return None
# 相当于login
@app.route('/oauth/authorize', methods=['GET', 'POST'])
@oauth.authorize_handler
def authorize(*args, **kwargs):
user = current_user()
if not user:
return redirect('/')
if request.method == 'GET':
client_id = kwargs.get('client_id')
client = Client.query.filter_by(client_id=client_id).first()
kwargs['client'] = client
kwargs['user'] = user
return render_template('authorize.html', **kwargs)
confirm = request.form.get('confirm', 'no')
return confirm == 'yes'
# 新的资源服务器
api = Api(app, decorators=[oauth.require_oauth('email')])
resource_fields = {
'username': fields.String(),
'date': fields.DateTime(default=str(datetime.now())),
'id': fields.Integer()
}
class ApiMe(Resource):
@marshal_with(resource_fields)
def get(self):
user = request.oauth.user
return user
api.add_resource(ApiMe, '/api/me')
if __name__ == '__main__':
db.create_all()
app.run()
client.py
# coding: utf-8
from flask import Flask, url_for, session, request, jsonify
from flask_oauthlib.client import OAuth
CLIENT_ID = '9UgleDGH6amWA8nylq6J7snNrNFyQ0XmCHkLoAp0'
CLIENT_SECRET = 'g24UB2cZlWL0BirD83BbfN3KhJQDmEXFn5Jm37Qq39gAeLA5FA'
app = Flask(__name__)
app.debug = True
app.secret_key = 'secret'
oauth = OAuth(app)
remote = oauth.remote_app(
'remote',
consumer_key=CLIENT_ID,
consumer_secret=CLIENT_SECRET,
request_token_params={'scope': 'email'},
base_url='http://127.0.0.1:5000/api/',
request_token_url=None,
access_token_url='http://127.0.0.1:5000/oauth/token',
authorize_url='http://127.0.0.1:5000/oauth/authorize'
)
# 相当于/client/login,用于重定向用户登录
@app.route('/')
def index():
if 'remote_oauth' in session:
resp = remote.get('me')
return jsonify(resp.data)
next_url = request.args.get('next') or request.referrer or None
return remote.authorize(
callback=url_for('authorized', next=next_url, _external=True)
)
# 相当于/client/passport,用于获取token,并存储在Session中
@app.route('/authorized')
def authorized():
resp = remote.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error_reason'],
request.args['error_description']
)
print(resp)
session['remote_oauth'] = (resp['access_token'], '')
return jsonify(oauth_token=resp['access_token'])
@remote.tokengetter
def get_oauth_token():
return session.get('remote_oauth')
if __name__ == '__main__':
import os
os.environ['DEBUG'] = 'true'
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true'
app.run(host='localhost', port=8000)
authorize.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Authorization</title>
</head>
<body>
<p>Client: {{ client.client_id }}</p>
<p>User: {{ user.username }}</p>
<form action="/oauth/authorize" method="post">
<p>Allow access?</p>
<input type="hidden" name="client_id" value="{{ client.client_id }}">
<input type="hidden" name="scope" value="{{ scopes|join(' ') }}">
<input type="hidden" name="response_type" value="{{ response_type }}">
<input type="hidden" name="redirect_uri" value="{{ redirect_uri }}">
{% if state %}
<input type="hidden" name="state" value="{{ state }}">
{% endif %}
<input type="submit" name="confirm" value="yes">
<input type="submit" name="confirm" value="no">
</form>
</body>
</html>
home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
{% if user %}
<p>You are {{ user.username }}</p>
{% else %}
<p>You are not authenticated</p>
{% endif %}
<p>Type any username:</p>
<form method="post" action="/">
<input type="text" name="username">
<input type="submit">
</form>
</body>
</html>