Mobile Development 18 min read

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.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Master WeChat Mini Program Login: From wx.login to Secure Token Authentication

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

openId

uniquely 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaloginTokenMini ProgramWeChatOpenID
Su San Talks Tech
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.