Vue集成QQ登录[server-side模式][Oauth2 code模式]

818 阅读2分钟

申请开通QQ互联登录

  1. 文档地址: 【QQ登录】OAuth2.0开发文档.
  2. 申请应用 地址 注意事项:
    1. 在网站首页加入QQ登录的按钮(下面会说具体怎么加)
    2. 网站信息和备案信息一致

模式

本文使用的是 server-side模式 又称Web Server Flow; 适用于需要从web server访问的应用,例如Web/wap网站。

其授权验证流程示意图如下(图片来源:OAuth2.0协议草案V21的4.1节 ) OAuth_guide_V2_1.png

对于应用而言,需要进行两步:

  1. 获取Authorization Code;
  2. 通过Authorization Code获取Access Token

引入资源

在public.index.html中引入js

  <script type="text/javascript" src="http://qzonestyle.gtimg.cn/qzone/openapi/qc_loader.js" data-callback="true"
    data-appid="APPID" data-redirecturi="回调地址" charset="utf-8">
    </script>

其中data-callback="true"这个参数的作用是在在授权完成后会自动帮你关掉回调页面.问什么需要关掉页面.下面会说

添加按钮和函数

  1. 添加按钮
              <span>Server-side模式</span>
              <img
                src="../../assets/image/bt_blue_76X24.png"
                @click="doQQLogin()"
              >
    
  2. 添加函数 点击按钮携带相关参数跳转到QQ互联的授权页,授权完成后会拿到授权码回调到回调页面,
	 doQQLogin() {
      //获取QQ连接

      getqqLoginInfo().then((res) => {
        window.location.href =
          "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=" +
          res.data.qqAppId +
          "&redirect_uri=" +
          res.data.qqRedirectUri +
          "&state=" +
          res.data.state;
      });
    },

回调页面

这个页面是在页面QQ登录完成后由腾讯帮你调用的接口,会在接口后面拼接参数?code=XXXXXXXXXXXXXXXXXXX,,这个页面的作用是:在接收到code之后将code发送到后台,后台接收到之后,会去QQ互联换取token,注意一点就是这个code是一次性,用完即焚,获取到token之后根据token和AppId可以用户信息,这样就可以执行登录了,登录完成后跳转到首页.

<template>

</template>

<script>
import { qqCallback } from "@/api/index";

export default {
  name: "qqCallback",

 
  mounted() {
    var accessToken = this.$utils.getUrlKey("access_token");
    var code = this.$utils.getUrlKey("code");
    //client_side模式
    if (accessToken && typeof accessToken != "undefined") {
      console.log("client_side模式:" + accessToken);
      if (QC.Login.check()) {
        this.$message.info("qq成功登录");
        this.$store.dispatch("user/qqlogin", accessToken).then(() => {
          this.$router.push("/");
        });
      }
    }

    //server-side模式
    if (code && typeof code != "undefined") {
      console.log("server-side模式:" + code);
      this.$store.dispatch("user/qqCodelogin", code).then(() => {
        this.$router.push("/");
      });
    }
  },
};
</script>

<style>
</style>

后台接口

rest接口

     /**
     * desc: 前端获取到QQ互联回调的授权码时,将授权码传到后台,获取access_token,并验证token执行登录,返回当前系统的token.
     *
     * @return cn.com.zsw.gblog.vo.R
     * @author: shiwangZhou
     * @date: 2020-09-23 8:40
     * @param: code  QQ互联回调的授权码
     */
    @GetMapping("qq/token")
    public R getAccessTokenByCode(String code) {
        String accessTokenByCode = oauthClient.getAccessTokenByCode(qqAppId, appKey, code, qqRedirectUri);
        log.info("请求access_token返回结果:" + accessTokenByCode);
        GetAccessTokenDTO accessTokenDTO = UrlUtils.paramToDTO(accessTokenByCode, GetAccessTokenDTO.class);
        String token = callback(accessTokenDTO.getAccess_token());
        Assert.notBlank(token, "未能获取到token,登录失败");
        return R.SUCCESS.data(token);

    }
   private String callback(String accessToken) {
        String openIdStr = oauthClient.getOpenId(accessToken);
        log.info("请求openID返回结果:" + openIdStr);
        String substring = openIdStr.substring(10, openIdStr.length() - 4);
        GetOpenIdRes openIdRes = JSON.parseObject(substring, GetOpenIdRes.class);
        GetUsrInfoRes userInfo = oauthClient.getUserInfo(accessToken, openIdRes.getClient_id(), openIdRes.getOpen_id());
        ThirdPartUser thirdPartUser = thirdPartUserService.getOne(Wrappers.<ThirdPartUser>lambdaQuery().eq(ThirdPartUser::getOpenId,
                openIdRes.getOpen_id()));
        GbUser qwe = userService.getOne(Wrappers.<GbUser>lambdaQuery().eq(GbUser::getUsername, "qwe"));
        UserDetails userDetails = userDetailsService.loadUserByUsername(qwe.getUsername());

        if (thirdPartUser == null) {
            thirdPartUser = new ThirdPartUser();
            thirdPartUser.setAvatar(userInfo.getAvatar());
            thirdPartUser.setGender("男".equalsIgnoreCase(userInfo.getGender()) ? 1 : 0);
            thirdPartUser.setId(IdUtil.createSnowflake(1, 1).nextId());
            thirdPartUser.setNickname(userInfo.getNickname());
            thirdPartUser.setUserId(qwe.getId());
            thirdPartUser.setOpenId(openIdRes.getOpen_id());
            thirdPartUser.setUserInfo(JSON.toJSONString(userInfo));
            thirdPartUserService.save(thirdPartUser);
        } else {
            userDetails = userDetailsService.loadUserByUsername(qwe.getUsername());
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        String id = IdUtil.createSnowflake(1, 1).nextIdStr();
        redisCache.setCacheObject(SysConstants.REDIS_JWT_TOKEN_ID + id, JSON.toJSONString(userDetails), 2, TimeUnit.HOURS);
        return id;
    }

总结:

  1. code模式由后端调用,相对于简化模式更加安全
  2. 简化模式对接起来更加简单

代码地址:

前端gitee 后端gitee

本文由博客一文多发平台 OpenWrite 发布!