前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Dubbo 分布式架构搭建教育 PC 站 - 微信登录

Dubbo 分布式架构搭建教育 PC 站 - 微信登录

作者头像
RendaZhang
发布2020-11-04 15:23:33
1.1K0
发布2020-11-04 15:23:33
举报
文章被收录于专栏:RendaRenda

微信开放平台(针对开发者和公司):https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

准备工作

  • 网站应用微信登录是基于 OAuth2.0 协议标准构建的微信 OAuth2.0 授权登录系统。
  • 在进行微信 OAuth2.0 授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的网站应用,并获得相应的 AppID 和 AppSecret,申请微信登录且通过审核后,可开始接入流程。
  • 注册帐号和申请应用都是免费的,必须要有一个线上的网站,才能审核通过,就可以使用微信的登录了。
  • 但是如果想使用微信支付的功能,就必须认证开发者资质(认证一次 300 块人民币)。

名词解释

OAuth 2.0 协议

OAuth (Open Authorization) 协议就是为用户资源的授权提供了一个安全、开放、简易的标准。

OAuth 在第三方应用与服务提供商之间设置了一个授权层,第三方应用通过授权层获取令牌,再通过令牌获取信息。

令牌与密码的作用都可以进入系统,但是有三点差异:

1、令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。

2、令牌可以被数据所有者撤销,会立即失效。

3、令牌有权限范围,比如不能获取用户密码信息。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。

这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。

OAuth 的四种授权模式:

1、授权码模式(功能最完整、流程最严密的授权模式)

2、密码模式

3、简化模式

4、客户端模式

App ID

应用 ID,唯一标识(身份证号)

App Secret

应用的密钥

Authorization Code

授权的临时凭证(例如:临时身份证)

Access Token

接口调用凭证(例如:令牌)

微信扫描登录授权简述

代码语言:javascript
复制
用户 -> 使用微信扫码登录第三方应用 -> 微信登录的服务地址回调函数 -> 发出申请 —> 微信账号

微信账号 -> 发送确认登录
用户 -> 点击确认

微信账号 -> 重定向到第三方应用提供的函数,并携带 Authorization Code
用户 -> 使用 Authorization Code + App ID + App Secret 到微信账号换取 Access Token
微信账号 -> 返回 Access Token

用户 -> 通过 Access Token 获取用户的信息
微信账号 -> 返回对应的用户信息

开发步骤

Vue 项目安装

微信官方提供的生成二维码的 js:

代码语言:javascript
复制
npm install vue-wxlogin

如果不是 Vue 的项目,可以直接引用官方提供的 js 文件,来生成二维码。

页面引入

src\components\Header\Header.vue

代码语言:javascript
复制
<div id="loginForm">
    <!-- 登录表单 -->
    <el-form/>
    <!-- 登录按钮 -->
    <el-button/>
    <!-- 微信登录图标 -->
    <img/>
</div>

<!-- 二维码 -->
<wxlogin id="wxLoginForm" style="display:none" 
         :appid="appid" :scope="scope" :redirect_uri="redirect_uri">
</wxlogin>
<script>
    import wxlogin from 'vue-wxlogin'; // 引入

    export default {
        name: "Header",
        components: {
            wxlogin  // 声明
        },
        data() {
            return {
                appid:"wxd99431bbff8305a0", // 应用唯一标识,在微信开放平台提交应用审核通过后获得
                scope:"snsapi_login", // 应用授权作用域,网页应用目前仅填写snsapi_login即可
                redirect_uri:"http://www.pinzhi365.com/wxlogin",  //重定向地址,(回调地址)
            };
        },
        methods: {
            goToLoginWX() {
                document.getElementById("loginForm").style.display = "none";
                document.getElementById("wxLoginForm").style.display = "block";
            }
        }
    }
</script>
依赖
代码语言:javascript
复制
<!-- 需要使用 HttpServletRequest 获得参数 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.4</version>
    <scope>provided</scope>
</dependency>
<!-- 需要使用 HttpClient 发出请求 -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>
修改 hosts 文件

回调默认指定的是 80 端口

代码语言:javascript
复制
127.0.0.1 www.pinzhi.com
封装 Http Client

commons.HttpClientUtil

代码语言:javascript
复制
package commons;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.net.URI;
import java.util.Map;

/**
 * HttpClient 的封装工具类
 *
 * @author Renda Zhang
 * @since 2020-10-27 0:28
 */
public class HttpClientUtil {

    public static String doGet(String url) {
        return doGet(url, null);
    }

    /**
     * Get 请求,支持 request 请求方式,不支持 restful 方式
     *
     * @param url   请求地址
     * @param param 参数
     * @return 响应的字符串
     */
    public static String doGet(String url, Map<String, String> param) {
        // 创建 httpclient 对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // 创建 url
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                // 在 url 后面拼接请求参数
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();

            // 创建 http get 请求
            HttpGet httpGet = new HttpGet(uri);
            // 执行请求
            response = httpClient.execute(httpGet);
            // 从响应对象中获取状态码(成功或失败的状态)
            int statusCode = response.getStatusLine().getStatusCode();
            System.out.println("响应的状态 = " + statusCode);
            // 200 表示响应成功
            if (statusCode == ) {
                // 响应的内容字符串
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            try {
                if (response != null) {
                    response.close();
                }
                httpClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return resultString;
    }

}
定义从微信返回的数据对象

entity.Token

代码语言:javascript
复制
package entity;

/**
 * 令牌实体类
 *
 * @author Renda Zhang
 * @since 2020-10-27 0:33
 */
public class Token {

    // 接口调用凭证
    private String access_token;
    // access_token 接口调用凭证超时时间,单位(秒)
    private String expires_in;
    // 用户刷新 access_token
    private String refresh_token;
    // 授权用户唯一标识
    private String openid;
    // 用户授权的作用域,使用逗号分隔
    private String scope;
    // 当且仅当该网站应用已获得该用户的 user info 授权时,才会出现该字段。
    private String unionid;

    public Token() {
    }

    public Token(String access_token, String expires_in, String refresh_token, String openid, String scope, String unionid) {
        this.access_token = access_token;
        this.expires_in = expires_in;
        this.refresh_token = refresh_token;
        this.openid = openid;
        this.scope = scope;
        this.unionid = unionid;
    }

    // getter and setter ...

}

entity.WxUser

代码语言:javascript
复制
package entity;

/**
 * 微信的用户信息
 *
 * @author Renda Zhang
 * @since 2020-10-27 0:33
 */
public class WxUser {

    // 普通用户的标识,对当前开发者帐号唯一
    private String openid;
    // 普通用户昵称
    private String nickname;
    // 普通用户性别,1 为男性,2 为女性
    private String sex;
    // 普通用户个人资料填写的省份
    private String province;
    // 普通用户个人资料填写的城市
    private String city;
    // 国家,如中国为 CN
    private String country;
    // 用户头像,最后一个数值代表正方形头像大小(有 0、46、64、96、132 数值可选,0 代表 640*640 正方形头像),用户没有头像时该项为空
    private String headimgurl;
    // 用户特权信息,json 数组,如微信沃卡用户为 chinaunicom
    private String privilege;
    // 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。
    private String unionid;

    public WxUser() {
    }

    public WxUser(String openid, String nickname, String sex, String province, String city, String country, String headimgurl, String privilege, String unionid) {
        this.openid = openid;
        this.nickname = nickname;
        this.sex = sex;
        this.province = province;
        this.city = city;
        this.country = country;
        this.headimgurl = headimgurl;
        this.privilege = privilege;
        this.unionid = unionid;
    }

    // getter and setter ...

}
回调函数 Controller

com.renda.wx.WxLoginController

代码语言:javascript
复制
package com.renda.wx;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.renda.entity.User;
import com.renda.entity.UserDTO;
import com.renda.user.UserService;
import commons.HttpClientUtil;
import entity.Token;
import entity.WxUser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 微信支付相关控制
 *
 * @author Renda Zhang
 * @since 2020-10-27 0:41
 */
@RestController
@RequestMapping("user")
public class WxLoginController {

    @Reference // dubbo 远程消费
    private UserService userService;

    // 是否用微信登录成功,dto 为 null,则尚未登录
    private UserDTO dto = null;

    @GetMapping("wxlogin")
    public String wxlogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 1. 微信官方发给我们一个临时凭证
        String code = request.getParameter("code");
        System.out.println("【临时凭证】code = " + code);
        // 2. 通过 code,去微信官方申请一个正式的 token(令牌)
        String getTokenByCode_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxd99431bbff8305a0&secret=60f78681d063590a469f1b297feff3c4&code=" + code + "&grant_type=authorization_code";
        String tokenString = HttpClientUtil.doGet(getTokenByCode_url);
        System.out.println("tokenString = " + tokenString);
        // 将 json 格式的 token 字符串转换成实体对象,方便存和取
        Token token = JSON.parseObject(tokenString, Token.class);

        // 3. 通过 token,去微信官方获取用户的信息
        String getUserByToken_url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + token.getAccess_token() + "&openid=" + token.getOpenid();
        String userinfoString = HttpClientUtil.doGet(getUserByToken_url);
        System.out.println("userinfoString = " + userinfoString);
        // 将 json格式的user字符串转换成实体对象,方便存和取
        WxUser wxUser = JSON.parseObject(userinfoString, WxUser.class);
        System.out.println("微信昵称 = " + wxUser.getNickname());
        System.out.println("微信头像 = " + wxUser.getHeadimgurl());

        // 业务流程:需要手机号 wxUser.getUnionid() 和密码 wxUser.getUnionid(),头像和昵称
        User user = null;
        dto = new UserDTO();
        // 检测手机号是否注册
        Integer i = userService.checkPhone(wxUser.getUnionid());
        if(i == ){
            // 未注册,自动注册并登录
            userService.register(wxUser.getUnionid(), wxUser.getUnionid(),wxUser.getNickname(),wxUser.getHeadimgurl());
            dto.setMessage("手机号尚未注册,系统已帮您自动注册,请牢记密码!");
            user = userService.login(wxUser.getUnionid(), wxUser.getUnionid());
        }else{
            user = userService.login(wxUser.getUnionid(), wxUser.getUnionid());
            if(user == null){
                // 300 表示失败
                dto.setState();
                dto.setMessage("帐号密码不匹配,登录失败!");
            }else{
                // 200 表示成功
                dto.setState();
                dto.setMessage("登录成功!");
            }
        }

        dto.setContent(user);

        response.sendRedirect("http://localhost:8080");
        return null;
    }

    @GetMapping("checkWxStatus")
    public UserDTO checkWxStatus(){
        return this.dto;
    }

    @GetMapping("logout")
    public Object logout(){
        this.dto = null;
        return null;
    }

}
前端微信登录 js 代码

src\components\Header\Header.vue

代码语言:javascript
复制
<script>
    created(){
        // 当刷新页面,组件创建成功之后,立刻检测本地储存中是否存在用户对象
        this.userDTO = JSON.parse( localStorage.getItem("user") );
        if(this.userDTO != null){
             // 已登录
            this.isLogin = true;
        }else{
            // 去检测微信是否登录过
            this.axios.get("http://localhost:80/user/checkWxStatus")
            .then((result) => {
                this.userDTO = result.data;
                this.phone = this.userDTO.content.phone;
                this.password = this.userDTO.content.password;
                // 走普通登录
                this.login();
            }).catch((error) => {
                this.$message.error("登录失败!");
            });
        }
    }

    // 登出
    logout(){
        // 将登录成功的对象信息保存到本地储存中
        localStorage.setItem("user", null); 
        // 更新登录状态
        this.isLogin = false;
        alert('谢谢使用,再见!');
        // 去检测微信是否登录过
        this.axios.get("http://localhost:80/user/logout")
        .then( (result)=>{})
        .catch( (error)=>{
            this.$message.error("登录失败!");
        });
    }
</script>
解决二维码在谷歌浏览器的 Bug

谷歌浏览器调试的时候,iframe 标签跨域问题导致无法跳转的 bug。

如果 iframe 未添加 sandbox 属性,或者 sandbox 属性不赋值,就代表采用默认的安全策略。

iframe 的页面将会被当做一个独立的源,并且不能提交表单,不能执行 JavaScript 脚本,也不能让包含 iframe 的父页面导航到其他地方,所有的插件,如 Flash 等也全部不能起作用。

简单来说 iframe 就只剩下一个展示数据的功能,正如他的名字一样,所有的内容都被放进了一个“单独的沙盒”。

Sandbox 包含的属性及作用:

  • allow-scripts - 允许运行执行脚本
  • allow-top-navigation - 允许 iframe 能够主导 window.top 进行页面跳转
  • allow-same-origin - 允许同域请求,比如 Ajax、storage
  • allow-forms - 允许进行提交表单
  • allow-popups - 允许 iframe 中弹出新窗口,比如 window.opentarget=”_blank”
  • allow-pointer-lock - 在 iframe 中可以锁定鼠标,主要和鼠标锁定有关

加上 sandbox=“allow-scripts allow-top-navigation allow-same-origin” 属性,即可解决。

官方 js:http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js

因为无法修改微信服务器上的 js 文件,所以将 js 代码放在本地并进行修改:

src\components\Header\Header.vue

代码语言:javascript
复制
created() {
  // 当刷新页面,组件创建成功之后,立刻检测本地储存中是否存在用户对象
  this.userDTO = JSON.parse(localStorage.getItem("user"));
  if (this.userDTO != null) {
    // 已登录
    this.isLogin = true;
  } else {
    // 去检测微信是否登录过
    this.axios
      .get("http://localhost:80/user/checkWxStatus")
      .then((result) => {
        this.userDTO = result.data;
        this.phone = this.userDTO.content.phone;
        this.password = this.userDTO.content.password;
        // 走普通登录
        this.login();
      })
      .catch((error) => {
        this.$message.error("登录失败!");
      });
  }

  !(function (a, b, c) {
    function d(a) {
      var c = "default";
      a.self_redirect === !
        ? (c = "true")
        : a.self_redirect === ! && (c = "false");
      var d = b.createElement("iframe"),
        e =
          "https://open.weixin.qq.com/connect/qrconnect?appid=" +
          a.appid +
          "&scope=" +
          a.scope +
          "&redirect_uri=" +
          a.redirect_uri +
          "&state=" +
          a.state +
          "&login_type=jssdk&self_redirect=" +
          c +
          "&styletype=" +
          (a.styletype || "") +
          "&sizetype=" +
          (a.sizetype || "") +
          "&bgcolor=" +
          (a.bgcolor || "") +
          "&rst=" +
          (a.rst || "");
      (e += a.style ? "&style=" + a.style : ""),
        (e += a.href ? "&href=" + a.href : ""),
        (d.src = e),
        (d.frameBorder = "0"),
        (d.allowTransparency = "true"),
        // 允许多种请求
        (d.sandbox = "allow-scripts allow-top-navigation allow-same-origin"),
        (d.scrolling = "no"),
        (d.width = "300px"),
        (d.height = "400px");
      var f = b.getElementById(a.id);
      (f.innerHTML = ""), f.appendChild(d);
    }
    a.WxLogin = d;
  })(window, document);
},

src\components\Course.vue

代码语言:javascript
复制
<div id="wxLoginForm"></div>
代码语言:javascript
复制
methods: {
    // 微信登录
    goToLoginWX() {
        // 普通的登录表单隐藏
        document.getElementById("loginForm").style.display = "none";
        // 显示二维码的容器
        document.getElementById("wxLoginForm").style.display = "block";
        // 去生成二维码
        this.$nextTick(function(){
            // 直接调用会报错:TypeError: Cannot read property 'appendChild' of null
            this.createCode();
        });
    },
    // 生成二维码
    createCode(){
        var obj = new WxLogin({
            // 显示二维码的容器
            id:"wxLoginForm",
            // 应用唯一标识,在微信开放平台提交应用审核通过后获得
            appid: "wxd99431bbff8305a0",
            // 应用授权作用域,网页应用目前仅填写 snsapi_login 即可
            scope: "snsapi_login",
            // 重定向地址,回调地址
            redirect_uri: "http://www.pinzhi365.com/wxlogin",
            href: "data:text/css;base64,加密后的样式"
        });
    },
}

用站长工具对样式代码进行 base64 加密:http://tool.chinaz.com/Tools/Base64.aspx

代码语言:javascript
复制
.impowerBox .qrcode {width: 200px;}
.impowerBox .title {display: none;}
.impowerBox .info {width: 200px;}
.status_icon {display: none}cs
.impowerBox .status {text-align: center;}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Renda 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 准备工作
  • 名词解释
    • OAuth 2.0 协议
      • App ID
        • App Secret
          • Authorization Code
            • Access Token
            • 微信扫描登录授权简述
            • 开发步骤
              • Vue 项目安装
                • 页面引入
                  • 依赖
                    • 修改 hosts 文件
                      • 封装 Http Client
                        • 定义从微信返回的数据对象
                          • 回调函数 Controller
                            • 前端微信登录 js 代码
                              • 解决二维码在谷歌浏览器的 Bug
                              相关产品与服务
                              访问管理
                              访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档