微信登录

学习使用微信登录接口完成用户登录操作

1.登录流程

微信登录:小程序登录 | 微信开放文档

以最近在做的一个项目为例,其中的微信用户登录流程为:

  1. 小程序端,调用wx.login()获取code,就是授权码。

  2. 小程序端,调用wx.request()发送请求并携带code,请求后端服务。

  3. 服务端,通过HttpClient向微信接口服务的登录凭证校验接口发送请求,并携带appid+secret+code+grant_type三个参数。

  4. 服务端,接收微信接口服务返回的数据,session_key+openid等。openid是微信用户的唯一标识。

  5. 服务端,根据openid在数据库查询用户,若不存在则注册用户,并查出userId

  6. 服务端,自定义登录态,将userId加入JWT令牌(token)中方便后绪请求身份校验。封装UserLoginVo对象返回给小程序,属性包括:userId、openid、token

  7. 小程序端,收到自定义登录态,存储storage。

  8. 小程序端,后绪通过wx.request()发起业务请求时,携带token。

  9. 服务端,收到请求后,通过携带的token,解析当前登录用户的id。

  10. 服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。

2.小程序端获取临时凭证

调用 wx.login() 获取 临时登录凭证code(js_code)

点击登录后,可以看到控制台输出了临时凭证,再用wx.request()方法将该凭证发送到后端服务器即可

3.后端处理临时凭证

小程序访问/user/user/login接口,后端将临时凭证封装在UserLoginDTO中交给UserService的wxlogin()处理

根据微信服务接口:小程序登录 / 小程序登录 (qq.com)

我们通过HttpClient需要发送GET请求到 https://api.weixin.qq.com/sns/jscode2session 并携带以下参数

从返回的参数中就可以得到openid,即每个微信用户的唯一标识号

通过这个openid就可以查询userId

将userId放入JWT并返回给小程序,小程序后续请求都带上JWT,即可通过解析JWT得到发送该请求的用户的userId

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
@RestController
@RequestMapping("/user/user")
@Slf4j
@Api(tags = "C端用户相关接口")
public class UserController {
@Autowired
UserService userService;
@Autowired
JwtProperties jwtProperties;

@PostMapping("/login")
@ApiOperation("登录")
public Result<UserLoginVO> wxlogin(@RequestBody UserLoginDTO userLoginDTO){
//微信登录
User user = userService.wxlogin(userLoginDTO);
//生成jwt令牌
Map<String,Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID,user.getId());
String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),
jwtProperties.getUserTtl(),
claims);
//返回对象
UserLoginVO vo = UserLoginVO.builder()
.id(user.getId())
.openid(user.getOpenid())
.token(token)
.build();
return Result.success(vo);
}
}

ServiceImpl

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
@Service
public class UserServiceImpl implements UserService {
//微信登录接口地址
public final static String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
//注入封装微信相关属性的对象
@Autowired
WeChatProperties weChatProperties;
@Autowired
UserMapper userMapper;
@Override
public User wxlogin(UserLoginDTO userLoginDTO) {
//调用微信登录接口获取用户openid
Map<String,String> map = new HashMap<>();
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code",userLoginDTO.getCode());
map.put("grant_type","authorization_code");

String json = HttpClientUtil.doGet(WX_LOGIN, map);
String openid = JSON.parseObject(json).getString("openid");
//判断openid是否为空,为空则表示登录失败,抛出业务异常
if(openid==null){
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//判断当前用户是否为新用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getOpenid,openid);
User user = userMapper.selectOne(wrapper);
//如果是新用户则自动注册
if(user==null){
user = User.builder().openid(openid).build();
userMapper.insert(user);
}
//返回这个用户对象
return user;
}
}

HttpClientUtil

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import com.alibaba.fastjson.JSONObject;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* Http工具类
*/
public class HttpClientUtil {

static final int TIMEOUT_MSEC = 5 * 1000;

/**
* 发送GET方式请求
* @param url
* @param paramMap
* @return
*/
public static String doGet(String url,Map<String,String> paramMap){
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();

String result = "";
CloseableHttpResponse response = null;

try{
URIBuilder builder = new URIBuilder(url);
if(paramMap != null){
for (String key : paramMap.keySet()) {
builder.addParameter(key,paramMap.get(key));
}
}
URI uri = builder.build();

//创建GET请求
HttpGet httpGet = new HttpGet(uri);

//发送请求
response = httpClient.execute(httpGet);

//判断响应状态
if(response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(response.getEntity(),"UTF-8");
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
response.close();
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}

return result;
}

/**
* 发送POST方式请求
* @param url
* @param paramMap
* @return
* @throws IOException
*/
public static String doPost(String url,
Map<String, String> paramMap)
throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";

try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);

// 创建参数列表
if (paramMap != null) {
List<NameValuePair> paramList = new ArrayList();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
paramList.add(
new BasicNameValuePair(param.getKey(), param.getValue())
);
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}

httpPost.setConfig(builderRequestConfig());

// 执行http请求
response = httpClient.execute(httpPost);

resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

return resultString;
}

/**
* 发送POST方式请求
* @param url
* @param paramMap
* @return
* @throws IOException
*/
public static String doPost4Json(String url,
Map<String, String> paramMap)
throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";

try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);

if (paramMap != null) {
//构造json格式数据
JSONObject jsonObject = new JSONObject();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
jsonObject.put(param.getKey(),param.getValue());
}
StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8");
//设置请求编码
entity.setContentEncoding("utf-8");
//设置数据类型
entity.setContentType("application/json");
httpPost.setEntity(entity);
}

httpPost.setConfig(builderRequestConfig());

// 执行http请求
response = httpClient.execute(httpPost);

resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

return resultString;
}
private static RequestConfig builderRequestConfig() {
return RequestConfig.custom()
.setConnectTimeout(TIMEOUT_MSEC)
.setConnectionRequestTimeout(TIMEOUT_MSEC)
.setSocketTimeout(TIMEOUT_MSEC).build();
}

}