Public platform - scan wechat QR code and log in automatically after paying attention

get ready

WeChat official account test number test
Login address: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

Interface configuration information: fill in url and token

url action 1:

The url is your interface address. When you configure it, wechat will automatically call the interface through the get request. The purpose of this step is to verify the token and obtain the random string parameter echostr it brought in the past.

url function 2:

Or this interface, when you scan the code to pay attention to the official account, it will re callback the url interface, but this time it sends post requests, and carries the parameters of xml format, and needs parsing to get openid parameters.

Therefore, the url can actually be divided into two interfaces with the same path and different request methods.

be careful:

The domain name of the url must be accessible from the Internet, or you can use intranet penetration tools, such as natapp.
There will be corresponding prompts for configuration failure and success!

The configuration is successful, as shown in the figure:

Write code

<1> Get QR Code:

  /**
   * Official account scan code login: return a two-dimensional code picture.
   */
  // Result is returned to the front-end tool class, which can be defined by yourself
  @ApiOperation(value = "official account-Code scanning login(PC end)", notes = "official account-Code scanning login(PC end)")
  @PostMapping(value = "/public/wechat/login/scan")
  public Result wechatMpLogin() throws Exception {
    Map<String, Object> data = PublicUtil.getQrCodeUrl();
    return Result.Builder.newBuilder(AppTipStatusEmum.SUCCESS_STATUS.getCode(), AppTipMsgEnum.SUCCESS.getCode()).setMessage("ok").setData(data).build();
  }

The PublicUtil tool classes involved are as follows (important):

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

// Official account tool
public class PublicUtil {

  private final static Logger log = LogManager.getLogger(PublicUtil.class);

  // Voucher acquisition (GET)
  public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";


  /**
   * Get interface access credentials
   * 
   * @return
   */
  public static Map getToken() throws Exception {

	// ThirdPartyLoginEnum is a self-defined interface, PublicWeChatEnum is a self-defined enumeration, and appid is the appid of the expression of drinking milk tea in the first picture
	// Appsecret is the appsecret for drinking milk tea in the figure above
	// HttpClientUtil is a tool class for sending http requests. JsonUtil is a tool class for bean and json conversion. You can define it yourself or download similar jar packages at night.
	
    String requestUrl = token_url.replace("APPID", ThirdPartyLoginEnum.PublicWeChatEnum.AppID.getCode())
    .replace("APPSECRET", ThirdPartyLoginEnum.PublicWeChatEnum.AppSecret.getCode());
    Map token = null;

    // Initiate a GET request to obtain credentials
    String s = HttpClientUtil.httpGet(requestUrl, null);

    if (StringUtils.isNotBlank(s)) {
      token = JsonUtil.fromJson(s, Map.class);
    }
    return token;
  }

  /**
   * Send template message appId unique ID of public account appSecret key of public account openId user ID
   * 
   * @return
   */
  public static void send_template_message(String openId, String templateId, String json, String goUrl) throws Exception {
    // Because the template I applied for needs to fill in the current time stamp, I get the current time here
    Map token = getToken();// Here is to note that if you are the official account number, you must add your ip in the background when you get token, otherwise you will report wrong when you get token.

    if (token == null) {
      log.error("fail in send:token==null");
    }

    String access_token = (String) token.get("access_token");
    String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + access_token;

    String jsonParam = json.replace("{{openid}}", openId).replace("{{templateId}}", templateId).replace("{{goUrl}}", goUrl);
    String s = HttpClientUtil.httpPost(url, jsonParam);

    if (StringUtils.isNotBlank(s)) {
      Map map = JsonUtil.fromJson(s, Map.class);
      String errcode = map.get("errcode").toString();
      if (!Objects.equals(errcode, "0") && !Objects.equals(errcode, "0.0")) {
        String errmsg = map.get("errmsg").toString();
        log.error("fail in send:errcode==" + errcode + ",errmsg==" + errmsg);
      }
    } else {
      log.error("fail in send:The template message response is null or''");
    }
    log.info("Sent successfully");
  }

  // Obtain user information through openid
  public static Map<String, Object> getUserInfoByOpenid(String openid) throws Exception {
    Map token = getToken();
    String access_tocken = (String) token.get("access_token");
    String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + access_tocken + "&openid=" + openid;
    String s = HttpClientUtil.httpGet(url, null);
    Map<String, Object> map = JsonUtil.fromJson(s, Map.class);
    return map;
  }

  // Get QR code
  public static Map<String, Object> getQrCodeUrl() throws Exception {
    Map token = PublicUtil.getToken();
    String access_token = (String) token.get("access_token");
    String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + access_token;
    String scene_str = UUID.randomUUID().toString().replaceAll("-", "") + new Date().getTime();
    String params = "{\"expire_seconds\":120, \"action_name\":\"QR_STR_SCENE\", \"action_info\":{\"scene\":{\"scene_str\":\"" + scene_str + "\"}}}";
    String s = HttpClientUtil.httpPost(url, params);
    Map<String, Object> resultMap = JsonUtil.fromJson(s, Map.class);

    Map<String, Object> data = new HashMap<>();

    if (resultMap.get("ticket") != null) {
      String qrcodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + resultMap.get("ticket");
      data.put("qrcodeUrl", qrcodeUrl);
    }
    data.put("scene_str", scene_str);

    Map<String, Object> m = new HashMap<>();
    m.put("code", ScanLoginTypeEnum.UN_LOGIN.getCode());
    m.put("key", scene_str);
    CacheUtil.set("uwa_cato" + scene_str, JsonUtil.toJson(m), 120);
    return data;
  }

  // Parsing xml
  public static Map<String, String> xmlToMap(HttpServletRequest httpServletRequest) {
    Map<String, String> map = new HashMap<String, String>();
    try {
      InputStream inputStream = httpServletRequest.getInputStream();
      SAXReader reader = new SAXReader(); // Read input stream
      org.dom4j.Document document = reader.read(inputStream);
      Element root = document.getRootElement(); // Get the xml root element
      List<Element> elementList = root.elements(); // Get all child nodes of the root element
      // Traverse all child nodes
      for (Element e : elementList)
        map.put(e.getName(), e.getText());
      // Release resources
      inputStream.close();
      inputStream = null;
      return map;
    } catch (Exception e) {
      e.getMessage();
    }
    return null;
  }
}

<2> Check whether to log in:

  // Detect login
  @GetMapping("/public/wechat/is/login")
  @ApiOperation(value = "official account-Check whether wechat logs in(PC end)", notes = "official account-Check whether wechat logs in(PC end)")
  public Result wechatMpCheckLogin(@RequestParam("scene_str") String scene_str) throws AssertException {
  	// CacheUtil is a caching tool class that encapsulates redis
    // According to scene_str queries redis to obtain the corresponding records
    // Later (0 is not logged in, 1 is concerned -- unbound, 2 is scanned - unbound, 3 is bound, and a token is sent)
    String loginInfo = CacheUtil.get("ycj" + scene_str);
    AssertException.isNull(loginInfo, AppTipMsgEnum.ERROR.getCode(), "Expired !");

    Map map = JsonUtil.fromJson(loginInfo, Map.class);
    return Result.Builder.newBuilder(AppTipStatusEmum.SUCCESS_STATUS.getCode(), AppTipMsgEnum.SUCCESS.getCode()).setMessage("ok").setData(map).build();
  }

<3> Check callback: url of the above interface configuration information interface: get request callback

  @ApiOperation(value = "Official account inspection token Callback", notes = "Official account inspection token Callback")
  @GetMapping("/public/wx/check")
  public void get(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Wechat encryption signature combines the token parameter filled in by the developer with the timestamp parameter and nonce parameter in the request.
    String signature = request.getParameter("signature");
    // time stamp
    String timestamp = request.getParameter("timestamp");
    // random number
    String nonce = request.getParameter("nonce");
    // Random string
    String echostr = request.getParameter("echostr");

    PrintWriter out = null;
    try {
      out = response.getWriter();
      // Verify the request by verifying the signature. If the verification is successful, return to echostr as it is. Otherwise, the access fails
      if (SignUtil.checkSignature(signature, timestamp, nonce)) {
        out.print(echostr);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      out.close();
      out = null;
    }
  }

The SignUtil tool classes involved here are as follows:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * Verify signature
 *
 */
public class SignUtil {
	
 
	/**
	 * Verify signature
	 * @param signature
	 * @param timestamp
	 * @param nonce
	 * @return
	 */
	public static boolean checkSignature(String signature, String timestamp, String nonce) {
		String[] arr = new String[] { "123456", timestamp, nonce };
		// Sort the token, timestamp and nonce parameters in dictionary
		Arrays.sort(arr);
		StringBuilder content = new StringBuilder();
		for (int i = 0; i < arr.length; i++) {
			content.append(arr[i]);
		}
		MessageDigest md = null;
		String tmpStr = null;
 
		try {
			md = MessageDigest.getInstance("SHA-1");
			// Splice three parameter strings into one string for sha1 encryption
			byte[] digest = md.digest(content.toString().getBytes());
			tmpStr = byteToStr(digest);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}

		// The encrypted string of sha1 can be compared with signature
		return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
	}
 
	/**
	 * Converts a byte array to a hexadecimal string
	 * 
	 * @param byteArray
	 * @return
	 */
	private static String byteToStr(byte[] byteArray) {
		String strDigest = "";
		for (int i = 0; i < byteArray.length; i++) {
			strDigest += byteToHexStr(byteArray[i]);
		}
		return strDigest;
	}
 
	/**
	 * Converts bytes to hexadecimal strings
	 * 
	 * @param mByte
	 * @return
	 */
	private static String byteToHexStr(byte mByte) {
		char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
		char[] tempArr = new char[2];
		tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
		tempArr[1] = Digit[mByte & 0X0F];
 
		String s = new String(tempArr);
		return s;
	}
}

<4> Code scanning attention callback: the url of the above interface configuration information interface: post request callback

  @ApiOperation(value = "official account-Code scanning login callback function(PC end)", notes = "official account-Code scanning login callback function(PC end)")
  @PostMapping("/public/wx/check")
  public void post(HttpServletRequest request) {
    publicThirdLoginService.publicWxCallback(request);
  }

The code of publicThirdLoginService.publicWxCallback called above is as follows:

 @Transactional(rollbackFor = Exception.class)
  public void publicWxCallback(HttpServletRequest request) {
    try {
      Map<String, String> map = PublicUtil.xmlToMap(request);

      String userOpenId = (String) map.get("FromUserName");
      String event = (String) map.get("Event");

      // Get user information
      Map<String, Object> userMap = PublicUtil.getUserInfoByOpenid(userOpenId);
      // Get QR code string
      String qrSceneStr = userMap.get("qr_scene_str") != null ? userMap.get("qr_scene_str").toString() : null;

      if ("subscribe".equals(event)) { // Click follow | subscribe
        // Write the code you need
        checkLoginInfo(request, userOpenId, userMap, qrSceneStr, "subscribe");

      } else if ("SCAN".equals(event)) { // Code scanning -- situations that have been concerned
        // Write the code you need
        checkLoginInfo(request, userOpenId, userMap, qrSceneStr, "SCAN");

      } else if ("unsubscribe".equals(event)) { // Unsubscribe | unsubscribe
        logger.info(userMap.get("nickname").toString() + "The user cancelled the attention");
      }
    } catch (Exception e) {
      logger.error("The official account WeChat has a callback error.!");
      e.printStackTrace();
    }
  }

The checkLoginInfo interfaces mentioned above are as follows (this method mainly operates its own database and other logic codes):

  @Transactional(rollbackFor = Exception.class)
  public void checkLoginInfo(HttpServletRequest request, String userOpenId, Map<String, Object> userMap, String qrSceneStr, String type) throws AssertException {
    // Have you paid attention before query
    // Check the database table UserThird (operate with your own logical code)
    UserThird userThirdParam = new UserThird();
    userThirdParam.setThirdCode(userOpenId);
    userThirdParam.setRegType(RegisterTypeEnum.WX_MP.getCode());
    UserThird userThirdSelect = userThirdService.selectByObj(userThirdParam);

    // newly added
    if (userThirdSelect == null || userThirdSelect.getId() == null) {
      UserThird userThird = new UserThird();
      userThird.setNickName(userMap.get("nickname").toString());
      userThird.setImgUrl(userMap.get("headimgurl").toString());
      userThird.setCreateTime(new Date());
	  // Omit

      if (StringUtils.isNotBlank(qrSceneStr)) {
        Map<String, Object> m = new HashMap<>();
        m.put("code", "SCAN".equals(type) ? 2 : 1);
        m.put("nickname", userMap.get("nickname").toString());
        m.put("avatar", userMap.get("headimgurl").toString());
        m.put("key", qrSceneStr);

        CacheUtil.set("uwa_cato" + qrSceneStr, JsonUtil.toJson(m), 120);
        CacheUtil.set("uwa_cato_openid" + qrSceneStr, userOpenId, 300);
      }
    } else if (userThirdSelect.getUserId() == null) { // Unbound

      Map<String, Object> m = new HashMap<>();
      m.put("code", "SCAN".equals(type) ? 2 : 1;
      m.put("nickname", userMap.get("nickname").toString());
      m.put("avatar", userMap.get("headimgurl").toString());
      m.put("key", qrSceneStr);

      CacheUtil.set("uwa_cato" + qrSceneStr, JsonUtil.toJson(m), 120);
      CacheUtil.set("uwa_cato_openid" + qrSceneStr, userOpenId, 300);
    } else {

      User user = userService.select(userThirdSelect.getUserId());
      if (user == null || user.getId() == null) { // Unbound
        Map<String, Object> m = new HashMap<>();
        m.put("code", "SCAN".equals(type) ? ScanLoginTypeEnum.SCAN_UNBIND.getCode() : ScanLoginTypeEnum.SUBSCRIBE_UNBIND.getCode());
        m.put("nickname", userMap.get("nickname").toString());
        m.put("avatar", userMap.get("headimgurl").toString());
        m.put("key", qrSceneStr);

        CacheUtil.set("uwa_cato" + qrSceneStr, JsonUtil.toJson(m), 120);
        CacheUtil.set("uwa_cato_openid" + qrSceneStr, userOpenId, 300);
      } else { // Bound
        // Judge whether the status is normal or not
        AssertException.assertException(user.getStatus() == AccountStatusEnum.NOT_ACTIVE.getCode(), AppTipMsgEnum.LOGIN_FAIL.getCode(), "Account not activated!");
        AssertException.assertException(Objects.equals(user.getIsDel(), DeleteStatusEnum.DELETED.getCode()), AppTipMsgEnum.LOGIN_FAIL.getCode(), "Account does not exist!");
        AssertException.assertException(user.getStatus() == AccountStatusEnum.BLACKLIST.getCode(), AppTipMsgEnum.LOGIN_FAIL.getCode(), "The account has been added to the blacklist, please contact the administrator!");

        // Log in successfully and issue the token
        user.setPasswd("");
        String token = commonService.createToken(user);

        Map<String, Object> m = new HashMap<>();
        m.put("code", ScanLoginTypeEnum.BIND.getCode());
        m.put("userInfo", user);
        m.put("key", qrSceneStr);
        m.put("token", token);
        CacheUtil.set("uwa_cato" + qrSceneStr, JsonUtil.toJson(m), 120);

        // Add login log
        try {
          Date now = new Date();
          UserLoginLog userLoginLog = new UserLoginLog();
          {
            userLoginLog.setLoginDate(now);
            userLoginLog.setUserId(Long.valueOf(user.getId()));
            userLoginLog.setCurrentReqIp(IpUtil.fetchAccessIp(request));
            userLoginLog.setCurrentReqIpAddr(IpToAddressUtil.getCityInfo(IpUtil.fetchAccessIp(request)));
            userLoginLog.setType(LoginStatusEnum.LOGIN.getCode());
            userLoginLog.setCreateTime(now);
          }
          userLoginLogService.insert(userLoginLog);
        } catch (Exception e) {
          logger.error("Login log error");
        }
      }
    }
  }

explain:
For the website using wechat login, no matter the wechat login on the public platform or the wechat login on the open platform, you can't get private information such as mobile phone number. If you need to bind the mobile phone number, you need to use other methods, such as scanning the code and jumping to the page binding the mobile phone number.

In addition, the code has shortcomings, welcome advice, thank you for watching!

For login to open platform, please see another article: open platform - scan wechat QR code to login

Tags: Java wechat

Posted on Wed, 27 Oct 2021 03:35:18 -0400 by MtPHP2