【vue+nestjs】qq第三方授权登录【超详细】

项目场景:

前端使用vue3+ts 后端使用nestjs


1.申请appId,appKey

1.进入qq互联官网。创建应用

特别注意

在填写网站回调域时,需要你线上真实能访问的。不然审核不通过。我的回调地址是前端路由地址

2.代码演示

特别注意:

如果你跟我一样是前后端分离的模式开发的,应用回调地址填写的应该是你的前端路由地址。在你的前端页面获取code,把code值传给后端接口。后端接口通过code获取gitee用户信息。

代码演示

我的应用回调地址:http://localhost:8080/vuecms/qq

  1. 前端点击qq图标登录代码:
javascript 复制代码
<div @click="handleToLogin('qq')">
     gitee
</div>

const handleToLogin = (type:string)=>{
   window.location.href="http://localhost:3000/user/oauth/qq"
}
  1. http://localhost:3000/user/oauth/qq后端接口代码
javascript 复制代码
	@Get('/oauth/qq')
  async qqLogin(@Res() response: Response) {
    let appId = 你的appId;
    let redirectUrl = 你的回调地址;
    const state = Date.now()
    let scope = "get_user_info,list_album"
    return qqOauthConfig.authorizeUrl+`&client_id=${appId}&redirect_uri=${redirectUrl}&state=${state}&scope=${scope}`;
  }
  1. 回调地址前端代码
javascript 复制代码
<template>
    <div class="u-f u-f-ac u-f-ajc" style="width: 100%;height:100vh">
        <template v-if="isOauth">
            <el-result
                    icon="success"
                    title="授权成功,跳转中..."
            >
            </el-result>
        </template>
        <template v-else>
            <el-result
                    icon="error"
                    title="授权失败"
            >
            </el-result>
        </template>
    </div>
</template>

<script setup lang="ts">
    import {useRoute,useRouter} from "vue-router";
    import {onMounted} from "@vue/runtime-core";
    import {requestGiteeLogin} from "@/network/common/oauthPage";
    import {setToken, setUserId, setUsername} from "@/utils/storage";
    import {handleGetCurInstance} from "@/utils/utils";
    import {ref} from "vue"
    let route = useRoute()
    let router = useRouter()
    let query = route.query;
    let {model} = handleGetCurInstance()
    let isOauth = ref(true)
    onMounted(()=>{
    	//获取返回的code,通过code对后端发起请求,获取qq用户信息
         let {code,state} = query;
        let form = {
            code,state
        }
        requestQQLogin(form).then(res=>{
            let {data,code,message} = res;
            if(code==200){
                setToken(data.token)
                setUserId(data.id)
                setUsername(data.username)
                window.location.href="/"
            }else{
                model.handleMsg(message,"warning")
                isOauth.value =false;
            }
        })
    })
</script>
  1. requestQQLogin请求的后端代码
javascript 复制代码
// qq 的认证配置
export const qqOauthConfig = {
  cid: "",//gitee官网设置获取
  secret: "",
  redirectURL: '',//gitee官网配置进行填写
  authorizeUrl: 'https://graph.qq.com/oauth2.0/authorize?response_type=code',
  getAccessTokenUrl: 'https://graph.qq.com/oauth2.0/token?grant_type=authorization_code',
  openId:"https://graph.qq.com/oauth2.0/me",
  qqUserAPI:"https://graph.qq.com/user/get_user_info"
};
javascript 复制代码
	//qq登录
  @Post('/oauth/qqLogin')
  async getQQInfo(@Body() qqLoginDto:QqLoginDto,@IpAddress() clientIp: string) {
      let {code,operationSystem,browser} = giteeLoginDto
    let accessToken:any = await this.handleGetQQAccessToken(code)
    if(!accessToken.data){
      return this.msgService.fail("code过期,请重新登录")
    }
    let giteeInfo:any = await this.getQQInfoByAccessToken(accessToken.data.accessToken,accessToken.data.appId);
    if(!giteeInfo.data){
      return this.msgService.fail("获取qq账号信息失败")
    }
    let { nickname } = giteeInfo.data.userData;
    let clientId = 0;
    let qqId = sysConfigEnum.qqLoginConfig + JSON.parse(JSON.stringify(giteeInfo.data.openid));
    //判断qq是否有关联账号。如果有就登陆,没有就新创建一个账号
    let userNum = await this.userEntity.createQueryBuilder().where({ qqId:qqId }).getCount()
    let username;
    //没有账号,注册帐号
    if(userNum<=0){
      let roleData = await this.roleEntity.createQueryBuilder().where({roleName:"试用角色"}).getOne()
      username = handleGetCode(8);
      username = await this.handleGetUsername(username);
      let originalPwd = handleGetCode(8);
      let password = JSON.parse(JSON.stringify(originalPwd))
      password = securityMd5(password)
      let userData;
      try {
        userData = await this.userEntity.createQueryBuilder().insert().values({username,originalPwd,password,qqId:qqId,roleId:roleData.id}).execute();
      }catch (error) {
        throw new HttpException(error,HttpStatus.SERVICE_UNAVAILABLE)
      }
      clientId = userData.identifiers[0]["id"]
    }else{
      let userData = await this.userEntity.createQueryBuilder().where({qqId:qqId}).getOne()
      username = userData.username
      clientId = userData.id;
    }
    let ip  = handleDealIpv6ToIpv4(clientIp)
    let token = this.authService.createToken({id:clientId,username,ip})
    await this.updateUserInfoStatus(clientId,token,ip,operationSystem,browser)
    return {
      id:clientId,username,token
    }
  }
  
//获取gitee的accessToken
  async handleGetQQAccessToken(code:string):Promise<resInterface>{
    let key = sysConfigEnum.qqLoginConfig
    let data = await this.sysConfigService.handleGetSysData(key)
    if(!data.appId || !data.appKey || !data.redirectUrl){
      return {data:false,msg:""};
    }
    let appId = data.appId;
    let appKey = data.appKey;
    let redirectUrl = data.redirectUrl;//回调路劲获取code
    let authData = await axios.get(qqOauthConfig.getAccessTokenUrl+`&code=${code}&client_id=${appId}&client_secret=${appKey}&redirect_uri=${redirectUrl}`).then(res=>{
      let resArr = res.data.split("&")
      let accessToken = resArr[0].split("=")[1]
      let expiresIn = resArr[1].split("=")[1]
      let refreshToken = resArr[2].split("=")[1]
      return {accessToken,expiresIn,refreshToken};
    }).catch(err=>{
      return err.data
    })
    if(authData?.error){
      return this.msgService.commonRes(false,authData?.error?.error_description);
    }else{
      return this.msgService.commonRes({accessToken:authData?.accessToken,appId},"");
    }
  }
  //通过access_token获取gitee信息
  async getQQInfoByAccessToken(accessToken: boolean | string,appId:string){
    let authData = await axios.get(qqOauthConfig.openId+`?access_token=${accessToken}`).then(res=>{
      let data = JSON.parse(res.data.substring(9, res.data.length-3))
      let clientId = data.client_id;
      let openid = data.openid;
      return {clientId,openid};
    }).catch(err=>{
      return err.data
    })
    if(authData?.error){
      return this.msgService.commonRes(false,authData?.error?.error_description);
    }
    let userData = await axios.get(qqOauthConfig.qqUserAPI+`?access_token=${accessToken}&oauth_consumer_key=${appId}&openid=${authData.openid}`).then(res=>{
      return res.data;
    }).catch(err=>{
      return err.data
    })
    if(userData?.error){
      return this.msgService.commonRes(false,authData?.error?.error_description);
    }else{
      return this.msgService.commonRes({userData,openid:authData.openid},"");
    }
  }

3.特别注意

如果以上步骤都没问题。需要把本地测试回调地址改为线上路径

如果你还是不懂,你可以克隆下我的项目。开源免费。如果对你有帮助,给我一个star就行了
https://gitee.com/derekgo/vue-cms_xg

✨ 踩坑不易,还希望各位大佬支持一下 \textcolor{gray}{踩坑不易,还希望各位大佬支持一下} 踩坑不易,还希望各位大佬支持一下

📃 个人主页: \textcolor{green}{个人主页:} 个人主页: 沉默小管

📃 个人网站: \textcolor{green}{个人网站:} 个人网站: 沉默小管

📃 个人导航网站: \textcolor{green}{个人导航网站:} 个人导航网站: 沉默小管导航网

📃 我的开源项目: \textcolor{green}{我的开源项目:} 我的开源项目: vueCms.cn

🔥 技术交流 Q Q 群: 837051545 \textcolor{green}{技术交流QQ群:837051545} 技术交流QQ群:837051545

👍 点赞,你的认可是我创作的动力! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!

⭐️ 收藏,你的青睐是我努力的方向! \textcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!

✏️ 评论,你的意见是我进步的财富! \textcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!

如果有不懂可以留言,我看到了应该会回复

如有错误,请多多指教

相关推荐
慧一居士1 分钟前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead3 分钟前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_7 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
伍哥的传说7 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js