微信小程序登录流程

概述

微信小程序登录和普通用户名密码登录不一样。用户不是把密码交给我们,而是小程序先向微信拿到一个临时code,再把code交给自己的服务端,服务端用code去微信服务器换openidsession_key

我们自己的系统不应该直接把微信session_key返回给前端。更稳的做法是:服务端确认openid以后,创建或查询本地用户,再签发自己的JWT。

小程序 wx.login
    |
    v
拿到 code
    |
    v
后端调用微信 jscode2session
    |
    v
拿到 openid / unionid / session_key
    |
    v
创建或查询本地用户
    |
    v
签发自己的 JWT

这样做以后,后续接口就不需要每次都访问微信。微信负责第一次身份入口,我们自己的JWT负责业务系统登录态。

小程序端

小程序端调用wx.login获取code,再提交给后端。

wx.login({
  success(res) {
    if (!res.code) {
      return;
    }

    wx.request({
      url: 'https://api.zhouzhou.net/auth/wechat/login',
      method: 'POST',
      data: {
        code: res.code
      },
      success(loginRes) {
        wx.setStorageSync('accessToken', loginRes.data.accessToken);
      }
    });
  }
});

后续请求带上自己的JWT:

const accessToken = wx.getStorageSync('accessToken');

wx.request({
  url: 'https://api.zhouzhou.net/api/profile',
  header: {
    Authorization: `Bearer ${accessToken}`
  }
});

小程序端只保存我们自己签发的Token,不保存微信session_key

登录请求

后端可以先定义一个登录请求对象。

public record WechatLoginRequest(
        String code
) {
}

返回值也保持简单:

public record TokenResponse(
        String accessToken,
        String tokenType,
        long expiresIn
) {
}

tokenType一般返回Bearer,前端请求接口时就能拼出标准的Authorization请求头。

微信会话对象

调用微信接口以后,会拿到openidsession_key,如果小程序绑定了开放平台,还可能拿到unionid

public record WechatSession(
        String openid,
        String unionid,
        String sessionKey
) {
}

openid是用户在当前小程序下的唯一标识,unionid是同一开放平台账号下的统一标识。只做一个小程序时,用openid就够了。如果后面要打通公众号、App、多个小程序,就要考虑unionid

调用微信接口

微信登录服务负责拿code换会话。

@Service
public class WechatMiniAppService {

    private final RestClient restClient;
    private final WechatProperties properties;

    public WechatMiniAppService(RestClient.Builder builder,
                                WechatProperties properties) {
        this.restClient = builder
                .baseUrl("https://api.weixin.qq.com")
                .build();
        this.properties = properties;
    }

    public WechatSession codeToSession(String code) {
        WechatCode2SessionResponse response = restClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/sns/jscode2session")
                        .queryParam("appid", properties.appId())
                        .queryParam("secret", properties.appSecret())
                        .queryParam("js_code", code)
                        .queryParam("grant_type", "authorization_code")
                        .build())
                .retrieve()
                .body(WechatCode2SessionResponse.class);

        if (response == null || response.errcode() != null) {
            throw new BadCredentialsException("微信登录失败");
        }

        return new WechatSession(
                response.openid(),
                response.unionid(),
                response.sessionKey()
        );
    }
}

微信返回对象可以这样定义:

public record WechatCode2SessionResponse(
        String openid,
        String unionid,
        @JsonProperty("session_key")
        String sessionKey,
        Integer errcode,
        String errmsg
) {
}

这里使用RestClient是因为新版本Spring里它比RestTemplate更适合新项目。如果项目还在Spring Boot 2,可以继续使用RestTemplateWebClient

配置对象:

@ConfigurationProperties(prefix = "app.wechat.miniapp")
public record WechatProperties(
        String appId,
        String appSecret
) {
}

配置文件:

app:
  wechat:
    miniapp:
      app-id: ${WECHAT_MINIAPP_APP_ID}
      app-secret: ${WECHAT_MINIAPP_APP_SECRET}

appSecret不要写死到仓库里。放到环境变量、配置中心或服务器密钥管理里会更安全。

OpenID登录Token

原来的设计里有一个OpenIDToken基类,这个思路可以保留。小程序类Token不应该被直接标记为已认证,它只是认证过程中的输入,最终要转换成系统自己的登录态。

public abstract class OpenIDToken extends AbstractAuthenticationToken {

    protected OpenIDToken() {
        super(null);
        super.setAuthenticated(false);
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("不能直接信任小程序登录Token");
        }
        super.setAuthenticated(false);
    }
}

微信小程序Token可以这样写:

public class WechatMiniAppAuthenticationToken extends OpenIDToken {

    private final String code;

    public WechatMiniAppAuthenticationToken(String code) {
        this.code = code;
    }

    @Override
    public Object getCredentials() {
        return code;
    }

    @Override
    public Object getPrincipal() {
        return null;
    }
}

这段Token只代表“用户提交了一个微信code”,还不代表用户已经登录。

AuthenticationProvider

真正的认证逻辑放到AuthenticationProvider中。它负责调用微信接口、查询本地用户、组装已认证的Authentication

@Component
public class WechatMiniAppAuthenticationProvider implements AuthenticationProvider {

    private final WechatMiniAppService wechatMiniAppService;
    private final UserService userService;

    public WechatMiniAppAuthenticationProvider(WechatMiniAppService wechatMiniAppService,
                                               UserService userService) {
        this.wechatMiniAppService = wechatMiniAppService;
        this.userService = userService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String code = (String) authentication.getCredentials();
        WechatSession session = wechatMiniAppService.codeToSession(code);

        LoginUser user = userService.findOrCreateByWechatOpenid(session.openid(), session.unionid());

        return new UsernamePasswordAuthenticationToken(
                user,
                null,
                user.getAuthorities()
        );
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return WechatMiniAppAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

findOrCreateByWechatOpenid是业务方法。第一次登录时创建用户,后续登录时直接查用户。如果业务需要绑定手机号,可以先创建一个半注册用户,等用户授权手机号后再补全资料。

登录接口

Controller里只做流程编排,不直接处理微信细节。

@RestController
@RequestMapping("/auth/wechat")
public class WechatAuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtTokenService jwtTokenService;

    public WechatAuthController(AuthenticationManager authenticationManager,
                                JwtTokenService jwtTokenService) {
        this.authenticationManager = authenticationManager;
        this.jwtTokenService = jwtTokenService;
    }

    @PostMapping("/login")
    public TokenResponse login(@RequestBody WechatLoginRequest request) {
        Authentication authentication = authenticationManager.authenticate(
                new WechatMiniAppAuthenticationToken(request.code())
        );

        LoginUser user = (LoginUser) authentication.getPrincipal();
        String accessToken = jwtTokenService.createAccessToken(user);

        return new TokenResponse(accessToken, "Bearer", 3600);
    }
}

这段代码和普通用户名密码登录很像。区别只是用户名密码Token换成了微信小程序Token。

配置AuthenticationManager

如果项目里有多个认证方式,可以把不同AuthenticationProvider注册进去。

@Bean
AuthenticationManager authenticationManager(WechatMiniAppAuthenticationProvider wechatProvider,
                                            DaoAuthenticationProvider daoProvider) {
    return new ProviderManager(List.of(wechatProvider, daoProvider));
}

这样用户名密码登录、小程序登录、后续可能的支付宝登录,都可以走同一套AuthenticationManager

安全配置

微信登录接口需要放行,其他业务接口仍然需要JWT。

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http,
                                        JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
    return http
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(session -> session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers(HttpMethod.POST, "/auth/wechat/login").permitAll()
                    .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
}

登录接口放行,不代表它不安全。它仍然会调用微信接口校验code,并且code本身是临时的,不能重复长期使用。

需要注意的点

微信code只能作为登录凭证,不要当用户ID保存。真正能稳定识别用户的是openidunionid

session_key不能返回给前端,也不应该放到JWT里。它是解密微信敏感数据的凭证,只应该保存在服务端。

如果需要解密手机号,要校验微信返回数据的签名和水印信息,确认appid和当前小程序一致。

如果登录接口被频繁刷,要加限流。微信接口不是你自己的数据库,外部依赖要保护好。

小结

微信小程序登录的关键点是边界清楚:微信负责确认用户在微信体系里的身份,我们自己的系统负责维护本地用户和业务Token。

不要让整个业务系统依赖微信会话。换到自己的JWT以后,后面的接口就能和普通API一样处理认证和权限。

© 舟哥 all right reserved,powered by Gitbook文件修订时间: 2026-07-16 12:52:55
作者: 舟哥
链接:https://www.b919p4.com
来源: B919P4
本文原创发布于B919P4,©著作权归作者所有,转载请注明出处,谢谢合作!

undefined

results matching ""

    No results matching ""