Master WeChat Mini Program Login: From wx.login to Secure Token Authentication
This guide walks through the complete implementation of WeChat Mini Program login, covering front‑end wx.login calls, back‑end code2Session exchanges, database schema design, API specifications, environment configuration, token validation via interceptors, and key security considerations, all illustrated with code snippets and diagrams.
Introduction
Mini program login is a common requirement; understanding the process is essential because openId and unionId are used later in the application.
Requirement Analysis
Clicking the login button should display a popup and obtain the user's phone number for authentication.
WeChat login business logic rules:
Design Idea
Refer to the official WeChat documentation: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
Official recommended login flow:
Front‑end in the mini program integrates WeChat dependencies and calls wx.login to obtain a temporary login credential (code) and passes it to the back‑end.
Back‑end calls the auth.code2Session interface to exchange the code for openId, unionId and a session key.
The server can create a custom login state based on the user identifier for subsequent front‑back interactions.
Table Structure
Create a table to store user information and the openId.
SQL statement:
CREATE TABLE "family_member" (
"id" bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
"phone" varchar(20) NOT NULL COMMENT '手机号',
"name" varchar(100) DEFAULT NULL COMMENT '名称',
"avatar" varchar(255) DEFAULT NULL COMMENT '头像',
"open_id" varchar(255) DEFAULT NULL COMMENT 'OpenID',
"gender" int DEFAULT NULL COMMENT '性别(0:男,1:女)',
"create_time" timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
"update_time" timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
"create_by" bigint DEFAULT NULL COMMENT '创建人',
"update_by" bigint DEFAULT NULL COMMENT '更新人',
"remark" varchar(255) DEFAULT NULL COMMENT '备注',
PRIMARY KEY ("id") USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='老人家属';API Specification
The API differs from typical REST endpoints; follow the WeChat developer platform flow.
Request parameters:
{
"code": "0e36jkGa1ercRF0Fu4Ia1V3fPD06jkGW", // temporary login credential code
"nickName": "微信用户",
"phoneCode": "13fe315872a4fb9ed3deee1e5909d5af60dfce7911013436fddcfe13f55ecad3"
}code : temporary login credential (valid for 5 minutes).
nickName : user nickname (currently always returns "微信用户").
phoneCode : encrypted phone data; the back‑end uses it to retrieve the phone number.
Response example:
{
"code": 200,
"msg": "操作成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"nickName": "好柿开花8915"
},
"operationTime": null
}Mini Program Environment Setup
Necessary Configuration
During testing use a test account and obtain the appId and secret from the WeChat mini‑program console. Both front‑end and back‑end need these parameters.
Basic Environment Description
Modify request paths to point to your back‑end services.
Ignore HTTPS verification locally for development.
Replace the mini‑program APPID with your test APPID.
Implementation
Implementation Idea
Controller
@PostMapping("/login")
@ApiOperation("小程序登录")
public AjaxResult login(@RequestBody UserLoginRequestDto userLoginRequestDto){
LoginVo loginVo = familyMemberService.login(userLoginRequestDto);
return success(loginVo);
}DTO definition:
package com.zzyl.nursing.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/** C端用户登录 */
@Data
public class UserLoginRequestDto {
@ApiModelProperty("昵称")
private String nickName;
@ApiModelProperty("登录临时凭证")
private String code;
@ApiModelProperty("手机号临时凭证")
private String phoneCode;
}VO definition:
package com.zzyl.nursing.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/** LoginVO */
@Data
@ApiModel(value = "登录对象")
public class LoginVo {
@ApiModelProperty(value = "JWT token")
private String token;
@ApiModelProperty(value = "昵称")
private String nickName;
}Business Layer (Important)
Encapsulate third‑party calls to make them reusable.
Obtain user openId.
Obtain phone number.
Obtain token (required for phone number retrieval).
WeChat Service Interface
package com.zzyl.nursing.service;
public interface WechatService {
/** Get openid from code */
String getOpenid(String code);
/** Get phone number from detail code */
String getPhone(String detailCode);
}WeChat Service Implementation
package com.zzyl.nursing.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzyl.nursing.service.WechatService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class WechatServiceImpl implements WechatService {
private static final String REQUEST_URL = "https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code";
private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
private static final String PHONE_REQUEST_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=";
@Value("${wechat.appId}")
private String appid;
@Value("${wechat.appSecret}")
private String secret;
@Override
public String getOpenid(String code) {
Map<String, Object> paramMap = getAppConfig();
paramMap.put("js_code", code);
String result = HttpUtil.get(REQUEST_URL, paramMap);
JSONObject jsonObject = JSONUtil.parseObj(result);
if (ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getStr("openid");
}
private Map<String, Object> getAppConfig() {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("appid", appid);
paramMap.put("secret", secret);
return paramMap;
}
@Override
public String getPhone(String detailCode) {
String token = getToken();
String url = PHONE_REQUEST_URL + token;
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("code", detailCode);
String result = HttpUtil.post(url, JSONUtil.toJsonStr(paramMap));
JSONObject jsonObject = JSONUtil.parseObj(result);
if (jsonObject.getInt("errcode") != 0) {
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getJSONObject("phone_info").getStr("phoneNumber");
}
private String getToken() {
Map<String, Object> paramMap = getAppConfig();
String result = HttpUtil.get(TOKEN_URL, paramMap);
JSONObject jsonObject = JSONUtil.parseObj(result);
if (ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getStr("access_token");
}
}Configuration in application.yml for appId and appSecret:
WeChat Login Business Development
/**
* 微信登录
* @param userLoginRequestDto
* @return
*/
LoginVo login(UserLoginRequestDto userLoginRequestDto);Implementation details:
@Autowired
private WechatService wechatService;
@Autowired
private TokenService tokenService;
@Override
public LoginVo login(UserLoginRequestDto dto) {
// 1. Get openId from WeChat API
String openId = wechatService.getOpenid(dto.getCode());
// 2. Query or create user record
FamilyMember member = getOne(Wrappers.<FamilyMember>lambdaQuery().eq(FamilyMember::getOpenId, openId));
if (ObjectUtil.isEmpty(member)) {
member = FamilyMember.builder().openId(openId).build();
}
// 3. Get phone number
String phone = wechatService.getPhone(dto.getPhoneCode());
// 4. Save or update user
saveOrUpdateFamilyMember(member, phone);
// 5. Generate JWT token
Map<String, Object> claims = new HashMap<>();
claims.put("userId", member.getId());
claims.put("userName", member.getName());
String token = tokenService.createToken(claims);
LoginVo vo = new LoginVo();
vo.setToken(token);
vo.setNickName(member.getName());
return vo;
}Helper method to save or update the family member:
private void saveOrUpdateFamilyMember(FamilyMember member, String phone) {
if (!ObjectUtil.equals(phone, member.getPhone())) {
member.setPhone(phone);
}
if (ObjectUtil.isNotEmpty(member.getId())) {
updateById(member);
return;
}
String nickName = DEFAULT_NICKNAME_PREFIX.get((int) (Math.random() * DEFAULT_NICKNAME_PREFIX.size()))
+ StringUtils.substring(member.getPhone(), 7);
member.setName(nickName);
save(member);
}Token Validation
After a successful login, the token is returned to the front‑end and attached to subsequent requests via the Authorization header.
Interceptor to validate the token:
package com.zzyl.framework.interceptor;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zzyl.common.exception.base.BaseException;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@Component
public class MemberInterceptor implements HandlerInterceptor {
@Autowired
private TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
String token = request.getHeader("authorization");
if (StringUtils.isEmpty(token)) {
throw new BaseException("认证失败");
}
Map<String, Object> claims = tokenService.parseToken(token);
if (ObjectUtil.isEmpty(claims)) {
throw new BaseException("认证失败");
}
Long userId = MapUtil.get(claims, "userId", Long.class);
if (ObjectUtil.isEmpty(userId)) {
throw new BaseException("认证失败");
}
UserThreadLocal.set(userId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}
}Register the interceptor in WebMvcConfigurer:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(memberInterceptor)
.excludePathPatterns(EXCLUDE_PATH_PATTERNS)
.addPathPatterns("/member/**");
}Summary
openIduniquely identifies a user within this mini program; unionId is the unique identifier across the entire WeChat platform.
The front‑end calls wx.login to obtain a temporary code, which the back‑end exchanges for openId.
Retrieving a phone number requires first obtaining an access token, then calling the phone‑number API.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
