SpringBoot integrates wechat login

Posted by misterguru on Thu, 23 Dec 2021 23:53:53 +0100

SpringBoot integrates wechat login

1. Preparation

1.1 obtain wechat login certificate

Go to the official website Wechat open platform (qq.com) , complete the following steps:

  1. register
  2. Mailbox activation
  3. Improve Developer Information
  4. Developer qualification certification
  5. Create web app

1.2 configuration file

In the configuration file application Add relevant configuration information to properties:

# Wechat open platform appid
wx.open.app_id=Yours appid
# Wechat open platform appsecret
wx.open.app_secret=Yours appsecret
# Wechat open platform redirection url
wx.open.redirect_url=http://81/api/ucenter/wx/callback

1.3 adding dependencies

<!--httpclient-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<!--commons-io-->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
</dependency>
<!--gson-->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>

1.4 create a tool class to read public constants

Create a tool class ConstantWxUtils that reads public constants:

/**
 * @author xppll
 * @date 2021/12/11 14:39
 */
@Component
public class ConstantWxUtils implements InitializingBean {


    @Value("${wx.open.app_id}")
    private String appId;
    @Value("${wx.open.app_secret}")
    private String appSecret;
    @Value("${wx.open.redirect_url}")
    private String redirectUrl;
    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
        WX_OPEN_APP_ID = appId;
        WX_OPEN_APP_SECRET = appSecret;
        WX_OPEN_REDIRECT_URL = redirectUrl;
    }
}

1.5 HttpClient tool class

/**
 *  The dependent jar packages are: commons-lang-2.6 jar,httpclient-4.3.2.jar,httpcore-4.3.1.jar,commons-io-2.4.jar
 * @author zhaoyb
 *
 */
public class HttpClientUtils {

   public static final int connTimeout=10000;
   public static final int readTimeout=10000;
   public static final String charset="UTF-8";
   private static HttpClient client = null;

   static {
      PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
      cm.setMaxTotal(128);
      cm.setDefaultMaxPerRoute(128);
      client = HttpClients.custom().setConnectionManager(cm).build();
   }

   public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
      return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
   }

   public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
      return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
   }

   public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
         SocketTimeoutException, Exception {
      return postForm(url, params, null, connTimeout, readTimeout);
   }

   public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
         SocketTimeoutException, Exception {
      return postForm(url, params, null, connTimeout, readTimeout);
   }

   public static String get(String url) throws Exception {
      return get(url, charset, null, null);
   }

   public static String get(String url, String charset) throws Exception {
      return get(url, charset, connTimeout, readTimeout);
   }

   /**
    * Send a Post request using the specified character set encoding
    *
    * @param url
    * @param body RequestBody
    * @param mimeType For example, application / XML "application / x-www-form-urlencoded" a = 1 & B = 2 & C = 3
    * @param charset code
    * @param connTimeout Link establishment timeout, Ms
    * @param readTimeout Response timeout, Ms
    * @return ResponseBody, Use the specified character set encoding
    * @throws ConnectTimeoutException Link establishment timeout exception
    * @throws SocketTimeoutException  Response timeout
    * @throws Exception
    */
   public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
         throws ConnectTimeoutException, SocketTimeoutException, Exception {
      HttpClient client = null;
      HttpPost post = new HttpPost(url);
      String result = "";
      try {
         if (StringUtils.isNotBlank(body)) {
            HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
            post.setEntity(entity);
         }
         // Set parameters
         Builder customReqConf = RequestConfig.custom();
         if (connTimeout != null) {
            customReqConf.setConnectTimeout(connTimeout);
         }
         if (readTimeout != null) {
            customReqConf.setSocketTimeout(readTimeout);
         }
         post.setConfig(customReqConf.build());

         HttpResponse res;
         if (url.startsWith("https")) {
            // Execute HTTP requests
            client = createSSLInsecureClient();
            res = client.execute(post);
         } else {
            // Execute Http request
            client = HttpClientUtils.client;
            res = client.execute(post);
         }
         result = IOUtils.toString(res.getEntity().getContent(), charset);
      } finally {
         post.releaseConnection();
         if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
            ((CloseableHttpClient) client).close();
         }
      }
      return result;
   }


   /**
    * Submit form
    *
    * @param url
    * @param params
    * @param connTimeout
    * @param readTimeout
    * @return
    * @throws ConnectTimeoutException
    * @throws SocketTimeoutException
    * @throws Exception
    */
   public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
         SocketTimeoutException, Exception {

      HttpClient client = null;
      HttpPost post = new HttpPost(url);
      try {
         if (params != null && !params.isEmpty()) {
            List<NameValuePair> formParams = new ArrayList<NameValuePair>();
            Set<Entry<String, String>> entrySet = params.entrySet();
            for (Entry<String, String> entry : entrySet) {
               formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
            post.setEntity(entity);
         }

         if (headers != null && !headers.isEmpty()) {
            for (Entry<String, String> entry : headers.entrySet()) {
               post.addHeader(entry.getKey(), entry.getValue());
            }
         }
         // Set parameters
         Builder customReqConf = RequestConfig.custom();
         if (connTimeout != null) {
            customReqConf.setConnectTimeout(connTimeout);
         }
         if (readTimeout != null) {
            customReqConf.setSocketTimeout(readTimeout);
         }
         post.setConfig(customReqConf.build());
         HttpResponse res = null;
         if (url.startsWith("https")) {
            // Execute HTTP requests
            client = createSSLInsecureClient();
            res = client.execute(post);
         } else {
            // Execute Http request
            client = HttpClientUtils.client;
            res = client.execute(post);
         }
         return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
      } finally {
         post.releaseConnection();
         if (url.startsWith("https") && client != null
               && client instanceof CloseableHttpClient) {
            ((CloseableHttpClient) client).close();
         }
      }
   }




   /**
    * Send a GET request
    *
    * @param url
    * @param charset
    * @param connTimeout  Link establishment timeout, Ms
    * @param readTimeout  Response timeout, Ms
    * @return
    * @throws ConnectTimeoutException   Link establishment timeout
    * @throws SocketTimeoutException   Response timeout
    * @throws Exception
    */
   public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
         throws ConnectTimeoutException,SocketTimeoutException, Exception {

      HttpClient client = null;
      HttpGet get = new HttpGet(url);
      String result = "";
      try {
         // Set parameters
         Builder customReqConf = RequestConfig.custom();
         if (connTimeout != null) {
            customReqConf.setConnectTimeout(connTimeout);
         }
         if (readTimeout != null) {
            customReqConf.setSocketTimeout(readTimeout);
         }
         get.setConfig(customReqConf.build());

         HttpResponse res = null;

         if (url.startsWith("https")) {
            // Execute HTTP requests
            client = createSSLInsecureClient();
            res = client.execute(get);
         } else {
            // Execute Http request
            client = HttpClientUtils.client;
            res = client.execute(get);
         }

         result = IOUtils.toString(res.getEntity().getContent(), charset);
      } finally {
         get.releaseConnection();
         if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
            ((CloseableHttpClient) client).close();
         }
      }
      return result;
   }


   /**
    * Get charset from response
    *
    * @param ressponse
    * @return
    */
   @SuppressWarnings("unused")
   private static String getCharsetFromResponse(HttpResponse ressponse) {
      // Content-Type:text/html; charset=GBK
      if (ressponse.getEntity() != null  && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
         String contentType = ressponse.getEntity().getContentType().getValue();
         if (contentType.contains("charset=")) {
            return contentType.substring(contentType.indexOf("charset=") + 8);
         }
      }
      return null;
   }



   /**
    * Create SSL connection
    * @return
    * @throws GeneralSecurityException
    */
   private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
      try {
         SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
            public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
               return true;
            }
         }).build();

         SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

            @Override
            public boolean verify(String arg0, SSLSession arg1) {
               return true;
            }

            @Override
            public void verify(String host, SSLSocket ssl)
                  throws IOException {
            }

            @Override
            public void verify(String host, X509Certificate cert)
                  throws SSLException {
            }

            @Override
            public void verify(String host, String[] cns,
                           String[] subjectAlts) throws SSLException {
            }

         });

         return HttpClients.custom().setSSLSocketFactory(sslsf).build();

      } catch (GeneralSecurityException e) {
         throw e;
      }
   }

   public static void main(String[] args) {
      try {
         String str= post("https://localhost:443/ssl/test.shtml","name=12&page=34","application/x-www-form-urlencoded", "UTF-8", 10000, 10000);
         //String str= get("https://localhost:443/ssl/test.shtml?name=12&page=34","GBK");
            /*Map<String,String> map = new HashMap<String,String>();
            map.put("name", "111");
            map.put("page", "222");
            String str= postForm("https://localhost:443/ssl/test.shtml",map,null, 10000, 10000);*/
         System.out.println(str);
      } catch (ConnectTimeoutException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      } catch (SocketTimeoutException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      } catch (Exception e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
   }

}

2. Realize wechat login

Please refer to the official document: [website application wechat login development guide]( Preparatory work | wechat open document (qq.com))

2.1 specific process

  1. The third party initiates a wechat authorization login request. After the wechat user allows the authorization of the third-party application, wechat will pull up the application or redirect to the third-party website, and bring the code parameter of the authorization temporary bill
  2. Add AppID and AppSecret through the code parameter, and exchange access through the API_ token
  3. Through access_token to call the interface to obtain the user's basic data resources or help the user realize basic operations

Get access_token sequence diagram:

2.2 generate QR CODE scanned by wechat (request CODE)

controller layer:

/**
 * @author xppll
 * @date 2021/12/11 14:48
 */
@CrossOrigin
@Controller
@RequestMapping("/api/ucenter/wx")
public class WxApiController {
    @Autowired
    private UcenterMemberService memberService;

    /**
     * Generate wechat scanning QR code
     * @return Directed to the requested wechat address
     */
    @GetMapping("login")
    public String getWxCode() {

        //Wechat open platform authorization baseUrl
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
            "?appid=%s" +
            "&redirect_uri=%s" +
            "&response_type=code" +
            "&scope=snsapi_login" +
            "&state=%s" +
            "#wechat_redirect";


        //Yes, redirect_url encoded by URLEncoder
        String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new GuliException(20001, e.getMessage());
        }
        //Set value for% s
        String url = String.format(
            baseUrl,
            ConstantWxUtils.WX_OPEN_APP_ID,
            redirectUrl,
            "atguigu"
        );

        //Redirect to the requested wechat address
        return "redirect:" + url;
    }

}

visit: http://localhost:8160/api/ucenter/wx/login

After accessing the authorization url, you will get a wechat login QR Code:

After scanning the QR code, the user will see the login confirmation page:

After the user clicks "confirm login", the wechat server will initiate a callback to the business server of grain college, so next we need to develop a callback controller

2.3 callback

There are several steps:

  1. Get access through code_ token

    https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
    
    parameterIs it necessaryexplain
    appidyesThe unique identification of the application is obtained after the application submitted by wechat open platform is approved
    secretyesThe application key AppSecret is obtained after the application submitted by wechat open platform is approved
    codeyesFill in the code parameters obtained in step 1
    grant_typeyesFill in authorization_code
  2. Get two values access from the returned result_ token,openid

  3. Query the database through openid to determine whether the user logs in for the first time

  4. If it is the first time to log in, according to access_token and openid then access the wechat resource server to obtain user information and store it in the database

  5. Use jwt to generate the token string according to the member object, and finally return to the home page to pass the token string through the path

/**
 * Obtain scanner information and add data
 * @param code Similar to mobile phone verification code, random unique value
 * @param state It is used to maintain the status of request and callback. After authorization request, it is brought back to the third party as is
 * @return
 */
@GetMapping("callback")
public String callback(String code, String state) {
    try {
        //Get the code value. The temporary ticket is similar to the verification code
        //Take the code to request the fixed address of wechat and get two values
        //1. Send a request to the authentication server in exchange for access_token
        String baseAccessTokenUrl =
                "https://api.weixin.qq.com/sns/oauth2/access_token" +
                        "?appid=%s" +
                        "&secret=%s" +
                        "&code=%s" +
                        "&grant_type=authorization_code";
        //Splice three parameters: id key and code value
        String accessTokenUrl = String.format(
                baseAccessTokenUrl,
                ConstantWxUtils.WX_OPEN_APP_ID,
                ConstantWxUtils.WX_OPEN_APP_SECRET,
                code
        );
        //2. Request the spliced address and get the two returned values access_token and openid
        //Send the request using httpclient and get the returned result (string in json form)
        String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
        //Get two values from the accessTokenInfo string_ token,openid
        //Convert the accessTokenInfo string into a map set, and obtain the value according to the key in the map
        //The json conversion tool Gson is used here
        Gson gson = new Gson();
        HashMap accessTokenMap = gson.fromJson(accessTokenInfo, HashMap.class);
        String access_token = (String) accessTokenMap.get("access_token");
        String openid = (String) accessTokenMap.get("openid");

        //3. Judge whether the user is logged in for the first time
        //Judging by openid
        UcenterMember member = memberService.getOpenIdMember(openid);
        //4. Only the first login can obtain information
        if (member == null) {
            //According to access_token and openid then access the wechat resource server to obtain user information
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                    "?access_token=%s" +
                    "&openid=%s";
            String userInfoUrl = String.format(
                    baseUserInfoUrl,
                    access_token,
                    openid
            );
            //Send request to get user information
            String userInfo = HttpClientUtils.get(userInfoUrl);
            System.out.println(userInfo);
            //Store user information in database
            //Convert json to map
            HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
            //Get nickname
            String nickname = (String) userInfoMap.get("nickname");
            //Get the wechat avatar
            String headimgurl = (String) userInfoMap.get("headimgurl");

            member = new UcenterMember();
            member.setOpenid(openid);
            member.setNickname(nickname);
            member.setAvatar(headimgurl);
            memberService.save(member);
        }
        //5. Use jwt to generate token string according to member object
        String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());

        //Finally, return to the home page and pass the token string through the path
        return "redirect:http://localhost:3000?token=" + jwtToken;
    } catch (Exception e) {
        throw new GuliException(20001, "Wechat login failed");
    }
}

Judge whether the user is logged in for the first time by scanning the code:

/**
 * Query whether there are users according to openid
 * @param openid Unique ID of authorized user
 * @return UcenterMember
 */
@Override
public UcenterMember getOpenIdMember(String openid) {
    LambdaQueryWrapper<UcenterMember> queryWrapper=new LambdaQueryWrapper<>();
    queryWrapper.eq(UcenterMember::getOpenid,openid);
    UcenterMember member = baseMapper.selectOne(queryWrapper);
    return member;
}


Last favorite little partner, remember the triple! 😏🍭😘

Topics: Java Spring Boot Project