One, the introduction

The most complete network of front and back end separation of wechat web page authorization solution. If there is a better optimization plan, welcome to exchange more, the author’s contact information at the end of the article, welcome to bother.

Second, the steps of web page authorization

  • 1 Step 1: The user agrees to authorize and obtains the code
  • Step 2: Exchange web access_token through code
  • 3 Step 3: Refresh access_token (if necessary)
  • 4 Step 4: Pull user information (scope is SNsapi_userinfo)
  • 5 Appendix: Verify the validity of access_token

Please refer to the official documentation for details

Note: The access_token here belongs to the web authorization access_token, not the common authorization access_token. The official explanation is as follows:

1. Wechat web page authorization is realized through OAuth2.0 mechanism. After the user is authorized to the public account, the public account can obtain a unique interface call certificate (web page authorization access_token). The access_token can be used to invoke the interface after authorization, such as obtaining basic user information. 2. For other wechat interfaces, ordinary access_token calls need to be obtained through the “Access Access_token” interface in basic support.

But not very clearly. The difference between the two is:

  • First, webpage authorization access_token can obtain user information as long as the user permits, can not pay attention to the public account, while ordinary access_token does not pay attention to the public account, access to user information is empty;
  • Second, the daily limit of the two is different by the number of times. The ordinary access_token is called 2000 times a day, the access to web page authorization is unlimited times, and the access to user information is 50,000 times a day.

Third, back-end access

The open source tool Weixin-Java-tools is used at the back end

3.1 Pom.xml introduces jar packages

<dependency>
	<groupId>com.github.binarywang</groupId>
	<artifactId>weixin-java-mp</artifactId>
	<version>3.8.0</version>
</dependency>
Copy the code

3.2 Application. yml Add configuration

Here you can use your own AppID and AppSecret to apply for a test account

# wechat official account
wechat:
  mpAppId: appid
  mpAppSecret: appsecret
Copy the code

3.3 Creating and reading the wechatmpproperties.java configuration file

package com.hsc.power.dm.wechat.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/** * wechat public number profile **@author liupan
 * @dateThe 2020-05-26 * /
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatMpProperties {
    private String mpAppId;
    private String mpAppSecret;
}
Copy the code

3.4 Creating a custom wechat configuration wechatmpconfig.java

package com.hsc.power.dm.wechat.config;

import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/** * wechat public number configuration **@author liupan
 * @dateThe 2020-05-26 * /
@Component
public class WechatMpConfig {

    @Autowired
    private WechatMpProperties wechatMpProperties;

    /** * Information required to configure WxMpService **@return* /
    @Bean  // This annotation specifies that the method is executed and the objects returned by the method are managed by the Spring container when the Spring container starts
    public WxMpService wxMpService(a) {
        WxMpService wxMpService = new WxMpServiceImpl();
        // Set the location where the configuration information is stored
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
        return wxMpService;
    }

    /** * Configure appID and appsecret **@return* /
    @Bean
    public WxMpConfigStorage wxMpConfigStorage(a) {
        // Using this implementation class means that configuration information is stored in memory
        WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl();
        wxMpDefaultConfig.setAppId(wechatMpProperties.getMpAppId());
        wxMpDefaultConfig.setSecret(wechatMpProperties.getMpAppSecret());
        returnwxMpDefaultConfig; }}Copy the code

3.5 Creating a wechat user Bean

package com.hsc.power.dm.wechat.vo;

import lombok.Data;
import me.chanjar.weixin.mp.bean.result.WxMpUser;

@Data
public class WechatUser {
    public WechatUser(WxMpUser wxMpUser, String accessToken) {
        this.setAccessToken(accessToken);
        this.setOpenid(wxMpUser.getOpenId());
        this.setUnionId(wxMpUser.getUnionId());
        this.setNickname(wxMpUser.getNickname());
        this.setLanguage(wxMpUser.getLanguage());
        this.setCountry(wxMpUser.getCountry());
        this.setProvince(wxMpUser.getCity());
        this.setCity(wxMpUser.getCity());
        this.setSex(wxMpUser.getSex());
        this.setSexDesc(wxMpUser.getSexDesc());
        this.setHeadImgUrl(wxMpUser.getHeadImgUrl());
    }

    private String openid;
    private String accessToken;
    private String unionId;
    private String nickname;
    private String language;
    private String country;
    private String province;
    private String city;
    private Integer sex;
    private String sexDesc;
    private String headImgUrl;
}
Copy the code

3.6 Authorization Interface wechatController.java

    1. /auth: obtains the forward address of authorization
    1. /auth/user/info: obtains user information after the initial authorization
    1. /token/user/info: obtains user information through silent authorization
package com.hsc.power.dm.wechat.web;

import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.hsc.power.core.base.ret.Rb;
import com.hsc.power.dm.wechat.vo.WechatUser;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.net.URLEncoder;

/** * wechat public account interface **@author liupan
 * @dateThe 2020-05-26 * /
@Slf4j
@RestController
@RequestMapping("/wechat")
public class WechatController {
    @Autowired
    private WxMpService wxMpService;

    /** * get the code argument **@paramReturnUrl Indicates the URL to jump to@return* /
    @GetMapping("/auth")
    public Rb<String> authorize(@RequestParam String authCallbackUrl, @RequestParam String returnUrl) {
        // Hardcode our callback address here for now for debugging
        // Get the redirected URL returned by wechat
        String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(authCallbackUrl, WxConsts.OAuth2Scope.SNSAPI_USERINFO, URLEncoder.encode(returnUrl));
        log.info(Get code, redirectUrl = {}", redirectUrl);
        return Rb.ok(redirectUrl);
    }

    /** * First authorization to obtain user information **@param code
     * @param returnUrl
     * @return* /
    @GetMapping("/auth/user/info")
    public Rb<WechatUser> userInfo(@RequestParam("code") String code, @RequestParam("state") String returnUrl) {
        WxMpOAuth2AccessToken wxMpOAuth2AccessToken;
        WxMpUser wxMpUser;
        try {
            // Use code to obtain access_token information
            wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
            wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null);
        } catch (WxErrorException e) {
            log.error("[wechat webpage authorization] exception, {}", e);
            throw ExceptionUtils.mpe(e.getError().getErrorMsg());
        }
        // Obtain the user's OpenID from access_token information
        String openId = wxMpOAuth2AccessToken.getOpenId();
        log.info(Obtain openId, openId = {}, openId);

        WechatUser wechatUser = new WechatUser(wxMpUser, wxMpOAuth2AccessToken.getAccessToken());
        return Rb.ok(wechatUser);
    }

    /** * Silent authorization obtains user information and determines whether accessToken is invalid. If accessToken is invalid, refresh accecssToken *@param openid
     * @param token
     * @return* /
    @GetMapping("/token/user/info")
    public Rb<WechatUser> getUserInfo(@RequestParam String openid, @RequestParam String token) {
        WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();
        wxMpOAuth2AccessToken.setOpenId(openid);
        wxMpOAuth2AccessToken.setAccessToken(token);
        boolean ret = wxMpService.oauth2validateAccessToken(wxMpOAuth2AccessToken);
        if(! ret) {// It is invalid
            try {
                / / refresh accessToken
                wxMpOAuth2AccessToken = wxMpService.oauth2refreshAccessToken(wxMpOAuth2AccessToken.getRefreshToken());
            } catch (WxErrorException e) {
                log.error("[wechat webpage authorization] failed to refresh token, {}", e.getError().getErrorMsg());
                throwExceptionUtils.mpe(e.getError().getErrorMsg()); }}// Get user information
        try {
            WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null);
            WechatUser wechatUser = new WechatUser(wxMpUser, wxMpOAuth2AccessToken.getAccessToken());
            return Rb.ok(wechatUser);
        } catch (WxErrorException e) {
            log.error("[wechat webpage authorization] failed to obtain user information, {}", e.getError().getErrorMsg());
            throwExceptionUtils.mpe(e.getError().getErrorMsg()); }}}Copy the code

Four, front-end access

4.1 Route Interception

NoAuth Configures whether the authorization page is required

router.beforeEach((to, from, next) = > {
  // Authorized by wechat public account
  if(! to.meta.noAuth) {// Routing requires authorization
    if (_.isEmpty(store.getters.wechatUserInfo)) {
      // Get user information
      if(! _.isEmpty(store.getters.openid) && ! _.isEmpty(store.getters.accessToken) ) {// OpenID and accessToken exist, already granted
        // Determine whether accessToken is expired, refresh the token after expiration, and obtain user information
        store.dispatch('getUserInfo')
        next()
      } else {
        // Todo redirect page authorization
        // Record the current page URL
        localStorage.setItem('currentUrl', to.fullPath)
        next({name: 'auth'}}})else {
      // Todo already has user information that needs to be updated periodically
      next()
    }
  } else {
    // Routing does not require authorization
    next()
  }
})
Copy the code

4.2 Authorization Page

{
  path: '/auth'.name: 'auth'.component: resolve= > {
    require(['@/views/auth/index.vue'], resolve)
  },
  meta: {
    noAuth: true}},Copy the code
<template></template> <script> import config from '@/config' import WechatService from '@/api/wechat' export default { Mounted () {WechatService. Auth (config. WechatAuthCallbackUrl). Then (res = > {the if (res) ok ()) {/ / access authorization page directly after the jump window.location.href = res.data } }) } } </script>Copy the code

4.3 authorized store

Authorization and user information are stored in VUEX

import _ from 'lodash'
import WechatService from '@/api/wechat'
import localStorageUtil from '@/utils/LocalStorageUtil'
export default {
  state: {
    unionId: ' '.openid: ' '.accessToken: ' '.wechatUserInfo: {}},getters: {
    unionId: state= > {
      return state.unionId || localStorageUtil.get('unionId')},openid: state= > {
      return state.openid || localStorageUtil.get('openid')},accessToken: state= > {
      return state.accessToken || localStorageUtil.get('accessToken')},wechatUserInfo: state= > {
      return state.wechatUserInfo || localStorageUtil.get('wechatUserInfo')}},mutations: {
    saveWechatUserInfo: (state, res) = > {
      state.wechatUserInfo = res
      // todo save to storage, set a certain date, periodic update
      state.unionId = res.unionId
      state.openid = res.openid
      state.accessToken = res.accessToken
      localStorageUtil.set('unionId', res.unionId)
      localStorageUtil.set('openid', res.openid)
      localStorageUtil.set('accessToken', res.accessToken)
      // Save userInfo and set the validity period to 30 days by default
      localStorageUtil.set('wechatUserInfo', res, 30)}},actions: {
    // Silent authorization to obtain user information
    async getUserInfo({ commit, getters }) {
      const openid = getters.openid
      const token = getters.accessToken
      if(! _.isEmpty(openid) && ! _.isEmpty(token)) {// OpenID and accessToken exist, already granted
        // Determine whether accessToken is expired, refresh the token after expiration, and obtain user information
        const res = await WechatService.getUserInfo(openid, token)
        if (res.ok()) {
          // Todo determines whether res.data is incorrect
          commit('saveWechatUserInfo', res.data)
        }
      }
    },
    // Obtain user information for the first time
    async getAuthUserInfo({ commit }, { code, state }) {
      if(! _.isEmpty(code) && ! _.isEmpty(state)) {const res = await WechatService.getAuthUserInfo(code, state)
        if (res.ok()) {
          commit('saveWechatUserInfo', res.data)
        }
      }
    }
  }
}
Copy the code

4.4 Customizing the Storage Tool LocalStorageutil.js

Localstorageutil. js: Used to set the preservation validity period

In this case, the user information is saved for 30 days. According to 4.1 Route Interception, the user information expires and authentication needs to be performed again. I feel this way is not good, but the monthly limit of obtaining user information is 50,000 times. I don’t want to call the interface to obtain user information every time. Is there a better solution?

import _ from 'lodash'
import moment from 'moment'
export default {
  * @param {*} key * @param {*} defaultValue */
  get(key, defaultValue) {
    return this.parse(key, defaultValue)
  },
  Obj * @param {*} key * @param {*} obj * @param {Integer} Expires Time: days */
  set(key, obj, expires) {
    if (expires) {
      const tmpTime = moment()
        .add(expires, 'days')
        .format('YYYY-MM-DD')
      const handleObj = { expires: tmpTime, value: obj }
      localStorage.setItem(key, JSON.stringify(handleObj))
    } else {
      if (_.isObject(obj)) {
        localStorage.setItem(key, JSON.stringify(obj))
      } else {
        localStorage.setItem(key, obj)
      }
    }
  },
  /** * Remove key * @param {*} key */ from session-storage
  remove(key) {
    localStorage.removeItem(key)
  },

  /** * Retrieve the key from session-storage and objectify the value * @param {*} key * @param {*} defaultValue */
  parse(key, defaultValue) {
    let value = localStorage.getItem(key)
    if (_.isObject(value)) {
      const valueObj = JSON.parse(value)
      if (valueObj.expires) {
        // There is an expiration date: before the present time, the expiration date
        if (moment(valueObj.expires).isBefore(moment(), 'day')) {
          / / delete
          this.remove(key)
          // Return directly
          return null
        }
        return valueObj.value
      }
      // Return the object directly without expiration time
      return valueObj
    }
    // Not an object, return value
    return value || defaultValue
  }
}
Copy the code

So far, in the wechat developer tools can obtain user information, effective pro test.

Open source is not easy, and use and cherish!






Spring Boot+Vue front and back end separation oF wechat public account webpage authorization solution