1, Preparatory work
https://open.weixin.qq.com
1. Register
2. Mailbox activation
3. Improve Developer Information
4. Developer qualification certification
Prepare business license, approve within 1-2 working days, RMB 300
5. Create web app
Submit for review and approval within 7 working days
6. Familiar with wechat login process
Reference documents: https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN
Get access_token sequence diagram
Note: individual developers cannot apply. They must have a business license. If they can't use it, consider using the wechat public platform. I use someone else's appid and key below.
2, Wechat login backend development
2.1 add configuration
application.properties configuration file:
# 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://Your server name / api/ucenter/wx/callback
2.2 create constant class and constant propertiesutil.java constant class
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * Read the relevant configuration of wechat login */ @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; } }
2.3 create controller
WxApiController
import com.atguigu.commonutils.JwtUtils; import com.atguigu.educenter.entity.UcenterMember; import com.atguigu.educenter.service.UcenterMemberService; import com.atguigu.educenter.utils.ConstantWxUtils; import com.atguigu.educenter.utils.HttpClientUtils; import com.atguigu.servicebase.exceptionhandler.GuliException; import com.google.gson.Gson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; @Controller //Only the request address does not need to return data, and RestController is not required @RequestMapping("/api/ucenter/wx") @CrossOrigin public class WxApiController { //1. Generate QR code scanned by wechat @GetMapping("login") public String getWxCode(){ //Fixed address, back splicing parameters // Wechat open platform authorization baseurl% s is equivalent to? placeholder 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 redirect_url=ConstantWxUtils.WX_OPEN_REDIRECT_URL; try { redirect_url = URLEncoder.encode(redirect_url, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //Set the value in% s String url = String.format(baseUrl, ConstantWxUtils.WX_OPEN_APP_ID, redirect_url, "atguigu" ); //Redirect to the requested wechat address return "redirect:"+url; } }
Authorization url parameter description
parameter | Is it necessary | explain |
---|---|---|
appid | yes | Apply unique identification |
redirect_uri | yes | Please use urlEncode to process the link |
response_type | yes | Fill in code |
scope | yes | The application authorization scope has multiple scopes separated by commas (,), and the web application only fills in snsapi at present_ login |
state | yes | It is used to maintain the status of request and callback. After authorization request, it is brought back to the third party as is. This parameter can be used to prevent csrf attack (Cross Site Request Forgery Attack). It is recommended that the third party take this parameter, which can be set as a simple random number plus session for verification |
2.4 testing
visit: http://localhost:8160/api/ucenter/wx/login
The interface after the mobile phone scans the QR code is as follows
It will also be displayed on the web page
3, Get the information of wechat scanner
3.1 test whether callback is available
The callback url has been set in the login controller, as shown in the following figure
After scanning, execute the local callback method, obtain two values in the callback, and pass them in the jump.
state: pass as is
Code: similar to mobile phone verification code, random unique value (temporary bill)
//2. Obtain scanner information and add data @GetMapping("callback") public String callback(String code,String state){ //Authorized temporary note code System.out.println("code = " + code); System.out.println("state = " + state); }
3.2 adding dependencies
The reason why the version is not specified here is that I have specified it in the parent project. I develop a micro service project according to your own situation.
<!--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>
3.3 add httpclient tool class
httpclient can achieve the same effect as browser access mainly through java code. We mainly use this to call the interface officially provided by wechat.
import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.Consts; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import java.io.IOException; import java.net.SocketTimeoutException; import java.security.GeneralSecurityException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * 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(); } } }
3.4 create callback controller method
1. The general idea of callback is to take the code value obtained by scanning login above and request the address provided by wechat https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_ type=authorization_ Code gets two values access_token and openid
For this, please refer to the wechat official website document https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
2. Then take the access obtained above_ Token and openid, and then request the address provided by wechat https://api.weixin.qq.com/sns/userinfo?access_token =% S & openid =% s,% s is my placeholder. You can modify it according to your own situation. Here you can get the information of wechat scanner, such as wechat nickname, wechat avatar, openid, etc.
Add the following method in WxApiController.java
@Autowired private UcenterMemberService memberService; //2. Obtain scanner information and add data @GetMapping("callback") public String callback(String code,String state){ try { //1. Get code value, temporary bill, similar to verification code //2. Take the code to request the fixed address of wechat and get two values access_token and openid 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 ); //Request the spliced address and get two values access_token and openid //Send the request using httpclient and get the returned result String accessTokenInfo = HttpClientUtils.get(accessTokenUrl); // System.out.println("accessTokenInfo:"+accessTokenInfo); //Get access from the accessTokenInfo string_ Token and openid //Convert the accessTokenInfo string into a map set, and obtain the corresponding value according to the key in the map //Use the json conversion tool Gson Gson gson=new Gson(); HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class); String access_token = (String) mapAccessToken.get("access_token"); String openid = (String) mapAccessToken.get("openid"); //Add the code scanner information to the database //Judge whether the same wechat information exists in the database according to openid UcenterMember member = memberService.getOpenIdMember(openid); if(member ==null){ //If member is empty, it means that there is no same wechat data in the table to add //3. Take the access you get_ Token and openid, and then request the fixed address provided by wechat to obtain the information of the code scanner //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 String userInfo = HttpClientUtils.get(userInfoUrl); // System.out.println("userInfo:"+userInfo); //Get and return the user information in userInfo (code scanner information) HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class); String nickname = (String) userInfoMap.get("nickname"); //nickname String headimgurl = (String) userInfoMap.get("headimgurl"); //head portrait member=new UcenterMember(); member.setOpenid(openid); member.setNickname(nickname); member.setAvatar(headimgurl); memberService.save(member); //add to } //Using jwt to generate token string from 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,"Login failed"); } }
the above also makes a judgment. If it is the first login, the information will be added to the database.
3.5 front end display scanner information
in fact, the work of the back-end developers has been basically completed here, but if we want to display it on the front end, we'd better use jwt according to the wechat information to generate the token string and pass the token string to the home page through the path.
The code in the previous step already contains this step.
//Using jwt to generate token string from 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;
3.6 front end display effect after login
I won't release the front-end code. You can achieve the desired effect according to your own needs. After I log in, I log in to the home page through callback, and display the information (avatar, nickname, etc.) in the upper right.