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


    项目场景:

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


    1.申请appId,appKey

    1.进入qq互联官网。创建应用
    在这里插入图片描述
    特别注意
    1.在填写网站回调域时,需要你线上真实能访问的。不然审核不通过。我的回调地址是前端路由地址
    2.如果你想本地调试,回调到你的线上地址。你可以在本地设置本地域名,做反向代理。我是用的是phpstudy,方向代理自己的线上域名。

    如果你还是不明白,可以留言

    2.代码演示

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

    代码演示

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

    1. 前端点击qq图标登录代码:
    <div @click="handleToLogin('qq')">
         gitee
    </div>
    
    const handleToLogin = (type:string)=>{
       window.location.href="http://localhost:3000/user/oauth/qq"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. http://localhost:3000/user/oauth/qq后端接口代码
    	@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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 回调地址前端代码
    <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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    1. requestQQLogin请求的后端代码
    // 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"
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    	//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},"");
        }
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    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}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!

    如果有不懂可以留言,我看到了应该会回复
    如有错误,请多多指教

  • 相关阅读:
    2023 Google 开发者大会
    虚拟机(三)VMware Workstation 桥接模式下无法上网
    centos7环境下安装jdk8
    第四章字符串_反转字符串里的单词
    Windows下Python安装FDFS-client错误问题
    【6~10章要点总结】
    【信号和槽】
    Java 第三阶段增强分析需求,代码实现能力【连接池】
    Eachers使用立即执行函数的好处
    【无标题】
  • 原文地址:https://blog.csdn.net/qq_36977923/article/details/133956266