Implementing WeChat QR Code Login with Spring Boot and OAuth2
This article explains how to integrate WeChat QR code login using OAuth2.0 in a Spring Boot backend, covering the authorization flow, required configuration, Java code examples for obtaining the code and access token, user login handling, and AOP-based login verification.
1. Authorization Process Overview
WeChat OAuth2.0 login enables users to securely log in to third‑party applications using their WeChat identity. The flow follows the authorization_code grant type and consists of three main steps: requesting a temporary code, exchanging the code for an access_token, and using the token to call protected APIs.
Step 1: Request CODE
Redirect the user to the WeChat QR connect URL with the appropriate appid, redirect_uri, scope=snsapi_login and state. After the user authorizes, WeChat redirects back to the provided redirect_uri with code and state parameters; if the user denies authorization, only state is returned.
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirectSuccessful redirect example: redirect_uri?code=CODE&state=STATE Denied example:
redirect_uri?state=STATEStep 2: Exchange CODE for access_token
Call the WeChat API to exchange the received code for an access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_codeSuccessful JSON response:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid":"o6_bmasdasdsad6_2sgVt7hMZOPfL"
}Error response example: {"errcode":40029,"errmsg":"invalid code"} Important security notes: AppSecret, access_token, and refresh_token should be stored on the server side to avoid leakage.
Step 3: Use access_token to Call APIs
After obtaining a valid access_token, you can call WeChat Open Platform APIs provided the user has granted the required scopes.
2. Authorization Flow Code
Configure the Open Platform credentials in application.properties (or equivalent):
# 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 {
// public account appid
private String mpAppId;
// public account appSecret
private String mpAppSecret;
// merchant id
private String mchId;
// merchant secret key
private String mchKey;
// merchant certificate path
private String keyPath;
// async notify URL
private String notifyUrl;
// open platform appid
private String openAppId;
// open platform secret
private String openAppSecret;
}Create a Spring bean that builds the WeChat service using the above configuration:
@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 handling QR login and callback:
@Controller
@RequestMapping("/wechat")
@Slf4j
public class WeChatController {
@Autowired
private WxMpService wxMpService;
@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 = new WxMpOAuth2AccessToken();
try {
token = wxOpenService.oauth2getAccessToken(code);
} catch (WxErrorException e) {
log.error("【WeChat Web Authorization】{}", e);
throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
}
String openId = token.getOpenId();
String returnUrl = "http://heng.nat300.top/sell/seller/login";
log.info("openid={}", openId);
return "redirect:" + returnUrl + "?openid=" + openId;
}
}3. User Login and Logout
Login controller that receives the openid, creates a token, stores it in Redis, and writes a cookie:
@Controller
@RequestMapping("/seller")
public class SellerUserController {
@Autowired
private SellerService sellerService;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ProjectUrlConfig projectUrlConfig;
@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.opsForValue().getOperations().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);
}
}4. Spring AOP Login Verification
@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 attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie == null) {
log.warn("[Login verification] Token not found in cookie");
throw new SellerAuthorizeException();
}
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if (StringUtils.isEmpty(tokenValue)) {
log.warn("[Login verification] Token not found in Redis");
throw new SellerAuthorizeException();
}
}
}5. Exception Handler for Unauthorized Access
@ControllerAdvice
public class SellExceptionHandler {
@ExceptionHandler(value = SellerAuthorizeException.class)
public ModelAndView handlerAuthorizeException() {
return new ModelAndView("redirect:".concat(
"https://open.weixin.qq.com/connect/qrconnect?" +
"appid=wx6ad144e54af67d87" +
"&redirect_uri=http%3A%2F%2Fsell.springboot.cn%2Fsell%2Fqr%2F" +
"oTgZpwenC6lwO2eTDDf_-UYyFtqI" +
"&response_type=code&scope=snsapi_login" +
"&state=http%3a%2f%2fheng.nat300.top%2fsell%2fwechat%2fqrUserInfo"));
}
@ExceptionHandler(value = SellException.class)
@ResponseBody
public ResultVO handlerSellerException(SellException e) {
return ResultVOUtil.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = ResponseBankException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public void handleResponseBankException() {}
}The article concludes with a call to share the content and join the architecture community for further learning.
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 Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.
