1、浏览器请求服务器获取QQ登录地址
2、浏览器在QQ服务器登录成功重定向指定网址带code
3、在指定网址VUE生命周期打开网页时请求服务器传递code
4、后台服务器把code发送给QQ获取accesstoken,再把accesstoken发给QQ获取openid
5、服务器通过openid查询本地服务器查看是否登录过
6、如果登录过直接返回jwttoken
7、如果没登陆过,返回加密openid的token给浏览器(用Itsdangerous),让用户填写登录信息,
8、提交用户信息和openid的token,保存用户。
1、创建第三方用户模型表
工程目录/meiduo_mall/utils/models.py
from django.db import models
class BaseModel(models.Model):
"""为模型类补充字段"""
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
abstract = True # 说明是抽象模型类, 用于继承使用,数据库迁移时不会创建BaseModel的表
oauth/models.py
from django.db import models
from meiduo_mall.utils.models import BaseModel
class OAuthQQUser(BaseModel):
"""
QQ登录用户数据
"""
user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户')
openid = models.CharField(max_length=64, verbose_name='openid', db_index=True)
class Meta:
db_table = 'tb_oauth_qq'
verbose_name = 'QQ登录用户数据'
verbose_name_plural = verbose_name
2、配置文件
# QQ登录参数
QQ_APP_ID = '101474184'
QQ_APP_KEY = 'c6ce949e04e12ecc909ae6a8b09b637c'
QQ_REDIRECT_URL = 'http://www.meiduo.site:8080/oauth_callback.html'
oauth/utils.py
from urllib.parse import urlencode, parse_qs
from urllib.request import urlopen
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadData
from django.conf import settings
import json
import logging
from . import constants
logger = logging.getLogger('django')
class OAuthQQ(object):
"""
QQ认证辅助工具类
"""
def __init__(self, app_id=None, app_key=None, redirect_uri=None, state=None):
self.app_id = app_id or settings.QQ_APP_ID
self.app_key = app_key or settings.QQ_APP_KEY
self.redirect_url = redirect_uri or settings.QQ_REDIRECT_URL
self.state = state or '/' # 用于保存登录成功后的跳转页面路径
def get_auth_url(self):
"""
获取qq登录的网址
:return: url网址
"""
params = {
'response_type': 'code',
'client_id': self.app_id,
'redirect_uri': self.redirect_url,
'state': self.state,
'scope': 'get_user_info',
}
url = 'https://graph.qq.com/oauth2.0/authorize?' + urlencode(params)
return url
在oauth/views.py中实现视图
class QQAuthURLView(APIView):
"""
获取QQ登录的url
"""
def get(self, request):
"""
提供用于qq登录的url
"""
state = request.query_params.get('state')
oauth = OAuthQQ(state=state)
auth_url = oauth.get_auth_url()
return Response({'auth_url': auth_url})
修改login.js,在methods中增加qq_login方法
// qq登录
qq_login: function(){
var state = this.get_query_string('next') || '/';
axios.get(this.host + '/oauth/qq/authorization/?state=' + state, {
responseType: 'json'
})
.then(response => {
location.href = response.data.auth_url;
})
.catch(error => {
console.log(error.response.data);
})
}
3、OAuthQQ辅助类
def get_access_token(self, code):
"""
获取access_token
:param code: qq提供的code
:return: access_token
"""
params = {
'grant_type': 'authorization_code',
'client_id': self.app_id,
'client_secret': self.app_key,
'code': code,
'redirect_uri': self.redirect_url
}
url = 'https://graph.qq.com/oauth2.0/token?' + urlencode(params)
response = urlopen(url)
response_data = response.read().decode()
data = parse_qs(response_data)
access_token = data.get('access_token', None)
if not access_token:
logger.error('code=%s msg=%s' % (data.get('code'), data.get('msg')))
raise QQAPIError
return access_token[0]
def get_openid(self, access_token):
"""
获取用户的openid
:param access_token: qq提供的access_token
:return: open_id
"""
url = 'https://graph.qq.com/oauth2.0/me?access_token=' + access_token
response = urlopen(url)
response_data = response.read().decode()
try:
# 返回的数据 callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;
data = json.loads(response_data[10:-4])
except Exception:
data = parse_qs(response_data)
logger.error('code=%s msg=%s' % (data.get('code'), data.get('msg')))
raise QQAPIError
openid = data.get('openid', None)
return openid
@staticmethod
def generate_save_user_token(openid):
"""
生成保存用户数据的token
:param openid: 用户的openid
:return: token
"""
serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
data = {'openid': openid}
token = serializer.dumps(data)
return token.decode()
oauth/views.py
class QQAuthUserView(APIView):
"""
QQ登录的用户
"""
def get(self, request):
"""
获取qq登录的用户数据
"""
code = request.query_params.get('code')
if not code:
return Response({'message': '缺少code'}, status=status.HTTP_400_BAD_REQUEST)
oauth = OAuthQQ()
# 获取用户openid
try:
access_token = oauth.get_access_token(code)
openid = oauth.get_openid(access_token)
except QQAPIError:
return Response({'message': 'QQ服务异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
# 判断用户是否存在
try:
qq_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:
# 用户第一次使用QQ登录
token = oauth.generate_save_user_token(openid)
return Response({'access_token': token})
else:
# 找到用户, 生成token
user = qq_user.user
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
response = Response({
'token': token,
'user_id': user.id,
'username': user.username
})
return response
OAuthQQ辅助类
@staticmethod
def check_save_user_token(token):
"""
检验保存用户数据的token
:param token: token
:return: openid or None
"""
serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
try:
data = serializer.loads(token)
except BadData:
return None
else:
return data.get('openid')
oauth/serializers.py
class OAuthQQUserSerializer(serializers.Serializer):
"""
QQ登录创建用户序列化器
"""
access_token = serializers.CharField(label='操作凭证')
mobile = serializers.RegexField(label='手机号', regex=r'^1[3-9]\d{9}$')
password = serializers.CharField(label='密码', max_length=20, min_length=8)
sms_code = serializers.CharField(label='短信验证码')
def validate(self, data):
# 检验access_token
access_token = data['access_token']
openid = OAuthQQ.check_save_user_token(access_token)
if not openid:
raise serializers.ValidationError('无效的access_token')
data['openid'] = openid
# 检验短信验证码
mobile = data['mobile']
sms_code = data['sms_code']
redis_conn = get_redis_connection('verify_codes')
real_sms_code = redis_conn.get('sms_%s' % mobile)
if real_sms_code.decode() != sms_code:
raise serializers.ValidationError('短信验证码错误')
# 如果用户存在,检查用户密码
try:
user = User.objects.get(mobile=mobile)
except User.DoesNotExist:
pass
else:
password = data['password']
if not user.check_password(password):
raise serializers.ValidationError('密码错误')
data['user'] = user
return data
def create(self, validated_data):
user = validated_data.get('user')
if not user:
# 用户不存在
user = User.objects.create_user(
username=validated_data['mobile'],
password=validated_data['password'],
mobile=validated_data['mobile'],
)
OAuthQQUser.objects.create(
openid=validated_data['openid'],
user=user
)
return user
oauth/views.py
class QQAuthUserView(GenericAPIView):
"""
QQ登录的用户
"""
serializer_class = OAuthQQUserSerializer
def post(self, request):
"""
保存QQ登录用户数据
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
# 生成已登录的token
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
response = Response({
'token': token,
'user_id': user.id,
'username': user.username
})
return response