微信授权登陆

本文详细描述了如何通过微信官方的OAuth2流程实现网站的微信登录功能,包括用户扫码、获取code、通过code换取access_token和openid,以及使用JWT存储和验证用户信息的过程。
摘要由CSDN通过智能技术生成

1.官方网站:微信登录功能 / 网页应用授权用户信息变更 (qq.com)

2.登陆流程:

和登陆微信没关系,是用户的微信号,和我程序的程序编号(微信给的 目前用的老师的),去请求微信的接口,微信给这个用户一个token,这个token在本系统中 只和该用户对应。登陆成功。

实际上有点出入,是用户扫码后确定登录后,自动去请求微信的接口,我拿不到用户的微信号。

3.具体实现

1》用户在页面点击登陆按钮,前端发送请求给后端

<li><a id="weixin" class="weixin" target="_blank" href="http://localhost:8160/eduuser/usercenter/wx/login"><i class="iconfont icon-weixin"/></a></li>

2》后端接收到请求,通过重定向去请求微信接口。返回重定向地址后,此时浏览器页面会自动新建一个页签,是微信的二维码登陆界面。

重定向到微信接口需要参数 appid这里使用谷粒学院老师的。

重定向地址是自己写的,在扫码之后微信会访问这个地址。

其他参数不清楚。

@GetMapping("login")
    public String genQrConnect(HttpSession session) {
        // 微信开放平台授权baseUrl
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";
        // 回调地址
        String redirectUrl = ConstantWxUtils.REDIRECT_URL; //获取业务服务器重定向地址
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
        } catch (UnsupportedEncodingException e) {
            throw new GuLiExeception(20001, e.getMessage());
        }
        // 防止csrf攻击(跨站请求伪造攻击)
        //String state = UUID.randomUUID().toString().replaceAll("-", "");//一般情况下会使用一个随机数
        String state = "imhelen";//为了让大家能够使用我搭建的外网的微信回调跳转服务器,这里填写你在ngrok的前置域名

        // 采用redis等进行缓存state 使用sessionId为key 30分钟后过期,可配置
        //键:"wechar-open-state-" + httpServletRequest.getSession().getId()
        //值:satte
        //过期时间:30分钟
        //生成qrcodeUrl
        String qrcodeUrl = String.format(
                baseUrl,
                ConstantWxUtils.APP_ID,
                redirectUrl,
                state);
        return "redirect:" + qrcodeUrl;
    }

3.用户在扫码登陆之后,微信会访问第二步携带的回调接口,并携带这个一个临时参数,在controller中获得此参数后,携带secret,访问正式的三方登陆接口,会返回access_token,这个access_token就是 该微信用户,在本系统中,唯一的token,token和用户是一对一的关系,微信登陆完成。

获得这个token后,可以再请求一次微信用户信息接口,获取用户的头像、微信名信息。

在获取完用户的头像、微信名信息后,重定向到前端地址,以重新加载刚才的扫码页面,并且在路径中携带token,前端发现路径中有token后,获取这个token,并携带这个token访问获取用户信息接口,获取用户信息。

    @GetMapping("callback")
    public String callBack(String code, String state) {
        //从redis中将state获取出来,和当前传入的state作比较
        //如果一致则放行,如果不一致则抛出异常:非法访问
        //向认证服务器发送请求换取access_token
        String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                "?appid=%s" +
                "&secret=%s" +
                "&code=%s" +
                "&grant_type=authorization_code";

        String accessTokenUrl = String.format(baseAccessTokenUrl,
                ConstantWxUtils.APP_ID,
                ConstantWxUtils.APP_SECRECT,
                code);
        // 使用httpClient发送请求,得到返回结果
        JSONObject accesstokenInfo = HttpClientUtils.httpGet(accessTokenUrl);
        String access_token = (String) accesstokenInfo.get("access_token");
        String openid = (String) accesstokenInfo.get("openid");

        LambdaQueryWrapper<UcenterMember> lambdaQueryWrapper = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(UcenterMember::getOpenid, openid);
        UcenterMember checkUser = ucenterMemberService.getOne(lambdaQueryWrapper);
        if (checkUser == null) {
            //访问微信的资源服务器,获取用户信息
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                    "?access_token=%s" +
                    "&openid=%s";

            String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
            try {
                JSONObject jsonObject = HttpClientUtils.httpGet(userInfoUrl);
                String nickName = jsonObject.getString("nickname");
                String headUrl = jsonObject.getString("headimgurl");
                UcenterMember ucenterMember = new UcenterMember(openid, nickName, headUrl);
                ucenterMemberService.save(ucenterMember);
                String jwtToken = JwtUtils.getJwtToken(ucenterMember.getId(), nickName);
                return "redirect:http://localhost:3000?guli_token=" + jwtToken;
            } catch (Exception e) {
                throw new GuLiExeception(20001, "用户未注册,获取腾讯用户信息失败");
            }
        }
        String jwtToken = JwtUtils.getJwtToken(checkUser.getId(), checkUser.getNickname());
        String temp=JwtUtils.getMemberIdByJwtTokenString(jwtToken);
        System.out.println("=======");
        System.out.println(temp);
        return "redirect:http://localhost:3000?guli_token="+jwtToken;
    }

前端代码, 前端发现路径中有token后,获取这个token,并携带这个token访问获取用户信息接口,获取用户信息。这是default.vue,当用户没有输入访问的具体地址时,默认访问此界面。

<template>
  <div class="in-wrap">
    <!-- 公共头引入 -->
    <header id="header">
      <section class="container">
        <h1 id="logo">
          <a href="#" title="谷粒学院">
            <img src="~/assets/img/logo.png" width="100%" alt="谷粒学院">
          </a>
        </h1>
        <div class="h-r-nsl">
          <ul class="nav">
            <router-link to="/" tag="li" active-class="current" exact>
              <a>首页</a>
            </router-link>
            <router-link to="/course" tag="li" active-class="current">
              <a>课程</a>
            </router-link>
            <router-link to="/teacher" tag="li" active-class="current">
              <a>名师</a>
            </router-link>
            <router-link to="/article" tag="li" active-class="current">
              <a>文章</a>
            </router-link>
            <router-link to="/qa" tag="li" active-class="current">
              <a>问答</a>
            </router-link>
          </ul>
          <ul class="h-r-login">
            <li v-if="!loginInfo.id" id="no-login">
              <a href="/login" title="登录">
                <em class="icon18 login-icon">&nbsp;</em>
                <span class="vam ml5">登录</span>
              </a>
              |
              <a href="/register" title="注册">
                <span class="vam ml5">注册</span>
              </a>
            </li>
            <li v-if="loginInfo.id" id="is-login-one" class="mr10">
              <a id="headerMsgCountId" href="#" title="消息">
                <em class="icon18 news-icon">&nbsp;</em>
              </a>
              <q class="red-point" style="display: none">&nbsp;</q>
            </li>
            <li v-if="loginInfo.id" id="is-login-two" class="h-r-user">
              <a href="/ucenter" title>
                <img
                  :src="loginInfo.avatar"
                  width="30"
                  height="30"
                  class="vam picImg"
                  alt
                />
                <span id="userName" class="vam disIb">{{
                  loginInfo.nickname
                }}</span>
              </a>
              <a
                href="javascript:void(0);"
                title="退出"
                @click="logout()"
                class="ml5"
                >退出</a
              >
            </li>
            <!-- /未登录显示第1 li;登录后显示第2,3 li -->
          </ul>
          <aside class="h-r-search">
            <form action="#" method="post">
              <label class="h-r-s-box">
                <input
                  type="text"
                  placeholder="输入你想学的课程"
                  name="queryCourse.courseName"
                  value
                />
                <button type="submit" class="s-btn">
                  <em class="icon18">&nbsp;</em>
                </button>
              </label>
            </form>
          </aside>
        </div>
        <aside class="mw-nav-btn">
          <div class="mw-nav-icon"></div>
        </aside>
        <div class="clear"></div>
      </section>
    </header>
    <!-- /公共头引入 -->

    <nuxt />

    <!-- 公共底引入 -->
    <footer id="footer">
      <section class="container">
        <div class>
          <h4 class="hLh30">
            <span class="fsize18 f-fM c-999">友情链接</span>
          </h4>
          <ul class="of flink-list">
            <li>
              <a href="http://www.atguigu.com/" title="尚硅谷" target="_blank"
                >尚硅谷</a
              >
            </li>
          </ul>
          <div class="clear"></div>
        </div>
        <div class="b-foot">
          <section class="fl col-7">
            <section class="mr20">
              <section class="b-f-link">
                <a href="#" title="关于我们" target="_blank">关于我们</a>|
                <a href="#" title="联系我们" target="_blank">联系我们</a>|
                <a href="#" title="帮助中心" target="_blank">帮助中心</a>|
                <a href="#" title="资源下载" target="_blank">资源下载</a>|
                <span>服务热线:010-56253825(北京) 0755-85293825(深圳)</span>
                <span>Email:info@atguigu.com</span>
              </section>
              <section class="b-f-link mt10">
                <span>©2018课程版权均归谷粒学院所有 京ICP备17055252号</span>
              </section>
            </section>
          </section>
          <aside class="fl col-3 tac mt15">
            <section class="gf-tx">
              <span>
                <img src="~/assets/img/wx-icon.png" alt />
              </span>
            </section>
            <section class="gf-tx">
              <span>
                <img src="~/assets/img/wb-icon.png" alt />
              </span>
            </section>
          </aside>
          <div class="clear"></div>
        </div>
      </section>
    </footer>
    <!-- /公共底引入 -->
  </div>
</template>
<script>
import "~/assets/css/reset.css";
import "~/assets/css/theme.css";
import "~/assets/css/global.css";
import "~/assets/css/web.css";
import "~/assets/css/base.css";
import "~/assets/css/activity_tab.css";
import "~/assets/css/bottom_rec.css";
import "~/assets/css/nice_select.css";
import "~/assets/css/order.css";
import "~/assets/css/swiper-3.3.1.min.css";
import "~/assets/css/pages-weixinpay.css";
import cookie from "js-cookie";
import loginApi from "@/api/login";
export default {
  data() {
    return {
      guli_token: "",
      loginInfo: {
        id: "",
        age: "",
        avatar: "",
        mobile: "",
        nickname: "",
        sex: "",
      },
    };
  },
  created() {
    // 获取路径中的token中的值
    this.guli_token = this.$route.query.guli_token;
    if (this.guli_token) {
      cookie.set("guli_token", this.guli_token, { domain: "localhost" });
      console.log("执行微信登录");
      //如果可以取到token,用微信的方式登录
      this.wxLogin();
    }
    this.showInfo();
  },
  methods: {
    //创建方法,从cookie中获取用户信息
    showInfo() {
      var userStr = cookie.get("guli_ucenter");
      if (userStr) {
        //把字符串转换为JSON对象
        this.loginInfo = JSON.parse(userStr);
      }
    },
    //退出
    logout() {
      // domain 作用范围
      cookie.set("guli_ucenter", "", { domain: "localhost" });
      cookie.set("guli_token", "", { domain: "localhost" });
      console.log("回首页");
      //回到首页面
      window.location.href = "/";
    },
    //微信登陆
    wxLogin() {
      if (this.guli_token == "") return;
      //登录成功根据token获取用户信息
      loginApi.getLoginInfo().then((response) => {
        this.loginInfo = response.data.data.member;

        //将用户信息记录cookie
        cookie.set("guli_ucenter", JSON.stringify(this.loginInfo), {
          domain: "localhost",
        });
      });
    },
  },
};
</script>

 后端接收到前端发送的token,根据这个token查询用户信息并返回(用户信息在第三步上面已经获取到,并且保存数据库,这里只查就可以)

    @GetMapping("/info")
    public R geInfo(HttpServletRequest request) {
        String headers = "";
        Enumeration<String> names = request.getHeaderNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            String value = request.getHeader(name);
            headers += name + "=" + value + "\n";
        }
        System.out.println(headers + "请求用户信息请求头如上");

        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        UcenterMember uCenterMember = ucenterMemberService.getById(memberId);
        return R.ok().data("member", uCenterMember);
    }

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值