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:
- register
- Mailbox activation
- Improve Developer Information
- Developer qualification certification
- 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
- 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
- Add AppID and AppSecret through the code parameter, and exchange access through the API_ token
- 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:
-
Get access through code_ token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
parameter Is it necessary explain appid yes The unique identification of the application is obtained after the application submitted by wechat open platform is approved secret yes The application key AppSecret is obtained after the application submitted by wechat open platform is approved code yes Fill in the code parameters obtained in step 1 grant_type yes Fill in authorization_code -
Get two values access from the returned result_ token,openid
-
Query the database through openid to determine whether the user logs in for the first time
-
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
-
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! 😏🍭😘