Dubbo 分布式架构搭建教育 PC 站 - 微信登录
微信开放平台(针对开发者和公司):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
接口调用凭证(例如:令牌)
微信扫描登录授权简述
用户 -> 使用微信扫码登录第三方应用 -> 微信登录的服务地址回调函数 -> 发出申请 —> 微信账号微信账号 -> 发送确认登录
用户 -> 点击确认微信账号 -> 重定向到第三方应用提供的函数,并携带 Authorization Code
用户 -> 使用 Authorization Code + App ID + App Secret 到微信账号换取 Access Token
微信账号 -> 返回 Access Token用户 -> 通过 Access Token 获取用户的信息
微信账号 -> 返回对应的用户信息
开发步骤
Vue 项目安装
微信官方提供的生成二维码的 js:
npm install vue-wxlogin
如果不是 Vue 的项目,可以直接引用官方提供的 js 文件,来生成二维码。
页面引入
src\components\Header\Header.vue
依赖
<dependency><groupId>javax.servletgroupId><artifactId>servlet-apiartifactId><version>2.4version><scope>providedscope>
dependency>
<dependency><groupId>org.apache.httpcomponentsgroupId><artifactId>httpclientartifactId><version>4.5.12version>
dependency>
修改 hosts 文件
回调默认指定的是 80 端口
127.0.0.1 www.pinzhi365.com
封装 Http Client
commons.HttpClientUtil
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 {// 创建 urlURIBuilder 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 == 200) {// 响应的内容字符串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
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_tokenprivate 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
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;// 国家,如中国为 CNprivate String country;// 用户头像,最后一个数值代表正方形头像大小(有 0、46、64、96、132 数值可选,0 代表 640*640 正方形头像),用户没有头像时该项为空private String headimgurl;// 用户特权信息,json 数组,如微信沃卡用户为 chinaunicomprivate 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
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 == 0){// 未注册,自动注册并登录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(300);dto.setMessage("帐号密码不匹配,登录失败!");}else{// 200 表示成功dto.setState(200);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
解决二维码在谷歌浏览器的 Bug
谷歌浏览器调试的时候,iframe 标签跨域问题导致无法跳转的 bug。
如果 iframe 未添加 sandbox 属性,或者 sandbox 属性不赋值,就代表采用默认的安全策略。
即 iframe 的页面将会被当做一个独立的源,并且不能提交表单,不能执行 JavaScript 脚本,也不能让包含 iframe 的父页面导航到其他地方,所有的插件,如 Flash 等也全部不能起作用。
简单来说 iframe 就只剩下一个展示数据的功能,正如他的名字一样,所有的内容都被放进了一个“单独的沙盒”。
Sandbox 包含的属性及作用:
allow-scripts- 允许运行执行脚本allow-top-navigation- 允许 iframe 能够主导window.top进行页面跳转allow-same-origin- 允许同域请求,比如 Ajax、storageallow-forms- 允许进行提交表单allow-popups- 允许 iframe 中弹出新窗口,比如window.open、target=”_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
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 === !0? (c = "true"): a.self_redirect === !1 && (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
methods: {// 微信登录goToLoginWX() {// 普通的登录表单隐藏document.getElementById("loginForm").style.display = "none";// 显示二维码的容器document.getElementById("wxLoginForm").style.display = "block";// 去生成二维码this.$nextTick(function(){// 直接调用会报错:TypeError: Cannot read property 'appendChild' of nullthis.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
.impowerBox .qrcode {width: 200px;}
.impowerBox .title {display: none;}
.impowerBox .info {width: 200px;}
.status_icon {display: none}cs
.impowerBox .status {text-align: center;}
想了解更多,欢迎关注我的微信公众号:Renda_Zhang
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
