最近项目中需要微信登录,不仅要pc端登录,还要有app端的登录,今天,先记录一下pc端微信登录,首先我查看了微信登录的官网,里面写了好多,但是到最后总结,其实说了好多废话,好多步骤都是用不要的,其实只是需要两步就可以了:
- 向微信请求url,什么意思哪,就是后台需要请求一个链接,拿到链接之后,发送给前端,前端拿到这个url之后,发送请求,这样就会得到一个一个二维码,用户会可以扫这个二维码进行登录
class WeChatAuthURLView(APIView):
"""获取微信登录url(获取code)"""
def get(self, request):
# 先获取查询字符串中redirect url的值
# next = request.query_params.get('redirect')
oauth = OauthWeChat()
# 获取微信code的url
login_url = oauth.get_code_url()
return APIResponse.success(data={'login_url': login_url}, message='OK')
import requests
import json
import random
from urllib.parse import urlencode
from itsdangerous import TimedJSONWebSignatureSerializer as TJWSSerializer, BadData
from django.conf import settings
from oauth import constents
class OauthWeChat(object):
"""微信登录工具"""
def get_code_url(self):
"""
获取微信登录的url
返回说明:redirect_uri?code=CODE&state=STATE;
请求成功返回code和state,否则只有state
"""
redirect_uri = 'http://www.xxxxx.com/mall/perfect'
url = 'https://open.weixin.qq.com/connect/qrconnect?'
params = {
'appid' : settings.WE_CHAT_APP_ID,
'redirect_uri' : redirect_uri,
'response_type' : 'code',
'scope' : 'snsapi_login',
'state' : self.generate_state(),
}
# resp = requests.get(url, params =params)
params = urlencode(params)
url = url + params
return url
def generate_state(self):
"""生成state"""
serializer = TJWSSerializer(settings.SECRET_KEY, expires_in=constents.SAVE_WECHAT_USER_TOKEN_EXPIRES)
num = random.randint(1, 999)
state = serializer.dumps({'num': num}).decode()
return state
至于请求url时,携带的参数微信文档上就有,我就不再说明了,只说一下这个参数:redirect_uri = ‘http://www.xxxx.com/mall/perfect’, 这个是什么哪,这个参数其实是前端的一个页面的地址,当用户扫码之后,用户授权登录,这时候微信就会重定向到这个页面,并且带上code和state,如’http://www.xxxx.com/mall/perfect?code=CODE&state=STATE,这时候,前端拿到code和state请求后端接口,后端拿到之后,验证state,防止csrf攻击,验证成功之后,拿到code,请求appid(其实access_token我们不需要)
2. 前端凭借code和state来访问后端,后端凭借code去向微信拿appid,拿到appid之后,去表里面查有这个appid没有,如果没有,说明用户有两种情况1.该用户注册了账号,但是这是第一次用微信登录2.该用户从来没有登录,还没有账号,不管是哪一种情况,我们直接将oppid加密返回给前端,这时候,页面redirect_uri = 'http://www.onefashionline.com/mall/perfect’就又有作用了,页面里面就让用户填写用户账号,填写之后,将oppid和用户信息一起请求后端接口,后端通过用户信息的手机号(区分用户)来查询用户注册过账号没有,注册过则将用户信息和oppid关联起来,如果没有,将用户注册并与oppid关联,第二次微信登录时,查询oppid,就能查到,这样直接办法token,状态保持即可
def check_state(state):
"""
检验保存用户数据的state
:param num: num
:return: num or None
"""
serializer = TJWSSerializer(settings.SECRET_KEY, constents.SAVE_WECHAT_USER_TOKEN_EXPIRES)
try:
data = serializer.loads(state)
except BadData:
return None
else:
return data.get('num')
def generate_bind_user_access_token(self, openid):
"""通过openid生成保存用户数据的token"""
# 通过itsdangerous生成凭据access_token
serializer = TJWSSerializer(settings.SECRET_KEY, expires_in=constents.SAVE_WECHAT_USER_TOKEN_EXPIRES)
# serializer.dumps(数据), 返回bytes类型
token = serializer.dumps({'openid': openid}).decode()
return token
@staticmethod
def check_save_user_token(token):
"""
检验保存用户数据的token
:param token: token
:return: openid or None
"""
serializer = TJWSSerializer(settings.SECRET_KEY, constents.SAVE_WECHAT_USER_TOKEN_EXPIRES)
try:
data = serializer.loads(token)
except BadData:
return None
else:
return data.get('openid')
class WeChatAuthUserView(CreateAPIView):
"""微信登录的用户"""
# 指定序列化器,参数校验和用户创建在序列化器中完成
serializer_class = OAuthWeChatUserSerializer
def get(self, request):
# 获取code
code = request.query_params.get('code')
state = request.query_params.get('state','')
# app微信登录需要携带参数type
type = request.query_params.get('type', None)
if type and type == 'app':
if not code:
return APIResponse.fail(message='登录失败,请重试')
# 获取openid access_token等参数
result = AppOauthWeChat().get_access_token(code)
if result:
openid = result.get('openid')
else:
return APIResponse.fail(message="微信服务器异常")
# 根据openID判断用户是否是第一次登陆
wechat_user = OAuthWeChatUser.objects.filter(openid=openid).first()
# 如果用户不存在就是第一次登陆,通过openid生成access_token返回,用来标记用户身份
if not wechat_user:
# 此处使用pc端的加密,因为校验在序列化器中校验,使用的是pc端的,要保持一致
token = OauthWeChat().generate_bind_user_access_token(openid)
data = {"access_token": token}
return Response({'code': 301, 'data': data, 'message': 'OK'})
# 如果不是,签发JWTtoken,保存用户状态
# 获取user对象
user = wechat_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)
# 更改登录时间
now_time = datetime.datetime.now()
User.objects.filter(id=user.id).update(last_login=now_time)
response = {
'token': token,
'user_id': user.id,
'role': user.role,
'username': user.username
}
return APIResponse.success(data=response, message='OK')
# pc端登录
oauth = OauthWeChat()
if not oauth.check_state(state):
return APIResponse.fail(message='错误的请求')
if not code:
return APIResponse(message="登录失败,请重试")
# 根据code获取openID, 可以在工具类中定义获取openID的方法
# access_token, openid = oauth.get_access_token(code)
result = oauth.get_access_token(code)
if result:
# access_token = result.get('access_token')
openid = result.get('openid')
else:
return APIResponse.fail(message="微信服务器异常")
# 根据openID判断用户是否是第一次登陆
wechat_user = OAuthWeChatUser.objects.filter(openid=openid).first()
# 如果用户不存在就是第一次登陆,通过openid生成access_token返回,用来标记用户身份
if not wechat_user:
token = oauth.generate_bind_user_access_token(openid)
# 添加用户信息
# return APIResponse.success(data={"access_token": token},message='OK')
data = {"access_token": token}
return Response({'code':301,'data':data,'message':'OK'})
# 如果不是,签发JWTtoken,保存用户状态
# 获取user对象
user = wechat_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)
# 更改登录时间
now_time = datetime.datetime.now()
User.objects.filter(id=user.id).update(last_login=now_time)
response = {
'token': token,
'user_id': user.id,
'role':user.role,
'username': user.username
}
return APIResponse.success(data=response,message='OK')
@transaction.atomic
def create(self, request, *args, **kwargs):
"""绑定微信用户"""
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
message = ','.join(
serializer.errors[key] for key in serializer.errors if isinstance(serializer.errors[key], str))
if not message:
message = ','.join(
serializer.errors[key][0] for key in serializer.errors if isinstance(serializer.errors[key], list))
return APIResponse.fail(message=message)
data = serializer.validated_data
mobile = data['mobile']
openid = data['openid']
user = User.objects.filter(mobile=mobile).first()
if not user:
username = '用户_' + mobile[-4:]
user = User.objects.create_user(username=username, mobile= mobile, password=data['password'],role=1)
share_url_token = user.generate_token()
UserInfo.objects.create(user=user, auth_state=0, share_url_token=share_url_token)
# 添加用户注册IP归属地
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0] # 这里是真实的ip
else:
ip = request.META.get('REMOTE_ADDR') # 这里获得代理ip
# 注册时根据ip来查询所在地(此时类型先写死为web)
ip_home(user.id, ip, 1)
else:
user.password = make_password(data['password'])
user.save()
OAuthWeChatUser.objects.create(user=user, openid = openid)
# 签发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)
# 更改登录时间
now_time = datetime.datetime.now()
User.objects.filter(id=user.id).update(last_login=now_time)
response = {
'token': token,
'user_id': user.id,
'role': user.role,
'username': user.username
}
return APIResponse.success(data=response, message='OK')
- 可以看到,这里面还有app登录,app登录和pc端登录不同的时,第一步的操作,就是请求code不需要后端操作,这里app前端会唤醒微信,直接拿到code,然后带上code直接就从第二步开始了
4.微信开放平台网址:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html