Hello, I am ning Zaichun, blogger, let’s study together!!

Write this article reason, mainly because recently write graduation design, used small program, this middle twists and turns, a long story. Graduation design really lets a person hemp brain wide 😂. alas

Recently, I have been updating continuously. After pushing the code every day, I will write down the problems encountered. I hope it will be helpful to you.

Found a lot of a lot of online, read more than dozens of, to tell the truth, some give the core code, add a wechat official flow chart is over, people will understand. But to be honest, it is really not suitable for entry-level scholars, waste a lot of time is not necessarily able to solve the problem, the code copy is not less this is less that, or is not uniform, I do not know if you have encountered such a problem when reading this article.

So I will step on the pit experience to write down, hope to help everyone, open source progress, exchange progress, learn together!!

Official wechat document

First, wechat small program official login flow chart

Personal Understanding:

  1. Call wx.login() to get the code, which is a very important parameter in the URL that implements wechat temporary login.

    • WeChat authorization url = “api.weixin.qq.com/sns/jscode2…”
    • js_codeThe value you use is the code you get.
  2. The obtained code is passed to our own SpringBoot backend, which sends a request to the wechat interface service.

    String url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
    String replaceUrl = url.replace("{0}", appid).replace("{1}", secret).replace("{2}", code);
    String res = HttpUtil.get(replaceUrl);// There is code behind, don't worry
    Copy the code
    • Appid: application ID, secret: application key, js_code: code sent to us from the foreground

    • Secret Obtaining method:

      1. Enter the wechat public platform
      2. Choose “Development Management” from the left menu.
      3. Click “Development Settings” on the right TAB.
      4. Click reset on the right side of the AppSecret bar and a QR code will pop up, requiring developers to scan the QR code to reset AppSecret. Click Copy when AppSecret appears and save your AppSecret.
      5. If you don’t save it, you have to regenerate it.
  3. After sending the request, the back end gets the return information:

    {"session_key":"G59Evf\/Em54X6WsFsrpA1g==","openid":"o2ttv5L2yufc4-VoSPhTyUnToY60"}
    Copy the code
  4. A custom login state is associated with openID and session_key.

    • The first way: we canOpenid and session_keySave in Redis, when the front end to access the belt can be accessed.
    • The second way: usejwtWay to generateTokenGive it back to the front end so that the front end can take it on the next request and allow them access.
  5. The front-end stores the token to storage

  6. The front end initiates a business request at wx.request() with a custom login state, and the back end checks the request header.

  7. The back end returns business data

That’s the official way, but in this day and age, data is so important that it’s impossible not to persist user data, so the process is a little more manipulative.

Second, personal login flowchart

Three, small program end

Just to be clear, this is just a Demo of the test, it’s a separate test.

I do not have a local wechat programming environment is to take a small partner’s test.

2.1, callwx.login()

wx.login({
    success:function(res){
        if(res.code){
            console.log(res.code); }}})Copy the code

A string that looks like this:

So we’re going to save this code that comes back, and we’re going to use it later in the backend test.

2.2, callgetUserInfo()

<button open-type="getUserInfo" bindgetuserinfo="userInfoHandler"> Click me <tton>
Copy the code
// wechat authorization
wx.getUserInfo({
    success: function(res) {
		console.log(res); }})Copy the code

Here’s some of the data that’s printed out.

What we need to preserve is

  1. encrytedData: Encrypted data of complete user information including sensitive data (that is, user data can be obtained through anti-decryption). See detailsUser data signature verification and encryption and decryption
  2. iv: Initial vector of the encryption algorithm. For details, seeUser data signature verification and encryption and decryption

So far, we need to get the data in the foreground, has ended, next we get the data together to see the back end!!


SpringBoot backend

In order to simplify the code, I just output the obtained data, without really saving it in the data. The business operations are shown in the article with comments.

Project Structure:

3.1 Related JARS

Create a SpringBoot project, or a Maven project.

  <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.5.2</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>

        <! Use hutool to call the HTTP wrapper class.
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.5</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
Copy the code

3.2. Yml configuration file

server:
  port: 8081
spring:
  application:
    name: springboot-weixin
  redis:
    database: 0
    port: 6379
    host: localhost
    password:
weixin:
  appid: 'appid'
  secret: 'Applied key'
Copy the code

3.3. Public Classes

It’s a constant

public class RedisKey {
    public static final String WX_SESSION_ID = "wx_session_id";
}
Copy the code
/** * Unified response result set *@author crush
 */
@Data
public class Result<T> {

    // Operation code
    Integer code;

    // Prompt message
    String message;

    // Result data
    T data;

    public Result(a) {}public Result(ResultCode resultCode) {
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public Result(ResultCode resultCode, T data) {
        this.code = resultCode.code();
        this.message = resultCode.message();
        this.data = data;
    }

    public Result(String message) {
        this.message = message;
    }

    public static Result SUCCESS(a) {
        return new Result(ResultCode.SUCCESS);
    }

    public static <T> Result SUCCESS(T data) {
        return new Result(ResultCode.SUCCESS, data);
    }

    public static Result FAIL(a) {
        return new Result(ResultCode.FAIL);
    }

    public static Result FAIL(String message) {
        return newResult(message); }}Copy the code

/** * General response status */
public enum ResultCode {

    /* Success status code */
    SUCCESS(0."Operation successful!"),

    /* Error status code */
    FAIL(-1."Operation failed!"),

    /* Error: 10001-19999 */
    PARAM_IS_INVALID(10001."Invalid parameter"),
    PARAM_IS_BLANK(10002."Parameter null"),
    PARAM_TYPE_BIND_ERROR(10003."Parameter format error"),
    PARAM_NOT_COMPLETE(10004."Parameter missing"),

    /* User error: 20001-29999*/
    USER_NOT_LOGGED_IN(20001."User not logged in, please log in first."),
    USER_LOGIN_ERROR(20002."Account does not exist or password is incorrect"),
    USER_ACCOUNT_FORBIDDEN(20003."Account has been disabled."),
    USER_NOT_EXIST(20004."User does not exist"),
    USER_HAS_EXISTED(20005."User already exists"),

    /* System error: 40001-49999 */
    FILE_MAX_SIZE_OVERFLOW(40003."Upload size too large"),
    FILE_ACCEPT_NOT_SUPPORT(40004."Upload file format not supported"),

    /* Data error: 50001-599999 */
    RESULT_DATA_NONE(50001."Data not found"),
    DATA_IS_WRONG(50002."Wrong data"),
    DATA_ALREADY_EXISTED(50003."Data already exists"),
    AUTH_CODE_ERROR(50004."Verification code error"),


    /* Permission error: 701-79999 */
    PERMISSION_UNAUTHENTICATED(70001."Login is required for this operation!"),

    PERMISSION_UNAUTHORISE(70002."Insufficient authority, no right to operate!"),

    PERMISSION_EXPIRE(70003."Login status expired!"),

    PERMISSION_TOKEN_EXPIRED(70004."Token has expired"),

    PERMISSION_LIMIT(70005."Limited access times"),

    PERMISSION_TOKEN_INVALID(70006."Invalid token"),

    PERMISSION_SIGNATURE_ERROR(70007."Signature failed"),

    // Operation code
    int code;
    // Prompt message
    String message;

    ResultCode(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int code(a) {
        return code;
    }

    public String message(a) {
        return message;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public void setMessage(String message) {
        this.message = message; }}Copy the code
package com.crush.mybatisplus.config;

import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

/** * Redis configuration class **@author crush
 */
@EnableCaching
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //key hasKey serialization
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);

        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        returnstringRedisTemplate; }}Copy the code

3.4, the Controller

import com.crush.weixin.commons.Result;
import com.crush.weixin.entity.WXAuth;
import com.crush.weixin.service.IWeixinService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/ * * * *@author crush
 * @sinceThe 2021-09-14 * /
@Slf4j
@RestController
@RequestMapping("/weixin")
public class WeixinController {

    @Autowired
    IWeixinService weixinService;

    // This is the interface that uses pass code in
    @GetMapping("/sessionId/{code}")
    public String getSessionId(@PathVariable("code") String code){
        return  weixinService.getSessionId(code);
    }

    @PostMapping("/authLogin")
    public Result authLogin(@RequestBody WXAuth wxAuth) {
        Result result = weixinService.authLogin(wxAuth);
        log.info("{}",result);
        returnresult; }}Copy the code

3.5, the service layer

public interface IWeixinService extends IService<Weixin> {

    String getSessionId(String code);

    Result authLogin(WXAuth wxAuth);
}
Copy the code
import cn.hutool.core.lang.UUID;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.crush.weixin.commons.RedisKey;
import com.crush.weixin.commons.Result;
import com.crush.weixin.entity.WXAuth;
import com.crush.weixin.entity.Weixin;
import com.crush.weixin.entity.WxUserInfo;
import com.crush.weixin.mapper.WeixinMapper;
import com.crush.weixin.service.IWeixinService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;


/ * * *@author crush
 * @sinceThe 2021-09-14 * /
@Slf4j
@Service
public class WeixinServiceImpl extends ServiceImpl<WeixinMapper.Weixin> implements IWeixinService {


    @Value("${weixin.appid}")
    private String appid;

    @Value("${weixin.secret}")
    private String secret;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    WxService wxService;


    @Override
    public String getSessionId(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
        String replaceUrl = url.replace("{0}", appid).replace("{1}", secret).replace("{2}", code);
        String res = HttpUtil.get(replaceUrl);
        String s = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(RedisKey.WX_SESSION_ID + s, res);
        return s;
    }

    @Override
    public Result authLogin(WXAuth wxAuth) {
        try {
            String wxRes = wxService.wxDecrypt(wxAuth.getEncryptedData(), wxAuth.getSessionId(), wxAuth.getIv());
            log.info("User Information:"+wxRes);
       		// User information: {"openId":"o2ttv5L2yufc4-sVoSPhTyUnToY60","nickName":"juana","gender":2,"language":"zh_CN","city":"Changsha","province": "Hunan" and "country", "China" and "avatarUrl" : "image link", "watermark" : {" timestamp ": 1631617387," appid ":" id "}}
            WxUserInfo wxUserInfo = JSON.parseObject(wxRes,WxUserInfo.class);
            // Business operations: you can use the data here to query the database, if there is no data in the database, add it, that is, to achieve wechat account registration
            // If it is already registered, use the data to generate JWT return token to achieve login status
            return Result.SUCCESS(wxUserInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        returnResult.FAIL(); }}Copy the code

It involves the method of user information decryption. If you want to know more about it, you can go to the official documents of wechat. I did not go into this.

import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.crush.weixin.commons.RedisKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Random;

@Slf4j
@Component
public class WxService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public String wxDecrypt(String encryptedData, String sessionId, String vi) throws Exception {
        // Start decrypting
        String json =  redisTemplate.opsForValue().get(RedisKey.WX_SESSION_ID + sessionId);
        log.info("Information previously stored in Redis:"+json);
        {"session_key":"G59Evf\/Em54X6WsFsrpA1g=="," openID ":" o2TTV5L2YUFC4-VOspHTYuntoy60 "}
        JSONObject jsonObject = JSON.parseObject(json);
        String sessionKey = (String) jsonObject.get("session_key");
        byte[] encData = cn.hutool.core.codec.Base64.decode(encryptedData);
        byte[] iv = cn.hutool.core.codec.Base64.decode(vi);
        byte[] key = Base64.decode(sessionKey);
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(encData), "UTF-8"); }}Copy the code

Finally, write a startup class to start the test.

@SpringBootApplication
public class SpringBootWeixin {
    public static void main(String[] args) { SpringApplication.run(SpringBootWeixin.class); }}Copy the code

Five, the test

After writing the back end, we can then use the data obtained from those small programs we collected earlier.

1. Send the first request:

Code: is the data we got before.

http://localhost:8081/weixin/sessionId/{code}  
Copy the code

A sessionId is returned, which is carried in the second request.

2. Send the second request

http://localhost:8081/weixin/authLogin
Copy the code

Request mode: POST

Data: data in json format

{
    "encryptedData":"sYiwcAM73Ci2EB3y9+C6....."."iv": "xZGOj6RwaOS=="."sessionId":"We got the sessionId on our last request."
}
Copy the code

The request success looks like this.

Let’s store what we need in the database for persistence.

6. Talk to yourself

This is just a small demo. In use, it will be used together with the security framework and Jwt. I will update it on weekends when I am free.


Hello, I am the blogger Ning Zaichun, have a question can leave a message comment or private letter me, we exchange study together!

But all see here, a thumbs-up 👩💻

Source:

SpringBoot-weixin-gitee

SpringBoot-weixin-github

Supplement:

To view projects on Github, you can install an extension at Google. It’s easier to look at the code

After installation, when you view your project on Github, you will see a directory structure like the one below on the left. More convenient

That’s all for today’s article, see you next time!!