WeChat Login under SSM Framework

typora-root-url: D:\Programming Projecttypora Document\Picture Directory

WeChat Login under SSM Framework

First look at the official login process, then follow the steps one by one

1. Get CODE

First get the code using the method provided by WeChat, use the wx.login() method, and also encapsulate a method to initiate WeChat requests.

This encapsulates two methods that execute asynchronously when calling login

//Get code exchange session
  getCode(){
    var that=this;
    //callback
    return new Promise(function(resolve, reject){
      wx.login({
          success(res) {
              console.log('code:'+res.code);
              //Save the acquired code to local data for sending to the server
              that.data.loginData.code = res.code;
              resolve();
          }
      })
    })
  },
  //WeChat Request Encapsulation
  getRequest(url,data){
    var that=this;
    return new Promise(function(resolve,reject){
      wx.request({
        url: 'http://localhost/BookKeeping/'+url,
        //Request data
        data: data,
        method: 'POST',
        //Data carried with token validation (not needed at this time)
        header: {
          Authorization: that.data.token
        }, 
        success: function(res){
          resolve(res)
        },
        fail: function() {
          reject()
        },
        complete: function() {
          //complete
        }
      })
    })
  },
//This allows the call to emit the code
  userLogin(e){
    console.log(e);
    var that=this;
    this.getCode()
    .then(()=>{
      console.log(this.data.loginData);
      return this.getRequest("api/login",this.data.loginData.code)           
    })
  }

Let's leave it to the server

2. Server handles the Code sent

When the server is processing, we need a tool to send our request data to the WeChat server

The requested data is as follows

The requested address is

GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

How does the backend send this request?

Before that, you need to import maven dependencies to support the package that originated the request

<!--Https Request Dependency-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5.3</version>
        </dependency>

Under the original ssm framework, create a new package to store our tool classes

This time you're using the tool that sends http requests, HttpUtil.java

package com.BookKeeping.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpUtil {

    public static String doGet(String uri) {
        //1: Create an instance of HttpClient
        CloseableHttpClient httpclient = HttpClients.createDefault();
        //2: Create a get request instance
        HttpGet httpGet = new HttpGet(uri);
        //Response to the request:
        CloseableHttpResponse response1 = null;
        try {
            //3: Use an instance of HttpClient to execute get requests
            response1 = httpclient.execute(httpGet);
            //Status of http requests: 404 500 200
            //System.out.println(response1.getStatusLine());
            int statusCode = response1.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                //Request succeeded:
                HttpEntity entity1 = response1.getEntity();
                String result = EntityUtils.toString(entity1, "utf-8");
                return result;
            } else {
                //request was aborted
                System.out.println("request was aborted......:"+statusCode);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public JSONObject domain(String type, String code){
        JSONObject jo = JSONObject.parseObject("{'message':'Error',}");
        if(type.equals("getSession_key")){
            System.out.println("Obtain session and openid");
            String result=doGet("https://api.weixin.qq.com/sns/jscode2session?appid=own appid&secret=own secret&js_code="+code+"&grant_type=authorization_code");
            jo = JSONObject.parseObject(result);
            return jo;
        }else if(type.equals("getUserData")){
            System.out.println("Get user information");
        }
        return jo;
    }
}

The domain method encapsulated above can return a request result as per the incoming method and code (because the result returned by WeChat is a json format)

Here we turn the returned result directly to json

Example request results:

3. Controller Layer Processing

Paste code directly

    @RequestMapping(value = "/login",method = RequestMethod.POST)
    @ResponseBody				//Make the returned data json format
    public Result login(@RequestBody String code){
        Result rs=new Result();	 //Customized Result Return Value (see previous article in more detail)
        Login result=new Login();//Entity class for login data, store login information, etc.
        
        System.out.println(code);
        //Get WeChat session and generate custom token
        HttpUtil hrs=new HttpUtil();

        //Get session_key and openid
        JSONObject session_key=hrs.domain("getSession_key",code);
        String session=session_key.getString("session_key");
        String openid=session_key.getString("openid");

        result.setSession(session);
	   //Put parameters in
        rs.setData(result);
        return rs;
    }

Login's entity class, note that right-click Generate automatically generates get and set and tostring, as detailed in the previous section

package com.BookKeeping.entity;

public class Login {
    private String encryptedData;

    private String iv;

    private String code;

    private String token;

    private String session;
}

Here we can test this interface in the WeChat Developer Tool:

Note that session s here have to be saved. WeChat officially says they have a 20-minute expiration date, which will be used later to decrypt data.

4. Get the user's encrypted data

To obtain user encrypted data, the following data is required:

The front end of the applet calls wx.getuserinfo to get the encrypted data encrytedData and the encrypted vector iv, which need to be saved to decrypt to the server

The corresponding js code is:

  //Get User Data
  getUserInfo() {
    var that=this;		//Otherwise, this cannot be accessed within the wx method
    return new Promise(function(resolve, reject){
      wx.getUserInfo({
        success: function(e) {
          console.log(e)
          //Store related data
          app.globalData.userInfo = e.userInfo
          that.data.loginData.iv=e.iv;
          that.data.loginData.encryptedData=e.encryptedData;
          resolve();
        }
      })  
    }) 
  },

The function calling the login is rewritten as follows:

  //User login operation
  userLogin(e){
    console.log(e);
    var that=this;
    this.getCode()
    .then(()=>{
      return this.getUserInfo()
    })
    .then(()=>{
      console.log(this.data.loginData);
      return this.getRequest("api/login",this.data.loginData.code)           
    })
    .then((res)=>{
      console.log(res)
      that.data.token=res.data.data.token;
      that.data.loginData.code=res.data.data.session;
      return this.getRequest("api/getUserData",this.data.loginData)
      
    }).then((res)=>{
      that.data.userInfo=res.data.data;
      that.data.hasUserInfo=true;
    })  
  },

Then the back end receives three data from the front end:

  1. Encrypt data
  2. Encryption Vector
  3. session_key

With that, we start decrypting

5. Decrypt data, send back

Before that, you need to import maven dependencies to support decrypting the corresponding packages

<!--WeChat AES-128-CBC Decryption Dependency-->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk16</artifactId>
            <version>1.46</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.xfire</groupId>
            <artifactId>xfire-core</artifactId>
            <version>1.2.6</version>
        </dependency>

As above, import an encrypted and decrypted tool class, Aes.java

package com.BookKeeping.common;

import com.alibaba.fastjson.JSONObject;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;


import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;

/**
 * AES Encryption and Decryption Algorithms
 */

public class Aes {
    public static String KEY_ALGORITHM = "AES";
    //Data Filling Method
    String algorithmStr = "AES/CBC/PKCS7Padding";
    //Avoid duplicating new generation of multiple BouncyCastleProvider objects because GC cannot recycle, which can cause memory overflow
    //new object only the first time decrypt() method is called
    public static boolean initialized = false;

    /**
     * AES encryption
     */
    public byte[] encrypt(byte[] originalContent, byte[] encryptKey, byte[] ivByte) {
        initialize();
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec skeySpec = new SecretKeySpec(encryptKey, "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(ivByte));
            byte[] encrypted = cipher.doFinal(originalContent);
            return encrypted;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * AES Decrypt
     * Fill Mode AES/CBC/PKCS7Padding
     * Decryption mode 128
     */
    public String decrypt(String encryptedData, String sessionKey, String ivs) {
        initialize();
        //Convert to byte type to decrypt
        byte[] content= Base64.decode(encryptedData);
        byte[] aesKey= Base64.decode(sessionKey);
        byte[] ivBytes= Base64.decode(ivs);
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            Key sKeySpec = new SecretKeySpec(aesKey, "AES");
            cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivBytes));// Initialization
            byte[] result = cipher.doFinal(content);

            return new String(result);
        } catch (Exception e) {
            System.out.println("Decryption failed");
            throw new RuntimeException(e);
        }
        //return "Error";
    }

    /**BouncyCastle Provided as security to prevent running errors during encryption and decryption because the jdk built-in does not support mode change.**/
    public static void initialize() {
        if (initialized)
            return;
        Security.addProvider(new BouncyCastleProvider());
        initialized = true;
    }

    // Generate iv
    public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
        AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
        params.init(new IvParameterSpec(iv));
        return params;
    }

    public JSONObject domain(String encryptedData, String sessionKey, String ivs){
        JSONObject jo = JSONObject.parseObject(decrypt(encryptedData, sessionKey, ivs));
        return jo;
    }
}

As above, the domain is a concrete implementation of decryption, and the returned domains are also converted to json format

Next the control layer writes this

    @RequestMapping(value = "/getUserData",method = RequestMethod.POST)
    public Result getUserData(@RequestBody Login login, @RequestHeader("Authorization") String token){
        System.out.println("Get User Data");

        Result rs=new Result();
        //Call Service Layer Processing
        User us=loginService.getUserData(login.getEncryptedData(),login.getCode(),login.getIv());
        System.out.println(us.toString());
        rs.setData(us);
        return rs;
    }

Service Layer

    @Override
    public User getUserData(String EncryptedData,String session,String ivs) {
        Aes aes=new Aes();
        User us=new User();		//Specific entity classes refer to the previous article

        //Getting user data using session_key
        JSONObject userData=aes.domain(EncryptedData,session,ivs);
        us.setAvatarUrl(userData.getString("avatarUrl"));
        us.setGender(userData.getInteger("gender"));
        us.setNickName(userData.getString("nickName"));
        us.setOpenId(userData.getString("openId"));
        return us;
    }

Now that the decryption is complete, you can see that the interface returns the following data

Two original articles have been published. Approved 0. Visits 8
Private letter follow

Tags: Session Apache Java JSON

Posted on Thu, 30 Jan 2020 19:47:43 -0800 by Nik