How to Implement WeChat QR Code Login with Spring Boot and OAuth2
This guide explains the complete WeChat OAuth2.0 QR code login flow, from requesting the authorization code and exchanging it for an access token to configuring Spring Boot beans, handling callbacks, storing tokens securely, and enforcing login checks with Spring AOP.
WeChat OAuth2.0 Authorization Login Overview
WeChat OAuth2.0 allows users to log in to third‑party applications using their WeChat identity. After user consent, the third‑party can obtain an access_token, which can be used to call WeChat Open Platform APIs to retrieve basic user information.
Authorization Flow
Third‑party initiates login request; after user authorizes, WeChat redirects with a temporary code.
Exchange the code, AppID and AppSecret for an access_token via API.
Use the access_token to call APIs and obtain user data.
Step 1 – Request CODE
Ensure the scope snsapi_login is set, then open the following URL (replace APPID, REDIRECT_URI, etc.):
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirectWeChat redirects to redirect_uri?code=CODE&state=STATE on success, or redirect_uri?state=STATE if the user denies.
Step 2 – Exchange CODE for access_token
Call:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_codeSuccessful response example:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid":"o6_bmasdasdsad6_2sgVt7hMZOPfL"
}Error example: {"errcode":40029,"errmsg":"invalid code"} Store AppSecret and tokens on the server side to avoid leakage.
Step 3 – Call APIs with access_token
Before calling, ensure the token is valid and the user has granted the required scope.
Spring Boot Configuration
Configure Open Platform credentials in application.properties:
# Open Platform
wechat.open-app-id=wx6ad144e54af67d87
wechat.open-app-secret=91a2ff6d38a2bbccfb7e9f9079108e2eDefine a configuration class to bind these properties:
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
private String mpAppId;
private String mpAppSecret;
private String mchId;
private String mchKey;
private String keyPath;
private String notifyUrl;
private String openAppId;
private String openAppSecret;
}Create beans for WxMpService:
@Configuration
public class WechatOpenConfig {
@Autowired
private WechatAccountConfig accountConfig;
@Bean
public WxMpService wxOpenService() {
WxMpService wxOpenService = new WxMpServiceImpl();
wxOpenService.setWxMpConfigStorage(wxOpenConfigStorage());
return wxOpenService;
}
@Bean
public WxMpConfigStorage wxOpenConfigStorage() {
WxMpInMemoryConfigStorage storage = new WxMpInMemoryConfigStorage();
storage.setAppId(accountConfig.getOpenAppId());
storage.setSecret(accountConfig.getOpenAppSecret());
return storage;
}
}Controller to start QR authorization and handle the callback:
@Controller
@RequestMapping("/wechat")
@Slf4j
public class WeChatController {
@Autowired
private WxMpService wxOpenService;
@GetMapping("/qrAuthorize")
public String qrAuthorize() {
String returnUrl = "http://heng.nat300.top/sell/wechat/qrUserInfo";
String url = wxOpenService.buildQrConnectUrl(returnUrl,
WxConsts.QRCONNECT_SCOPE_SNSAPI_LOGIN,
URLEncoder.encode(returnUrl));
return "redirect:" + url;
}
@GetMapping("/qrUserInfo")
public String qrUserInfo(@RequestParam("code") String code) {
WxMpOAuth2AccessToken token = wxOpenService.oauth2getAccessToken(code);
String openId = token.getOpenId();
log.info("openid={}", openId);
return "redirect:http://www.baidu.com?openid=" + openId;
}
}Login controller uses the obtained openid to create a token stored in Redis and set a cookie:
@Controller
@RequestMapping("/seller")
public class SellerUserController {
@Autowired
private SellerService sellerService;
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("/login")
public ModelAndView login(@RequestParam("openid") String openid,
HttpServletResponse response, Map<String,Object> map) {
SellerInfo sellerInfo = sellerService.findSellerInfoByOpenid(openid);
if (sellerInfo == null) {
map.put("msg", ResultEnum.LOGIN_FAIL.getMessage());
map.put("url", "/sell/seller/order/list");
return new ModelAndView("common/error");
}
String token = UUID.randomUUID().toString();
Integer expire = RedisConstant.EXPIRE;
redisTemplate.opsForValue().set(String.format(RedisConstant.TOKEN_PREFIX, token),
openid, expire, TimeUnit.SECONDS);
CookieUtil.set(response, CookieConstant.TOKEN, token, expire);
return new ModelAndView("redirect:http://heng.nat300.top/sell/seller/order/list");
}
@GetMapping("/logout")
public ModelAndView logout(HttpServletRequest request,
HttpServletResponse response, Map<String,Object> map) {
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie != null) {
redisTemplate.delete(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
CookieUtil.set(response, CookieConstant.TOKEN, null, 0);
}
map.put("msg", ResultEnum.LOGOUT_SUCCESS.getMessage());
map.put("url", "/sell/seller/order/list");
return new ModelAndView("common/success", map);
}
}Spring AOP aspect verifies login status by checking the token cookie and Redis entry, throwing SellerAuthorizeException when invalid:
@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {
@Autowired
private StringRedisTemplate redisTemplate;
@Pointcut("execution(public * com.hh.controller.Seller*.*(..)) && !execution(public * com.hh.controller.SellerUserController.*(..))")
public void verify() {}
@Before("verify()")
public void doVerify() {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attrs.getRequest();
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie == null) {
log.warn("【登录校验】Cookie中查不到token");
throw new SellerAuthorizeException();
}
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if (StringUtils.isEmpty(tokenValue)) {
log.warn("【登录校验】Redis中查不到token");
throw new SellerAuthorizeException();
}
}
}Exception handler redirects unauthenticated users back to the WeChat QR login URL:
@ControllerAdvice
public class SellExceptionHandler {
@ExceptionHandler(SellerAuthorizeException.class)
public ModelAndView handlerAuthorizeException() {
return new ModelAndView("redirect:" +
"https://open.weixin.qq.com/connect/qrconnect?" +
"appid=wx6ad144e54af67d87&redirect_uri=...&response_type=code&scope=snsapi_login&state=...");
}
// other handlers omitted for brevity
}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.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
