本文最后更新于:2023年11月9日 晚上
微信扫码登录
小伙伴们有各种疑问可以去参考官方文档进行详细的学习下 微信开发文档 ,此次介绍的将是前后端不分离的微信扫码登录
微信登录开发流程:
- 申请微信接入
- 生成登录二维码
- 用户扫码并授权
- 调用回调方法
- 通过code去获取用户信息带到页面展示
官方流程图:
1. 申请微信接入:
先提醒下各位:申请微信接入很麻烦,本人因为公司业务需要,用的是公司申请好的。还没自己去申请过。
先去到 微信开放平台 https://open.weixin.qq.com
申请一个网站应用 (要审核通过之后才能用)
注:
- appid和appSecret就是调用微信接口的凭证
- 授权回调域:调用微信接口生成的二维码地址,用户扫码并授权后,会重定向到回调地址
- 因为在本地localhost进行测试,如果回调地址为localhost第三方微信将无法进行跳转。原因是外网访问不到本地,怎么办?解决办法:那就使用 ngrok 内网穿透把本地项目服务映射到公网,所以在测试时填写的回调地址是内网穿透时的访问域名
- 如果不知道内网穿透的小伙伴,建议先看看 Sunny-Ngrok 内网穿透的使用
如果不知道内网穿透的小伙伴,建议先看看 Sunny-Ngrok 内网穿透的使用
启动 Sunny-Ngrok 内网穿透的客户端,先将本地服务映射到公网。
2. 项目环境搭建:
下面正式开始代码部分,创建SpringBoot项目并导入所需Jar包:
环境:JDK1.8,SpringBoot2.3.5.RELEASE
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
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20200518</version> </dependency> </dependencies>
|
yaml文件:
因为所需要这4个参数,所以直接配置在yaml文件中,使用时直接在类中直接通过@Value注解引入即可
- appid:
- appsecret:
- scope: snsapi_login
- callBack:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| server: port: 80
spring: thymeleaf: prefix: classpath:/templates/ suffix: .html mvc: static-path-pattern: classpath:/static/
wechat: #微信接口的AppID: appid: #微信接口的AppSecret: appsecret: #应用授权作用域(网站应用目前的固定写法就是snsapi_login) scope: snsapi_login #扫码之后的回调地址: callBack: http:
|
这里需要一个工具类HttpRequestUtils ,用于发起http请求。可以将代码直接复制过去用
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
| package com.login.utils;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;
public class HttpRequestUtils {
private static CloseableHttpClient httpClient;
static { PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(100); cm.setDefaultMaxPerRoute(20); cm.setDefaultMaxPerRoute(50); httpClient = HttpClients.custom().setConnectionManager(cm).build(); }
public static String httpGet(String url) { CloseableHttpResponse response = null; BufferedReader in = null; String result = ""; try { HttpGet httpGet = new HttpGet(url); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(30000).setConnectionRequestTimeout(30000).setSocketTimeout(30000).build(); httpGet.setConfig(requestConfig); httpGet.setConfig(requestConfig); httpGet.addHeader("Content-type", "application/json; charset=utf-8"); httpGet.setHeader("Accept", "application/json"); response = httpClient.execute(httpGet); in = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); StringBuffer sb = new StringBuffer(""); String line = ""; String NL = System.getProperty("line.separator"); while ((line = in.readLine()) != null) { sb.append(line + NL); } in.close(); result = sb.toString(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (null != response) { response.close(); } } catch (IOException e) { e.printStackTrace(); } } return result; } }
|
3.后端Controller接口:
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| package com.login.controller;
import com.login.utils.HttpRequestUtils; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping;
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Map;
@Controller @CrossOrigin @SuppressWarnings("unchecked") public class weChatController {
@Value(value = "${wechat.appid}") private String appid;
@Value(value = "${wechat.appsecret}") private String appsecret;
@Value(value = "${wechat.scope}") private String scope;
@Value(value = "${wechat.callback}") private String callBack;
@RequestMapping("/login") public String index(Model model) throws Exception {
String oauthUrl = "https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect"; String redirect_uri = URLEncoder.encode(callBack, "utf-8"); oauthUrl = oauthUrl.replace("APPID", appid).replace("REDIRECT_URI", redirect_uri).replace("SCOPE", scope); model.addAttribute("oauthUrl", oauthUrl);
return "login"; }
@RequestMapping("/custom") public String custom(Model model) throws UnsupportedEncodingException {
String redirect_uri = URLEncoder.encode(callBack, "utf-8");
model.addAttribute("appid", appid); model.addAttribute("scope", scope); model.addAttribute("redirect_uri", redirect_uri);
return "custom"; }
@RequestMapping("/callBack") public String callBack(String code,Map<String,Object> map) {
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; url = url.replace("APPID", appid).replace("SECRET", appsecret).replace("CODE", code); String tokenInfoStr = HttpRequestUtils.httpGet(url);
JSONObject tokenInfoObject = new JSONObject(tokenInfoStr);
String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID"; userInfoUrl = userInfoUrl.replace("ACCESS_TOKEN", tokenInfoObject.getString("access_token")).replace("OPENID", tokenInfoObject.getString("openid")); String userInfoStr = HttpRequestUtils.httpGet(userInfoUrl);
map.put("token", tokenInfoStr);
JSONObject user = new JSONObject(userInfoStr);
String openid = user.getString("openid"); map.put("openid", openid);
String headimgurl = user.getString("headimgurl"); map.put("headimgurl", headimgurl);
String nickname = user.getString("nickname"); map.put("nickname", nickname);
int sex = user.getInt("sex"); map.put("sex", sex);
String country = user.getString("country"); map.put("country", country);
String province = user.getString("province"); map.put("province", province);
String city = user.getString("city"); map.put("city", city);
return "callBack"; }
}
|
4.HTML页面代码:
1.点击生成二维码页面:我这里是嵌入了一张微信的Logo图片,点击a标签跳转 weChatLogin.html
1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>wechat登录</title> </head> <body> <a th:href="${oauthUrl}"> <img src="../static/wechat_logo.png" alt="微信登录"> </a> </body> </html>
|
2.回调方法后返回用户信息页面 callBack.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>授权结果页</title> </head> <body>
<h2>用戶,授权成功!</h2><br> <h3>通过code获取access_token 结果:</h3> <p th:text="${token}"></p>
<h3>通过access_token获取用户信息 结果:</h3> 头像:<img th:src="${headimgurl}" alt="用户头像"><br/> openid:<span th:text="${openid}"></span><br/> 昵称:<span th:text="${nickname}"></span><br/> 性别:<span th:text="${sex}"></span><br/> 国家:<span th:text="${country}"></span><br/> 省份:<span th:text="${province}"></span><br/> 城市:<span th:text="${city}"></span>
</body> </html>
|
3.内嵌(自定义二维码) custom.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>内嵌(自定义二维码)</title> </head> <script src="http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script> <body>
<center><div id="login_container"></div></center> <script th:inline="javascript"> var obj = new WxLogin({ self_redirect:true, id:"login_container", appid: [[${appid}]], scope: [[${scope}]], redirect_uri: [[${redirect_uri}]], state: "", style: "", href: "" }); </script> </body> </html>
|
5.测试结果:
使用内网穿透的域名来访问接口哦,因为已经在公网映射到本地项目了
访问login接口,点击logo图标
扫码测试
- 授权后调用回调方法,拿到用户信息放到页面展示
注:openid是微信用户的唯一id
6.补充说明:
到这里呢微信登录SpringBoot前后端不分离就差不多了,在这里想给大家提一下如果是前后端分离怎么做?
1.回调地址一般是网站的主页对吧,例如:www.abc.com
2.前端按钮通过appid和回调地址生成二维码
3.用户扫码授权之后,微信接口会再通过回调地址重定向回主页 www.abc.com。在这是会有一个名为code的参数
4.此时前端拥有code之后,传到后端接口方法中去,后端通过code获取用户信息。再返回前端
总结为一句话:1.www.adc.com主页生成二维码,2.扫码授权登录,3.拿code参数去获取用户信息
如果我们有再三思考的机会,几乎没有一件事情是不能被简化的。