前边我们已经完成了基本配置信息,下面我们就进一步实现我们的具体业务逻辑。
首先实现注册以及登陆模块的义务逻辑
1.注册
pass
2.登陆
pass
3.qq登陆
qq登陆的业务逻辑
①用户在浏览器发送请求得到一个url,这个url是为了获取一个二维码。
请求方式:GET /oauth/qq/authorization/?next=xxx
next解释:用户登陆成功后需要转到的界面
我们后端需要做的就是把这样的数据进行拼接然后返回
这是QQ开发文档提供的步骤:
根据接口文档我们进行数据的拼接:
# QQ登录参数
QQ_CLIENT_ID = '101474184'
QQ_CLIENT_SECRET = 'c6ce949e04e12ecc909ae6a8b09b637c'
QQ_REDIRECT_URI = 'http://www.meiduo.site:8080/oauth_callback.html'
QQ_STATE = '/'
因为前端给我们传了next参数,它指定了登陆成功后跳转的界面,我们需要把这个参数传入赋值给state,因为成功授权后会原样带回。
视图函数调用辅助工具类:
class QQAuthURLView(APIView):
def get(self, request):
# 获取QQ登陆的url
# 前端传过来的next
next = request.query_params.get('next')
oauth = OAuthQQ(state = next) # 将next传入辅助工具类
login_url = oauth.get_qq_login_url()
return Response({'login_url':login_url})
# 辅助工具类
class OAuthQQ(object):
def __init__(self, client_id = None, client_secret= None, redirect_uri = None, state = None):
self.client_id = client_id or settings.QQ_CLIENT_ID,
self.client_secret = client_secret or settings.QQ_CLIENT_SECRET
self.redirect_uri = redirect_uri or settings.QQ_RESIRECT_URI
self.state = state or settings.QQ_STATE # 用于保存登陆成功后的页面跳转路径
def get_qq_login_url(self):
"""
生成获取qq登陆二维码的url地址
:return:
"""
paramas = {
'response_type': 'code',
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'state': self.state,
'scope': 'get_user_info',
}
url = 'https://graph.qq.com/oauth2.0/authorize?'+ urlencode(paramas)
return url
到此前端接受我们的url,生成一个二维码
②用户进行扫码,获取用户Code值
如果用户成功登录并授权,则会跳转到指定的回调地址,并在redirect_uri地址后带上Code(过期时间10分钟)和原始的state值。
这样用户就得到了Code值,下面要做的就是将这个code值交给我们自己的应用,然后应用根据这个code 值向qq服务器发送请求获取Access Token
对应接口文档:
③同理,我们需要进行地址拼接,然后向qq服务器发送请求获取accsess token
先在辅助工具类内部增加方法:get_access_token()
def get_access_token(self, code):
"""
使用code,获取access_token
:param code: authorization code
:return: access_token
"""
# 准备url
url = 'https://graph.qq.com/oauth2.0/token?'
# 准备参数
params = {
'grant_type': 'authorization_code',
'client_id': self.client_id,
'client_secret': self.client_secret,
'code': code,
'redirect_uri': self.redirect_uri
}
# 将params字典转成查询字符串
query_params = urlencode(params)
# 拼接请求地址
url += query_params
try:
# 美多商城向QQ服务器发送GET请求
# (bytes)'access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14'
response_data = urlopen(url).read()
# (str)'access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14'
response_str = response_data.decode()
# 将response_str,转成字典
response_dict = parse_qs(response_str)
# 读取access_token
access_token = response_dict.get('access_token')[0]
except Exception as e:
logger.error(e)
# 在封装工具类的时候,需要捕获异常,并抛出异常,千万不要解决异常,谁用谁解决异常
# BookInfo.objects.gSerializeet() 类似于这样的一种思想
raise QQAPIException('获取access_token失败')
return access_token
class QQAuthUserView(APIView):
def get(self, request):
"""
处理oauth_callback回调页面时
提取code,access_token
"""
# 提取code请求参数
code = request.query_params.get('code')
if code is None:
return Response({'message':'缺少code'}, status=status.HTTP_400_BAD_REQUEST)
# 创建OAuthQQ对象
oauth = OAuthQQ()
try:
# 使用code向QQ服务器请求access_token
access_token = oauth.get_access_token(code) # 传入参数,调用方法获取access_token
要是没有得到,接口文档也给我们提供了返回信息的说明:
④有了access_token我们就可以用它获取open id
接口文档:
我们只需传入获取的access_token 就可以获取open_id
在辅助工具类内部增加方法:get_open_id
def get_open_id(self, access_token):
"""
使用access_token获取openid
:param access_token: 通过code 获取到的access_token
:return: openid
"""
# 准备请求地址
url = 'https://graph.qq.com/oauth2.0/me?access_token=%s' % access_token
response_str = ''
try:
# 美多商城向QQ服务器发送GET请求
# (bytes)'callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );'
response_data = urlopen(url).read()
response_str = response_data.decode()
# callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;
response_dict = json.loads(response_str[10:-4])
# 读取openid
open_id = response_dict.get('openid')
except Exception as e:
# 如果有异常,QQ服务器返回 "code=xxx&msg=xxx"
err_data = parse_qs(response_str)
logger.error(e)
raise QQAPIException('code=%s msg=%s' % (err_data.get('code'), err_data.get('msg')))
return open_id
class QQAuthUserView(APIView):
def get(self, request):
"""
处理oauth_callback回调页面时
提取code,access_token,openid
"""
# 提取code请求参数
# code = request.query_params.get('code')
# if code is None:
# return Response({'message':'缺少code'}, status=status.HTTP_400_BAD_REQUEST)
# 创建OAuthQQ对象
oauth = OAuthQQ()
try:
# 使用code向QQ服务器请求access_token
#access_token = oauth.get_access_token(code)
# 使用access_token向QQ服务器请求openid
open_id = oauth.get_open_id(access_token)
except QQAPIException as e:
logger.error(e)
return Response({'message': 'QQ服务异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
获取了open_id后就可以得到一个唯一识别qq用户的标识
然后根据这个open_id查数据库的表,看是否有这样的模型对象:
1.有说明不是第一次注册,生成JWT token(状态保持),返回:token,user_name,user_id;
2.没有说明是第一次登陆,在辅助工具类增加函数generate_save_user_token生成open id 签名(加密)后的结果,返回open id
class QQAuthUserView(GenericAPIView):
# 指定序列化器
serializer_class = serializers.QQAuthUserSerializer
def get(self, request):
"""
处理oauth_callback回调页面时
提取code,access_token,openid
"""
# 提取code请求参数
#code = request.query_params.get('code')
#if code is None:
#return Response({'message':'缺少code'}, status=status.HTTP_400_BAD_REQUEST)
# 创建OAuthQQ对象
#oauth = OAuthQQ()
#try:
# 使用code向QQ服务器请求access_token
#access_token = oauth.get_access_token(code)
# 使用access_token向QQ服务器请求openid
#open_id = oauth.get_open_id(access_token)
#except QQAPIException as e:
#logger.error(e)
#return Response({'message': 'QQ服务异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
# 使用openid查询该QQ用户是否在美多商城中绑定过用户
try:
# oauth_model : 代表一条记录,OAuthQQUser类型的对象
oauth_model = OAuthQQUser.objects.get(openid=open_id)
except OAuthQQUser.DoesNotExist:
# 如果openid没绑定美多商城用户,创建用户并绑定到openid
# 生成openid签名后的结果
token_openid = oauth.generate_save_user_token(open_id)
# 将openid签名之后的结果响应给用户
return Response({'access_token': token_openid})
# return Response({'openid':open_id})
else:
# 如果openid已绑定美多商城用户,直接生成JWT token,并返回
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
# 获取跟openid绑定的user对象
user = oauth_model.user
payload = jwt_payload_handler(user)
# JWT token
token = jwt_encode_handler(payload)
# 响应数据
return Response({
'token':token,
'user_id':user.id,
'username':user.username
})
前端根据返回值判断,要是返回open id,说明是第一次登陆,则过渡到绑定界面;否则直接跳转到应用主页
要是没绑定,前端会发送POST请求将手机号与open id绑定,要是用户注册过手机号,验证密码后直接绑定,要是没注册过,则先写入数据库,然后进行绑定(此过程需要序列化器所以视图类继承GenericAPIView,但是get方法,post方法还得写)
在视图中添加post方法:
def post(self, request):
"""openid绑定用户"""
# 创建序列化器
serializer = self.get_serializer(data=request.data) # 前端数据:手机号,密码,验证码,open_id
# 校验
serializer.is_valid(raise_exception=True)
# 调用create方法,实现绑定用户的保存
user = serializer.save()
# 生成状态保持信息
# 生成JWT 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)
return Response({
'token': token,
'username': user.username,
'user_id': user.id
})
对应的序列化器:
这里注意,前端将open id 保存在了access_token字段里边
class QQAuthUserSerializer(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
# 这里注意,前端将open id 保存在了access_token字段里边
access_token = data['access_token']
# 获取身份凭证
openid = OAuthQQ.check_save_user_token(access_token)
if not openid:
raise serializers.ValidationError('无效的access_token')
# 将openid放在校验字典中,后面会使用
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('密码错误')
# 将认证后的user放进校验字典中,后续会使用
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'],
)
# 将用户绑定openid
OAuthQQUser.objects.create(
openid=validated_data['openid'],
user=user
)
# 返回用户数据
return user