前后端分离项目登录实现

最近在开发博客网站登录过程中,涉及到了多个前端对应一个后端的前后端分离项目如何使用OAuth完成第三方授权登录的问题,特此总结一篇文章,详细记录了完整的开发过程思路分析和具体的代码实现,大家需要相同的业务场景时可参考使用,

一、OAuth概述

1. OAuth功能

前后端分离项目OAuth登录总结

举个例子,当你想要上班摸鱼逛知乎时,但是你从来没有注册过知乎账号,而且你又嫌麻烦不愿意注册知乎账号,此时就可以使用第三方社交账号登录,例如使用QQ账号授权登录后,会自动登录知乎账号,并将自己的用户名、性别、头像等基本信息就会保存在知乎平台做账号绑定。

2. 什么是OAuth

首先说明一点,OAuth 不是一个API或者公共服务,而是一个验证授权的开放标准,只要授权方和被授权方遵守这个协议去写代码提供服务,那双方就是实现了OAuth模式。目前可以提供OAuth的平台有很多,他们都遵从这个标准,实现了自己的OAuth功能。虽然OAuth指定了明确的标准,但是各家的使用方式还是略有差异。

OAuth主要有OAuth 1.0、OAuth 1.0a、OAuth 2.0三个版本。OAuth 1.0a主要是修复了 OAuth 1.0的安全 问题,OAuth 2 是为了解决 OAuth 1.0a 过于复杂的问题。OAuth2.0 是目前广泛使用的版本,目前第三方平台也都是基于OAuth2标准开放服务。

3. OAuth流程

前后端分离项目OAuth登录总结

上述例子中的知乎就是客户端,QQ就是认证服务器,OAuth2.0就是客户端和认证服务器之间为了解决相互不信任而产生的一个授权协议。(要是相互信任那豆瓣直接读取QQ的数据库登录不就好了,搞这么费劲作甚)

整个流程分为以下三个阶段

① 用户点击QQ登录进入授权页面同意授权,登录完成后获取到code;

② 知乎网站请求QQ服务器,通过code换取授权access_token;

③ QQ通过网页授权access_token向知乎返回用户的基本信息。

二、项目开发思路

1. 整体流程思路

上面举例仅是简单的业务场景,前后端不分离项目开发思路。但是遇到多个前端对应一个后端的前后端分离项目,开发的流程思路还是略有差异。

完整的设计思路如下:

  • 首先在各个第三方登录平台创建应用,PC端和手机端各一个,是两个不同的key和secret。
  • 当用户点击第三方按钮登录时,前端传客户端类型和第三方平台给后端API接口,获取到应用的client id(也就是key)
  • 前端依据各个平台的请求地址格式,拼接成完整的url地址,其中包含前端的回调地址,并调转到第三方登录页
  • 用户登录完成后,会跳转到请求地址中指定的redirect_uri前端回调地址
  • 前端回调页获取到code参数后请求传参给后端。
  • 后端通过code参数请求第三方平台,换取token,然后使用token获取用户详细信息。
  • 后期根据openid判断用户是否已注册过(已注册——>直接登录;未注册——>获取用户信息并创建用户然后登录)返回给前端用户id和token。
  • 前端存储用户信息,并跳转到首页。至此,整个流程完成

2. 前端模块设计

前端的工作主要有两部分,分别是登录页和回调页

登录页放置第三方登录按钮,当用户点击登录后,后后端API接口传入登录平台、应用类型(桌面端还是移动端)两个参数,获取到client id,然后根据不同的第三方登录平台要求拼接URL地址,跳转到第三方登录页

回调页功能是当用户完成登录授权后,会跳转到回调页,从URL中获取到code参数,传递给后端。等待后端完成登录处理后,获取到用户id和token,并保存到local storage或者session storage中

3. 后端模块设计

后端的工作主要有两部分,分别是查询应用client id和完成用户登录

查询应用client id为一个接口,用于登录页请求。根据前端传入的登录平台、应用类型两个参数,返回应用的client id

用户登录为另一个API接口,用户回调页请求。用户传入code后,请求第三方平台OAuth接口,获取用户openid。然后判断当前用户是否已注册过账号(已注册——>直接登录;未注册——>获取用户信息并创建用户然后登录)并返回给前端用户id和token

三、应用创建与注意事项

1. 新浪微博

  • 相关地址

微博申请地址:https://open.weibo.com/

微博登录文档:https://open.weibo.com/wiki/Connect/login

  • 注意事项

新浪微博需要实名认证,只认证身份信息,不验证回调地址等信息是否正确。等审核通过再修改回调地址。在审核期间修改回调地址不生效。

2. 支付宝

  • 相关地址

支付宝申请地址https://open.alipay.com/

支付宝官方文档:https://opendocs.alipay.com/support/01rg6a

  • 注意事项

支付宝同样也需要实名认证,但也是只验证身份信息。但调用支付宝OAuth时需要使用支付宝sdk完成。

调用sdk建立连接是需要传入KEY、PRIVATE_KEY、PUBLIC_KEY三个参数,其中PUBLIC_KEY从应用信息——>接口加签方式中查看,PRIVATE_KEY是创建应用时上传的证书key

前后端分离项目OAuth登录总结

3. QQ

  • 相关地址

qq申请地址https://connect.qq.com/

qq文档:https://wiki.connect.qq.com/oauth2-0%e5%bc%80%e5%8f%91%e6%96%87%e6%a1%a3

  • 注意事项

QQ审核除了实名认证外,还审核应用的信息,记得应用名称填写备案号上的应用名称,并且要在应用中添加QQ登录按钮和跳转链接才能审核通过,审核通过后可以修改回调地址。

4. 百度

  • 相关地址

百度申请地址:http://developer.baidu.com/console#app/project

百度文档:https://openauth.baidu.com/doc/regdevelopers.html

5. github

  • 相关地址

github申请地址:https://github.com/settings/developers

github官方文档:https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps

6. 微软

  • 相关地址

微软申请地址:https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade

微软参考文档:https://docs.microsoft.com/zh-cn/graph/auth-v2-user

  • 注意事项

有个tenant参数。根据自己的账户权限,传入具体的值(文档示例和网上的资料都是common,但不一定适合自己的账户权限)

前后端分离项目OAuth登录总结前后端分离项目OAuth登录总结

四、后端代码

1. 项目配置(settings.py)

将第三方平台创建的应用key和secret保存到setting配置中,后续函数处理时直接调用setting配置即可。

2. 模型(models.py)

模型这儿设计了三张表,分别是用户来源、用户信息、用户第三方登录用户ID。

用户来源表主要记录用户是第三方登录还是直接注册的,并提前在用户来源表中输入相关记录。与用户信息表source字段一对多关联。

用户信息表通过使用AbstractUser可以对Django内置的User进行扩展使用,添加一些用户自定义的属性字段。

第三方登录用户ID用于记录用户ID与第三方平台对应关系,判断用户是否已注册绑定过账号。

from django.contrib.auth.models import User, AbstractUserfrom django.db import modelsfrom django.conf import settingsclass UserSource(models.Model): name = models.CharField('来源', max_length=100) class Meta: verbose_name = '注册用户来源' verbose_name_plural = verbose_name def __str__(self): return self.nameclass UserInfo(AbstractUser): source = models.ForeignKey(UserSource, verbose_name='用户来源', on_delete=models.CASCADE, default=1) phOne= models.CharField(verbose_name='手机号', max_length=20, blank=True, null=True) sex_choice = [('1', '男'), ('2', '女')] sex = models.CharField(verbose_name='性别', max_length=1, choices=sex_choice, default=1) web = models.URLField(verbose_name='个人网站', blank=True, null=True) signature = models.TextField(verbose_name='个性签名', max_length=200, default="这个人很懒,什么都没留下!") photo = models.URLField(verbose_name='头像', default='https://oss.cuiliangblog.cn/images/photo.jpg') area_code = models.CharField(verbose_name='地区编号', max_length=10, blank=True, null=True) area_name = models.CharField(verbose_name='地区名称', max_length=20, blank=True, null=True) birthday = models.DateField(verbose_name='生日', blank=True, null=True) is_flow = models.BooleanField('开启更新订阅', default=0) search = models.ManyToManyField(SearchKey, verbose_name='搜索记录') class Meta: verbose_name = '用户详细信息' verbose_name_plural = verbose_name def __str__(self): return self.usernameclass OAuthId(models.Model): user = models.ForeignKey(UserInfo, verbose_name='用户ID', on_delete=models.CASCADE, default=1) source = models.ForeignKey(UserSource, verbose_name='用户来源', on_delete=models.CASCADE, default=1) openid = models.CharField(max_length=100, verbose_name='用户OAuthID') class Meta: verbose_name = '第三方登录用户ID' verbose_name_plural = verbose_name def __str__(self): return self.user.username

前后端分离项目OAuth登录总结

2. 路由(urls.py)

后端向前端提供两个API接口,一个是查询应用client id,另一个是处理登录完成后回调相关业务逻辑

from django.urls import pathfrom rest_framework import routersfrom account import viewsapp_name = "account"urlpatterns = [ path('OAuthID/', views.OAuthIDAPIView.as_view()), # 获取第三方客户端ID path('OAuthCallback/', views.OAuthCallbackAPIView.as_view()), # 第三方登录后回调]router = routers.DefaultRouter()urlpatterns += router.urls

前后端分离项目OAuth登录总结前后端分离项目OAuth登录总结

3. 视图(views.py)

import jsonimport urllib.parsefrom loguru import loggerfrom rest_framework import status, viewsetsfrom rest_framework.response import Responsefrom rest_framework.views import APIViewfrom public.tools import OAuthfrom django.conf import settingsclass OAuthIDAPIView(APIView): """ 获取第三方登录应用ID """ @staticmethod def get(request): platform = request.query_params.get('platform') kind = request.query_params.get('kind') result = {'clientId': settings.AUTH[platform.upper()][kind.upper()]['KEY']} return Response(result, status=status.HTTP_200_OK)class OAuthCallbackAPIView(APIView): """ 授权第三方登录后回调地址 """ @staticmethod def post(request): platform = request.data.get('platform') kind = request.data.get('kind') code = request.data.get('code') redirect_uri = request.data.get('redirect_uri') print(platform, code, redirect_uri, kind) auth = OAuth(platform, kind, code, redirect_uri) result = {} if platform == 'WEIBO': result = auth.weiboLogin() elif platform == 'QQ': result = auth.qqLogin() elif platform == 'PAY': result = auth.payLogin() elif platform == 'GITHUB': result = auth.githubLogin() elif platform == 'BAIDU': result = auth.baiduLogin() elif platform == 'MICROSOFT': result = auth.microsoftLogin() return Response(result, status=status.HTTP_200_OK)

4. OAuth类(tools.py)

import randomimport datetimeimport uuidfrom urllib.parse import urlencodefrom loguru import loggerimport requestsfrom alipay.aop.api.request.AlipaySystemOauthTokenRequest import AlipaySystemOauthTokenRequestfrom alipay.aop.api.request.AlipayUserInfoShareRequest import AlipayUserInfoShareRequestfrom alipay.aop.api.constant.ParamConstants import *from alipay.aop.api.response.AlipaySystemOauthTokenResponse import AlipaySystemOauthTokenResponsefrom alipay.aop.api.response.AlipayUserInfoShareResponse import AlipayUserInfoShareResponsefrom django.core.cache import cachefrom django.core.mail import EmailMultiAlternativesfrom django.conf import settingsfrom django.utils import timezoneimport jsonfrom account.models import UserInfo, UserSource, OAuthIdfrom rest_framework_simplejwt.tokens import RefreshTokenfrom alipay.aop.api.AlipayClientConfig import AlipayClientConfigfrom alipay.aop.api.DefaultAlipayClient import DefaultAlipayClientimport tracebackclass OAuth: """ 第三方登录 """ def __init__(self, platform, kind, code, redirect_uri): print(platform, kind, code, redirect_uri) self._client_key = settings.AUTH[platform][kind]['KEY'] # 应用id self._client_secret = settings.AUTH[platform][kind]['SECRET'] # 应用key self._code = code # 用户code self._redirect_uri = redirect_uri # 登录回调地址 self.openid = '' # 用户第三方登录ID self.source_id = '' # 用户来源id self.user_id = '' # 用户id self.platform = platform # 第三方登录平台 self.kind = kind # 前端类型(PC或M) def __checkUserRegister(self): """ 检查用户是否已注册 :return: """ print('开始检测用户是否已注册过') user = OAuthId.objects.filter(source_id=self.source_id, openid=self.openid) if user.count() != 0: self.user_id = user.first().user.id return True else: return False def __createUser(self, username, **kwargs): """ 创建新用户 :param username: 用户名 :param kwargs: 用户信息 :return: None """ print("开始创建新用户啦") while UserInfo.objects.filter(username=username): # 防止用户名重复 username = username + str(random.randrange(10)) userinfo = { 'source_id': self.source_id, 'username': username, 'password': str(uuid.uuid1()) } for key, value in kwargs.items(): userinfo[key] = value logger.info('存储信息:{}'.format(userinfo)) print(userinfo) # 用户信息表插入数据 new_user = UserInfo.objects.create_user(**userinfo) self.user_id = new_user.id # OAuthId表插入数据 OAuthId.objects.create(user_id=self.user_id, source_id=self.source_id, openid=self.openid) def __userLogin(self): """ 用户登录签发token :return: """ print("开始登录了") user = UserInfo.objects.get(id=self.user_id) user.last_login = timezone.now() user.save() refresh = RefreshToken.for_user(user) result = dict() result['token'] = str(refresh.access_token) result['userid'] = user.id result['username'] = user.username return result def weiboLogin(self): """ 微博登录 :return: """ print("微博登录了") self.source_id = UserSource.objects.get(name='微博').id # 获取用户access_token和uid access_token_url = 'https://api.weibo.com/oauth2/access_token?client_id={0}&client_secret={1}&grant_type=authorization_code&code={2}&redirect_uri={3}'.format( self._client_key, self._client_secret, self._code, self._redirect_uri) access_respOnse= requests.post(access_token_url).json() print(access_response['access_token'], access_response['uid']) self.openid = access_response['uid'] # 判断用户是否已注册过 user = self.__checkUserRegister() if user: print('已注册过,直接登录') return self.__userLogin() else: # 获取用户信息 userinfo_url = "https://api.weibo.com/2/users/show.json?access_token={0}&uid={1}".format( access_response['access_token'], access_response['uid']) userinfo_respOnse= requests.get(userinfo_url).json() print(userinfo_response) logger.info('微博用户信息:{}'.format(userinfo_response)) username = userinfo_response['name'] signature = userinfo_response['description'] photo = userinfo_response['avatar_large'] web = userinfo_response['url'] area_name = userinfo_response['location'] if userinfo_response['gender'] == 'f': sex = 2 else: sex = 1 # 新建用户 self.__createUser(username, signature=signature, photo=photo, web=web, area_name=area_name, sex=sex) # 用户登录 return self.__userLogin() def qqLogin(self): """ QQ登录 """ print("QQ登录了") self.source_id = UserSource.objects.get(name='qq').id # 获取用户access_token access_token_url = 'https://graph.qq.com/oauth2.0/token?client_id={0}&client_secret={1}&grant_type=authorization_code&code={2}&redirect_uri={3}&fmt=json'.format( self._client_key, self._client_secret, self._code, self._redirect_uri) access_respOnse= requests.get(access_token_url).json() print(access_response['access_token']) # 使用Access Token获取用户的OpenID openID_url = 'https://graph.qq.com/oauth2.0/me?access_token={}&fmt=json'.format(access_response['access_token']) openID_respOnse= requests.get(openID_url).json() print("openID:", openID_response['openid']) self.openid = openID_response['openid'] # 判断用户是否已注册过 user = self.__checkUserRegister() if user: print('已注册过,直接登录') return self.__userLogin() else: # 获取用户信息 print('开始获取用户信息') userinfo_url = "https://graph.qq.com/user/get_user_info?access_token={0}&oauth_consumer_key={1}&openid={2}&fmt=json".format( access_response['access_token'], self._client_key, self.openid) userinfo_respOnse= requests.get(userinfo_url).json() logger.info('QQ用户信息:{}'.format(userinfo_response)) print(userinfo_response) username = userinfo_response['nickname'] photo = userinfo_response['figureurl_2'] area_name = userinfo_response['province'] + ' ' + userinfo_response['city'] if userinfo_response['gender'] == '女': sex = 2 else: sex = 1 # 新建用户 self.__createUser(username, photo=photo, area_name=area_name, sex=sex) # 用户登录 return self.__userLogin() def baiduLogin(self): """ 百度账号登录 """ print("百度登录了") self.source_id = UserSource.objects.get(name='百度').id print(self.source_id) # 获取用户access_token access_token_url = 'https://openapi.baidu.com/oauth/2.0/token?grant_type=authorization_code&client_id={0}&client_secret={1}&code={2}&redirect_uri={3}'.format( self._client_key, self._client_secret, self._code, self._redirect_uri) access_respOnse= requests.get(access_token_url).json() print(access_response['access_token']) # 使用Access Token获取用户信息 userinfo_url = "https://openapi.baidu.com/rest/2.0/passport/users/getInfo?access_token={}".format( access_response['access_token']) userinfo_respOnse= requests.get(userinfo_url).json() logger.info('百度用户信息:{}'.format(userinfo_response)) print(userinfo_response) self.openid = userinfo_response['portrait'] # 判断用户是否已注册过 user = self.__checkUserRegister() if user: print('已注册过,直接登录') return self.__userLogin() else: # 获取用户信息 username = userinfo_response['username'] photo = 'https://himg.bdimg.com/sys/portrait/item/' + userinfo_response['portrait'] # 新建用户 self.__createUser(username, photo=photo) # 用户登录 return self.__userLogin() def microsoftLogin(self): """ 微软账号登录 """ print("微软账号登录了") self.source_id = UserSource.objects.get(name='微软').id print(self.source_id) # 获取用户access_token access_token_headers = { 'Content-Type': 'application/x-www-form-urlencoded' } access_token_data = { "client_id": self._client_key, "client_secret": self._client_secret, "code": self._code, "redirect_uri": self._redirect_uri, "grant_type": 'authorization_code', "scope": 'offline_access user.read' } access_token_url = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token' access_respOnse= requests.post(access_token_url, headers=access_token_headers, data=urlencode(access_token_data)).json() print(access_response['access_token']) # 使用Access Token获取用户信息 userinfo_headers = { "Authorization": 'Bearer ' + access_response['access_token'], "Host": 'graph.microsoft.com' } userinfo_url = "https://graph.microsoft.com/v1.0/me" userinfo_respOnse= requests.get(userinfo_url, headers=userinfo_headers).json() logger.info('微软用户信息:{}'.format(userinfo_response)) print(userinfo_response) self.openid = userinfo_response['id'] # 判断用户是否已注册过 user = self.__checkUserRegister() if user: print('已注册过,直接登录') return self.__userLogin() else: # 获取用户信息 username = userinfo_response['displayName'].replace(" ", "") # 新建用户 self.__createUser(username) # 用户登录 return self.__userLogin() def __payToken(self, client): """ 支付宝通过code获取用户token :param client: :return: """ # 构造请求参数对象 request = AlipaySystemOauthTokenRequest() request.code = self._code request.grant_type = "authorization_code" response_cOntent= None # 执行API调用 try: response_cOntent= client.execute(request) except Exception as e: print(traceback.format_exc(), e) if not response_content: print("failed execute") else: # 解析响应结果 respOnse= AlipaySystemOauthTokenResponse() response.parse_response_content(response_content) if response.is_success(): # 如果业务成功,可以通过response属性获取需要的值 auth_token = response.access_token self.openid = response.user_id return auth_token # 响应失败的业务处理 else: # 如果业务失败,可以从错误码中可以得知错误情况,具体错误码信息可以查看接口文档 print(response.code + "," + response.msg + "," + response.sub_code + "," + response.sub_msg) def __payUserInfo(self, client, token): """ 获取支付宝用户信息 :return: """ request = AlipayUserInfoShareRequest() # 添加auth_token udf_params = dict() udf_params[P_AUTH_TOKEN] = token request.udf_params = udf_params response_cOntent= None # 执行API调用 try: # 执行接口请求 response_cOntent= client.execute(request) except Exception as e: print(traceback.format_exc(), e) if not response_content: print("failed execute") else: respOnse= AlipayUserInfoShareResponse() # 解析响应结果 response.parse_response_content(response_content) # 响应成功的业务处理 if response.is_success(): # 如果业务成功,可以通过response属性获取需要的值 # print(response) logger.info('支付宝用户信息:{}'.format(response)) username = response.nick_name photo = response.avatar area_name = response.province + ' ' + response.city if response.gender == 'f': sex = 2 else: sex = 1 # 新建用户 self.__createUser(username, photo=photo, area_name=area_name, sex=sex) # 响应失败的业务处理 else: # 如果业务失败,可以从错误码中可以得知错误情况,具体错误码信息可以查看接口文档 print(response.code + "," + response.msg + "," + response.sub_code + "," + response.sub_msg) def payLogin(self): """ 支付宝登录 """ print("支付宝登录了") self.source_id = UserSource.objects.get(name='支付宝').id # 实例化客户端 alipay_client_cOnfig= AlipayClientConfig() alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do' alipay_client_config.app_id = self._client_key alipay_client_config.app_private_key = settings.AUTH[self.platform][self.kind]['PRIVATE_KEY'] alipay_client_config.alipay_public_key = settings.AUTH[self.platform][self.kind]['PUBLIC_KEY'] client = DefaultAlipayClient(alipay_client_config) # 获取用户token token = self.__payToken(client) # 判断用户是否已注册过 user = self.__checkUserRegister() if user: print('已注册过,直接登录') return self.__userLogin() else: # 获取用户信息 print('开始获取用户信息') self.__payUserInfo(client, token) return self.__userLogin() def __githubToken(self): """ github获取用户token :return: """ respOnse= None headers = { 'accept': 'application/json' } url = 'https://github.com/login/oauth/access_token?client_id={0}&client_secret={1}&code={2}'.format( self._client_key, self._client_secret, self._code) i = 0 while i < 3: try: print("开始尝试获取token", timezone.localtime()) respOnse= requests.post(url, headers=headers, timeout=5).json() if response: return response except requests.exceptions.RequestException: i += 1 if response is None: print("获取token请求失败了") return False def __githubUserInfo(self, token): """ github获取用户信息 :return: """ respOnse= None headers = { 'accept': 'application/json', 'Authorization': 'token ' + token } url = 'https://api.github.com/user' i = 0 while i < 3: try: print("开始尝试获取用户信息", timezone.localtime()) respOnse= requests.get(url, headers=headers, timeout=5).json() if response: print(response) logger.info('github用户信息:{}'.format(response)) return response except requests.exceptions.RequestException: i += 1 if response is None: print("获取用户信息失败了") return False def githubLogin(self): """ github登录 :return: """ print("github登录了") self.source_id = UserSource.objects.get(name='github').id # 获取用户access_token access_respOnse= self.__githubToken() if access_response: print(access_response['access_token']) # 获取用户信息 userinfo_respOnse= self.__githubUserInfo(access_response['access_token']) if userinfo_response: print(userinfo_response) self.openid = userinfo_response['id'] # 判断用户是否已注册过 user = self.__checkUserRegister() if user: print('已注册过,直接登录') return self.__userLogin() else: if userinfo_response['name']: username = userinfo_response['name'] else: username = userinfo_response['login'] signature = userinfo_response['bio'] photo = userinfo_response['avatar_url'] if userinfo_response['blog']: web = userinfo_response['blog'] else: web = userinfo_response['html_url'] area_name = userinfo_response['location'] # 新建用户 self.__createUser(username, signature=signature, photo=photo, web=web, area_name=area_name) # 用户登录 return self.__userLogin() else: return False

五、前端代码

1. API地址封装

// 获取第三方登录IDexport function getOAuthID(platform) { return index.get('/account/OAuthID/' + '?platform=' + platform + '&kind=PC')}// 第三方授权登录后回调export function postOAuthCallback(params) { return index.post('/account/OAuthCallback/', params)}

2. PC端用户登录页

<template><div class="other-login"> <el-divider> <span>第三方账号登录</span> </el-divider> <div class="other-logo"> <span @click="otherLogin('QQ')" class="pointer"><MyIcon type="icon-qq-logo"/></span> <span @click="otherLogin('PAY')" class="pointer"><MyIcon type="icon-alipay-logo"/></span> <span @click="otherLogin('BAIDU')" class="pointer"><MyIcon type="icon-baidu-logo"/></span> <span @click="otherLogin('WEIBO')" class="pointer"><MyIcon type="icon-weibo-logo"/></span> <span @click="otherLogin('GITHUB')" class="pointer"><MyIcon type="icon-github-logo"/></span> <span @click="otherLogin('MICROSOFT')" class="pointer"><MyIcon type="icon-microsoft-logo"/></span> </div></template><script setup>import icon from '@/utils/icon'import {onBeforeMount, onMounted, reactive, ref} from "vue";import {getBgiUrl} from "@/api/public";import {useRouter} from "vue-router";import VerifyImgBtn from "@/components/verify/VerifyImgBtn.vue";import VerifyCodeBtn from "@/components/verify/VerifyCodeBtn.vue"import {ElMessage} from 'element-plus'import {getOAuthID, getRegister, postCode, postLogin, postRegister} from "@/api/account";import store from "@/store";import {getSiteConfig} from "@/api/management";const router = useRouter();let {MyIcon} = icon()// 第三方登录const otherLogin = (kind) => {ElMessage('正在跳转至第三方平台,请稍候……')console.log(kind)let domain = window.location.protocol + "//" + window.location.hostif (kind === 'WEIBO') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://api.weibo.com/oauth2/authorize?client_id=' + response.clientId + '&response_type=code&redirect_uri=' + domain + '/OAuth/' + kind console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) ElMessage.error('获取第三方登录ID失败!') });}if (kind === 'QQ') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://graph.qq.com/oauth2.0/authorize?client_id=' + response.clientId + '&response_type=code&redirect_uri=' + domain + '/OAuth/' + kind + '&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) ElMessage.error('获取第三方登录ID失败!') });}if (kind === 'PAY') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=' + response.clientId + '&scope=auth_user&redirect_uri=' + encodeURIComponent(domain + '/OAuth/' + kind) + '&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) ElMessage.error('获取第三方登录ID失败!') });}if (kind === 'GITHUB') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://github.com/login/oauth/authorize?client_id=' + response.clientId + '&scope=user&redirect_uri=' + domain + '/OAuth/' + kind + '&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) ElMessage.error('获取第三方登录ID失败!') });}if (kind === 'BAIDU') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://openapi.baidu.com/oauth/2.0/authorize?client_id=' + response.clientId + '&redirect_uri=' + domain + '/OAuth/' + kind + '&response_type=code&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) ElMessage.error('获取第三方登录ID失败!') });}if (kind === 'MICROSOFT') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=' + response.clientId + '&response_type=code&redirect_uri=' + domain + '/OAuth/' + kind + '&response_mode=query&scope=offline_access user.read&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) ElMessage.error('获取第三方登录ID失败!') });}</script><style scoped lang="scss"></style>

3. PC端授权回调页

<template> <Loading :type="'tips'" :text="'正在调用'+platform_name+'平台登录,请稍候……'"/></template><script setup>import {useRouter} from "vue-router";import {ElMessage} from 'element-plus'import {onMounted, reactive, ref} from "vue";import {postOAuthCallback} from "@/api/account";import Loading from "@/components/common/Loading.vue"import store from "@/store";const router = useRouter()// 平台名称const platform_name = ref('')// 回调登录表单const OAuthForm = reactive({ platform: '', kind: 'PC', code: '', redirect_uri: ''})// 向后端发送登录回调请求const postCallback = () => { postOAuthCallback(OAuthForm).then((response) => { console.log(response) ElMessage({ message: '登录成功!', type: 'success', }) store.commit('setKeepLogin', false) store.commit('setUserSession', response) console.log(store.state.nextPath) router.push(store.state.nextPath) }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) ElMessage.error('自动登录异常,请更换其他登录方式!') router.push('/loginRegister') });}ref('')onMounted(() => { OAuthForm.platform = router.currentRoute.value.params.platform OAuthForm.redirect_uri = window.location.protocol + "//" + window.location.host + router.currentRoute.value.path if (OAuthForm.platform === 'PAY') { OAuthForm.code = router.currentRoute.value.query.auth_code } else { OAuthForm.code = router.currentRoute.value.query.code } console.log(OAuthForm) switch (OAuthForm.platform) { case 'WEIBO': platform_name.value = '新浪微博' break; case 'QQ': platform_name.value = '腾讯QQ' break; case 'PAY': platform_name.value = '支付宝' break; case 'GITHUB': platform_name.value = 'GitHub' break; case 'BAIDU': platform_name.value = '百度' break; case 'MICROSOFT': platform_name.value = '微软' break; default: platform_name.value = '第三方' } postCallback()})</script><style scoped></style>

4. 手机端用户登录页

业务逻辑与手机端一致,只是授权页跳转时URL参数手机端和PC端略有差异

<template> <div class="other"> <van-divider>第三方账号登录</van-divider> <div class="other-logo"> <span @click="otherLogin('QQ')"> <MyIcon class="logo-icon" type="icon-qq-logo"/> <p>QQ</p> </span> <span @click="otherLogin('PAY')"> <MyIcon class="logo-icon" type="icon-alipay-logo"/> <p>支付宝</p> </span> <span @click="otherLogin('BAIDU')"> <MyIcon class="logo-icon" type="icon-baidu-logo"/> <p>百度</p> </span> <span @click="otherLogin('WEIBO')"> <MyIcon class="logo-icon" type="icon-weibo-logo"/> <p>微博</p> </span> <span @click="otherLogin('GITHUB')"> <MyIcon class="logo-icon" type="icon-github-logo"/> <p>GitHub</p> </span> <span @click="otherLogin('MICROSOFT')"> <MyIcon class="logo-icon" type="icon-microsoft-logo"/> <p>微软</p> </span> </div> </div></template><script setup>import {Form, Button, Field, Divider, Icon, Checkbox, Toast} from 'vant';import VerifyImgBtn from "@/components/verify/VerifyImgBtn.vue";import {reactive, ref} from "vue";import {getOAuthID, postLogin} from '@/api/account'import store from "@/store/index";import {useRouter} from "vue-router";import icon from '@/utils/icon'let {MyIcon} = icon()const router = useRouter()// 第三方登录const otherLogin = (kind) => { Toast('正在跳转至第三方平台,请稍候……') console.log(kind) let domain = window.location.protocol + "//" + window.location.host if (kind === 'WEIBO') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://open.weibo.cn/oauth2/authorize?client_id=' + response.clientId + '&response_type=code&redirect_uri=' + domain + '/OAuth/' + kind + '&display=mobile' console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) Toast.fail('获取第三方登录ID失败!') }); } if (kind === 'QQ') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://graph.qq.com/oauth2.0/authorize?client_id=' + response.clientId + '&response_type=code&redirect_uri=' + domain + '/OAuth/' + kind + '&display=mobile' + '&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) Toast.fail('获取第三方登录ID失败!') }); } if (kind === 'PAY') { getOAuthID(kind).then((response) => { console.log(response) let parameter = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=' + response.clientId + '&scope=auth_user&redirect_uri=' + domain + '/OAuth/' + kind + '&state=' + Math.random().toString(36).slice(-6) let url = 'alipays://platformapi/startapp?appId=20000067&url=' + encodeURIComponent(parameter) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) Toast.fail('获取第三方登录ID失败!') }); } if (kind === 'GITHUB') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://github.com/login/oauth/authorize?client_id=' + response.clientId + '&scope=user&redirect_uri=' + domain + '/OAuth/' + kind + '&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) Toast.fail('获取第三方登录ID失败!') }); } if (kind === 'BAIDU') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://openapi.baidu.com/oauth/2.0/authorize?client_id=' + response.clientId + '&redirect_uri=' + domain + '/OAuth/' + kind + '&response_type=code&display=mobile&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) Toast.fail('获取第三方登录ID失败!') }); } if (kind === 'MICROSOFT') { getOAuthID(kind).then((response) => { console.log(response) let url = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=' + response.clientId + '&response_type=code&redirect_uri=' + domain + '/OAuth/' + kind + '&response_mode=query&scope=offline_access user.read&state=' + Math.random().toString(36).slice(-6) console.log(url) window.location.href = url; }).catch(respOnse=> { //发生错误时执行的代码 console.log(response) Toast.fail('获取第三方登录ID失败!') }); }}</script><style lang="scss" scoped>@import "src/assets/style/index";</style>

五、效果演示

1. 手机端登录

  • 访问地址:https://m.cuiliangblog.cn/loginRegister
  • 登录页

前后端分离项目OAuth登录总结

  • 授权回调页

前后端分离项目OAuth登录总结

  • 个人中心页

前后端分离项目OAuth登录总结

2. 电脑端登录

  • 访问地址:https://www.cuiliangblog.cn/loginRegister
  • 用户登录页

前后端分离项目OAuth登录总结

  • 授权回调页

前后端分离项目OAuth登录总结

  • 首页

前后端分离项目OAuth登录总结

3. admin后台

前后端分离项目OAuth登录总结

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:dandanxi6@qq.com

(0)
上一篇 2022年 11月 28日 下午3:35
下一篇 2022年 11月 28日 下午3:41

相关推荐

  • diy电脑电源什么牌子好

    一入DIY深似海,从此妹子是路人。选CPU选显卡都可以借助性能来选,但是选电源你了解多少呢?电源的额定功率,电源的输出线材,电源的做工用料都会影响性能,但是玩家往往对电源没有研究,…

    2023年 9月 10日
  • referto,refer to多重含义用法

    be referred to as… 被称为… =be known to sb as … refer A to B = know A as B …

    综合百科 2022年 12月 16日
  • 了解设计印刷形式的意义(设计印刷的种类与流程)

    根据技术原理的不同,我们可以把印刷工艺分成许多种类,最常见和最主要的有凸版印刷、平版印刷、凹版印刷、孔版印刷四类。凸版印刷发明最早的一种印刷技术,其原理是印刷版面上印纹突出,非印纹…

    2023年 3月 6日
  • 新白娘子传奇大结局

    [海峡网] 法海一人不敌魔化的素贞,将真相告知。可看遍天下人的残酷的素贞不愿许仙舍身为名,如若没有了许仙,这天下要大义有何用,自己要仙途有何用。素贞越杀越恨,人挡杀人,佛挡杀佛。这…

    2022年 12月 9日
  • 四川最好的二本大学及分数线

    成都号称“天府之国”,其位居新一线城市榜首,是不少西南地区学子日后就业首选。 编者介绍一下成都就业形势很好的3所二本高校。 常识:一所高校既有一本招生也有二本招生,我们通常认为是二…

    2022年 11月 5日
  • 强国梦对中国发展的影响

    “中国梦”到底是什么 当今,中国以一个大国的身份立于世界之林,中国梦究竟意味着什么,不仅我们每个中国人在深思,世界各国的人也在分析与揣测。从某种角度来说,西方国家对中国感到恐慌的一…

    2023年 8月 6日
  • 从雷锋三个名字说起

    我们知道,雷锋小时候叫庚伢子,上学后改名雷正兴,离开家乡赴鞍钢前改名雷锋。这三个名字,反应出三个不同的历史背景,展现出雷锋成长的轨迹。 庚伢子,是雷锋小名。 他1940年12月18…

    2022年 12月 22日
  • 仙剑奇侠传官方年表设定总结,仙剑奇侠传全程攻略

    最近的两条消息,让我这个前《仙剑奇侠传》玩家本来一如死灰的内心,多少起了些波澜。 一是《仙剑》电视剧传出了备案信息,可能是要翻拍了。 另一条消息就是《仙剑奇侠传7》推出了试玩版,时…

    2022年 12月 24日
  • 人生际遇,高光时刻VS至暗时刻,人生际遇的象限区间?| 睡前聊一会儿

    睡前聊一会儿,梦中有世界。听众朋友们,晚上好。意气风发的你,一定还记得小时候第一次获得小红花奖励时的激动,还记得翻山越岭后“一览众山小”的豪情,还记得班会上被老师表扬、年会上被同事…

    2022年 11月 5日
  • 星星不是发光体,星星不是发光体小说3

    深夜,她蹑手蹑脚地在黑暗中摸索着挪到阳台,亮着灯的人家屈指可数。她睡不着。上午发生的事历历在目。她最喜欢的一支笔丢了,后来在最要好的朋友的桌上找到了。她与朋友大吵一架后开始冷战。朋…

    2024年 1月 7日