Goal: log in to a website with a WeChat Official Account and retrieve the user's information.
Prerequisites
- The WeChat Official Account has been verified
- You have a server that can receive callbacks from the WeChat server
Steps
1. Get the App ID and App Secret of your WeChat Official Account
2. Connect a server to receive callbacks from the WeChat server. You need a domain name, and you can use an intranet tunneling tool such as cpolar.
First, prepare the resources shown in the image
<img src="https://raw.githubusercontent.com/liyown/pic-go/master/blog/0" style="zoom:67%;" />Explanation:
The URL tells WeChat whether your server is reachable. Configure it as required — for example, the URL I use here is
JAVAhttps://13a34511.r1.cpolar.top/api/user/wxcheckSo you must have the corresponding endpoint ready on your own service.
JAVA// /user is still on the outside @GetMapping("wxcheck") public String wxCheck(@RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr) { if (userService.wxCheck(signature, timestamp, nonce, wxOpenConfig.getToken())) { return echostr; } else { return "error"; } }You have to return data in the format WeChat requires:
1) Sort the three parameters token, timestamp, and nonce lexicographically
2) Concatenate the three parameter strings into one string and encrypt it with SHA-1
3) Compare the encrypted string with the signature to confirm that the request comes from WeChat
JAVA@Override public Boolean wxCheck(String signature, String timestamp, String nonce, String token) { // WeChat Official Account access verification // Sort the three parameters token, timestamp, and nonce lexicographically String[] arr = new String[]{token, timestamp, nonce}; Arrays.sort(arr); // Concatenate the three parameter strings into one string and encrypt it with SHA-1 StringBuilder content = new StringBuilder(); for (String s : arr) { content.append(s); } String tmpStr = MYDigestUtils.sha1DigestAsHex(content.toString()); log.info("WeChat Official Account access verification, signature = {}, tmpStr = {}", signature, tmpStr); return signature.equals(tmpStr); }After submitting the form above, you will see a message confirming that verification passed.
3. Next, get the access_token
You need to return the token used to access WeChat resources, and this token is required to access resources in WeChat, such as the QR code later on
- Official documentation: Start Development / Get Access Token (qq.com)
- Code:
@Override
public String getAccessToken() {
// Get the access_token
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
wxOpenConfig.getAppId(), wxOpenConfig.getAppSecret());
// Send the request
String result = restTemplate.getForObject(url, String.class);
log.info("Getting access_token, result = {}", result);
return result;
}
4. Generate a QR Code with a Ticket
@Override
public String getQRCode(String accessToken) {
// Get the QR code
String url = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", accessToken);
// Expires after 5 minutes
String body = WXUtils.getQRCodeBody("300", "QR_STR_SCENE");
String result = restTemplate.postForObject(url, body, String.class);
log.info("Getting QR code, result = {}", result);
return result;
}
5. Return the QR code's ticket to the front end, which requests the QR code image and displays it to the user
6. Once the user scans the code, WeChat pushes the event to the URL above. It is just the GET request converted into a POST request, in this format:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[qrscene_123123]]></EventKey>
<Ticket><![CDATA[TICKET]]></Ticket>
</xml>
There are of course many event types, and you can define a strategy pattern to handle each of them separately.
@Slf4j
public class DispatchEventHandle {
private final Map<String, EventHandle> eventHandleMap = new HashMap<>();
{
eventHandleMap.put("subscribe", new SubscribeEventHandle());
eventHandleMap.put("SCAN", new ScanEventHandle());
}
public void dispatch(JSONObject jsonObject) {
String eventType = jsonObject.getJSONObject("xml").getStr(WxEventHandleConstants.EVENT_TYPE_KEY);
log.info("Event type: {}", eventType);
EventHandle eventHandle = eventHandleMap.get(eventType);
if (eventHandle == null) {
log.error("No matching event handler was found");
return;
}
eventHandle.handle(jsonObject);
}
}
The endpoint that receives the request is defined as follows: