客户端 web server QQ互联
可直接使用QQ登录工具QQLoginTool
pip install QQLoginTool
1. 获取QQ登陆扫码页面
class QQAuthURLView(View):
"""提供QQ登陆扫码页面"""
def get(self, request):
# 接收next
next = request.GET.get('next')
# 创建工具对象
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,
redirect_uri=settings.QQ_REDIRECT_URI,
state=next)
# 生成QQ登陆扫码连接地址
login_url = oauth.get_qq_url()
# 响应
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'login_url': login_url})
2. 接收Authorization Code,OAuth2.0认证获取openid
class QQAuthUserView(View):
"""处理QQ登陆回调:oauth_callback"""
def get(self, request):
"""处理QQ回调的业务逻辑"""
# 获取code
code = request.GET.get('code')
if not code:
return http.HttpResponseForbidden('获取code失败')
# 创建工具对象
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,
redirect_uri=settings.QQ_REDIRECT_URI)
try:
# 使用code获取access_token
access_token = oauth.get_access_token(code)
# 使用access_token获取openid
openid = oauth.get_open_id(access_token)
except Exception as e:
logger.error(e)
return http.HttpResponseServerError('OAuth2.0认证失败')
# 使用openid判断该QQ用户是否绑定过商城的用户
try:
oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:
# openid未绑定商城用户
access_token_openid = generate_access_token(openid)
context = {'access_token_openid': access_token_openid}
return render(request, 'oauth_callback.html', context)
else:
# openid已绑定商城用户,oauth_user.user表示从QQ登陆模型类对象中找到对应的用户模型类对象
login(request, oauth_user.user)
# 重定向到state:从哪来,QQ登录完之后回哪儿去
next = request.GET.get('state')
response = redirect(next)
# 将用户名写入到cookie中
response.set_cookie('username', oauth_user.user.username, max_age=3600)
# 响应QQ登陆结果
return response
def post(self, request):
"""商城用户绑定到openid"""
# 接收参数
mobile = request.POST.get('mobile')
password = request.POST.get('password')
sms_code_client = request.POST.get('sms_code')
access_token_openid = request.POST.get('access_token_openid')
# 校验参数
# 判断参数是否齐全
if not all([mobile, password, sms_code_client, access_token_openid]):
return http.HttpResponseForbidden('缺少必传参数')
# 判断手机号是否合法
if not re.match(r'^1[3-9]\d{9}$', mobile):
return http.HttpResponseForbidden('请输入正确的手机号')
# 判断密码是否合格
if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
return http.HttpResponseForbidden('请输入8-20位的密码')
# 判断短信验证码是否一致
redis_conn = get_redis_connection('verify_code')
sms_code_server = redis_conn.get('sms_%s' % mobile)
if sms_code_server is None:
return render(request, 'oauth_callback.html', {'error_sms_code': '无效的短信验证码'})
if sms_code_server.decode() != sms_code_client:
return render(request, 'oauth_callback.html', {'error_sms_code': '输入短信验证码有误'})
# 判断openid是否有效
# openid使用itsdangerous签名之后只有600秒的有效期
openid = check_access_token(access_token_openid)
if not openid:
return render(request, 'oauth_callback.html', {'openid_errmsg': 'openid已失效'})
# 使用手机号查询对应的用户是否存在
try:
user = User.objects.get(mobile=mobile)
except User.DoesNotExist:
# 如果手机号用户不存在,新建用户
user = User.objects.create_user(username=mobile, password=password, mobile=mobile)
else:
# 如果手机号用户存在,需要校验密码
if not user.check_password(password):
return render(request, 'oauth_callback.html', {'account_errmsg': '账号或密码错误'})
# 将新建及已存在用户绑定到openid
# oauth_qq_user = OAuthQQUser(user=user, openid=openid)
# oauth_qq_user.save()
try:
oauth_qq_user = OAuthQQUser.objects.create(user=user, openid=openid)
except Exception as e:
logger.error(e)
return render(request, 'oauth_callback.html', {'qq_login_errmsg': 'QQ登录失败'})
# 实现状态保持
login(request, user)
# 重定向到state
next = request.GET.get('state')
response = redirect(next)
# 将用户名写入到cookie中
response.set_cookie('username', oauth_qq_user.user.username, max_age=3600)
# 响应QQ登陆结果
return response
3. 演示
(1) 前端用户点击QQ登陆按钮,生成了login_url
(2) 用户进入扫码页面,扫码后:
由于此时用户QQ账号未与商城内用户绑定,因此定义序列化方法将openid以隐藏标签的形式存储在前端页面
def generate_access_token(openid):
"""
签名、序列化openid
:param openid: openid明文
:return: token(openid密文)
"""
# 创建序列化器对象
# s = Serializer('密钥: 越复杂越安全', '过期时间')
s = Serializer(settings.SECRET_KEY, constants.ACCESS_TOKEN_EXPIRES)
# 准备待序列化的字典数据
data = {'openid': openid}
# 调用dumps方法进行序列化: 类型是bytes
token = s.dumps(data)
# 返回序列化后的数据
return token.decode()
<input type="hidden" name="access_token_openid" value="eyJhbGciOiJIUzUxMiIsImlhdCI6MTU4MzQxNDcwNywiZXhwIjoxNTgzNDE1MzA3fQ.eyJvcGVuaWQiOiI1ODU0MDQ3Q0Q3MjhGOEQ2N0IyMERERTdDMDhCOTcwRiJ9.OkbAjloC01h3oH3YmmbW4lhwR4oCHYqCFQdY7DM-5jH0WggxUQgkQkZoLV-U_Bo7pz0BJ8PzrOQb1wKYEHN4JA">
(3) 用户开始绑定QQ账号和商城内账号
当用户点击绑定时,后端将前端暂存的加密后的openid再次解密。
并将openid作为字段存储在QQ用户认证库中。
随后将重定向到next字段记录的登陆前用户希望访问的页面。