微信扫码登录

本文最后更新于:2023年11月9日 晚上

微信扫码登录

小伙伴们有各种疑问可以去参考官方文档进行详细的学习下 微信开发文档 ,此次介绍的将是前后端不分离的微信扫码登录

微信登录开发流程:

  1. 申请微信接入
  2. 生成登录二维码
  3. 用户扫码并授权
  4. 调用回调方法
  5. 通过code去获取用户信息带到页面展示

官方流程图:

1. 申请微信接入:

先提醒下各位:申请微信接入很麻烦,本人因为公司业务需要,用的是公司申请好的。还没自己去申请过。

先去到 微信开放平台 https://open.weixin.qq.com


申请一个网站应用 (要审核通过之后才能用)


注:

  1. appid和appSecret就是调用微信接口的凭证
  2. 授权回调域:调用微信接口生成的二维码地址,用户扫码并授权后,会重定向到回调地址
  3. 因为在本地localhost进行测试,如果回调地址为localhost第三方微信将无法进行跳转。原因是外网访问不到本地,怎么办?解决办法:那就使用 ngrok 内网穿透把本地项目服务映射到公网,所以在测试时填写的回调地址是内网穿透时的访问域名
  4. 如果不知道内网穿透的小伙伴,建议先看看 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注解引入即可

  1. appid:
  2. appsecret:
  3. scope: snsapi_login
  4. 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://wechatlogin.free.idcfengye.com/callBack

这里需要一个工具类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;

/**
* @author: libo
* @date: 2020/11/21 20:49
* @motto: 即使再小的帆也能远航
*/
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;

/**
* @author: libo
* @date: 2020/11/21 21:32
* @motto: 即使再小的帆也能远航
*/
@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) {

//1.通过code获取access_token
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);

//2.通过access_token和openid获取用户信息
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);

/*转为JSON*/
JSONObject user = new JSONObject(userInfoStr);

/*只获取openid并返回,openid是微信用户的唯一标识,userInfoStr里面有用户的全部信息*/
String openid = user.getString("openid");
map.put("openid", openid);

/*获取用户头像url*/
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.测试结果:

使用内网穿透的域名来访问接口哦,因为已经在公网映射到本地项目了

  1. 访问login接口,点击logo图标

  2. 扫码测试

  1. 授权后调用回调方法,拿到用户信息放到页面展示
    注: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参数去获取用户信息

如果我们有再三思考的机会,几乎没有一件事情是不能被简化的。

微信扫码登录
http://example.com/2020/11/21/微信扫码登录/
作者
阿波~
发布于
2020年11月21日
更新于
2023年11月9日
许可协议