1, Introduction
The most complete front and back end separation wechat web page authorization solution in the whole network. If there is a better optimization plan, welcome to communicate more. At the end of the article, there is the author's contact information, welcome to nag.
2, To authorize a web page
- Step 1: users agree to authorize and obtain code
- 2 step 2: exchange code for authorized access_token
- 3 step 3: refresh access_token (if required)
- Step 4: pull the user information (scope should be snsapi_userinfo)
- 5 attachment: Access Certificate_ Whether token) is valid
Details reference Official documents
Note: access here_ Token belongs to webpage authorized access_token, not access authorized_ token, Official explanation As follows:
About web access authorization_ Token and common access_ The difference of token
1, WeChat web page authorization is realized through OAuth2.0 mechanism. After the user authorizes the official account, the official account can get a unique interface calling Certificate (access_). Token), access is authorized through the web page_ Token can be used for interface call after authorization, such as obtaining basic user information;
2. For other wechat interfaces, access needs to be obtained through "get access" in basic support_ Common access obtained by token interface_ Token call.
But not very clearly. In fact, the difference between the two is:
- First, web access authorization_ Token users can get user information if they can, and they can not pay attention to the official account, but the ordinary access_. Token did not pay attention to the official account and was empty.
- Second, the daily limit call times of the two are different, ordinary access_token 2000 times a day, access to authorized webpage_ The number of token is unlimited, and the user information is obtained 50000 times a day.
3, Back end access
Open source tools for back end weixin-java-tools
three point one pom.xml Introducing jar package
<dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>3.8.0</version> </dependency>
three point two application.yml Add configuration
Here you can apply for your own appid and appsecret Test account
# WeChat official account wechat: mpAppId: appid mpAppSecret: appsecret
3.3 new read profile WechatMpProperties.java
package com.hsc.power.dm.wechat.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * WeChat official account configuration file * * @author liupan * @date 2020-05-26 */ @Data @Component @ConfigurationProperties(prefix = "wechat") public class WechatMpProperties { private String mpAppId; private String mpAppSecret; }
3.4 create a new 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 official account configuration * * @author liupan * @date 2020-05-26 */ @Component public class WechatMpConfig { @Autowired private WechatMpProperties wechatMpProperties; /** * Information required to configure WxMpService * * @return */ @Bean // This annotation specifies that when the Spring container is started, the method will be executed and the objects returned by the method will be managed by the Spring container public WxMpService wxMpService() { WxMpService wxMpService = new WxMpServiceImpl(); // Set the storage location of configuration information wxMpService.setWxMpConfigStorage(wxMpConfigStorage()); return wxMpService; } /** * Configure appID and appsecret * * @return */ @Bean public WxMpConfigStorage wxMpConfigStorage() { // Using this implementation class means storing configuration information in memory WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl(); wxMpDefaultConfig.setAppId(wechatMpProperties.getMpAppId()); wxMpDefaultConfig.setSecret(wechatMpProperties.getMpAppSecret()); return wxMpDefaultConfig; } }
3.5 new 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; }
3.6 authorization interface WechatController.java
- /auth: get authorized jump address
- /auth/user/info: initial authorization to obtain user information
- /token/user/info: silent authorization to obtain user information
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 official account interface * * @author liupan * @date 2020-05-26 */ @Slf4j @RestController @RequestMapping("/wechat") public class WechatController { @Autowired private WxMpService wxMpService; /** * Get code parameters * * @param returnUrl url to jump * @return */ @GetMapping("/auth") public Rb<String> authorize(@RequestParam String authCallbackUrl, @RequestParam String returnUrl) { // Hard code our callback address here for debugging // Get the redirect url returned by wechat String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(authCallbackUrl, WxConsts.OAuth2Scope.SNSAPI_USERINFO, URLEncoder.encode(returnUrl)); log.info("[Wechat website authorization] access code,redirectUrl = {}", redirectUrl); return Rb.ok(redirectUrl); } /** * Initial 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 { // Exchange code for 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()); } // From access_ Get the user's openid from the token information String openId = wxMpOAuth2AccessToken.getOpenId(); log.info("[Wechat website authorization] access openId,openId = {}", openId); WechatUser wechatUser = new WechatUser(wxMpUser, wxMpOAuth2AccessToken.getAccessToken()); return Rb.ok(wechatUser); } /** * Silently authorize to obtain user information, judge whether the accessToken is invalid, and refresh the accessToken if it is invalid * @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) { // Invalid try { // Refresh accessToken wxMpOAuth2AccessToken = wxMpService.oauth2refreshAccessToken(wxMpOAuth2AccessToken.getRefreshToken()); } catch (WxErrorException e) { log.error("[Wechat page authorization] refresh token Failed,{}", e.getError().getErrorMsg()); throw ExceptionUtils.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 web page authorization] failed to get user information,{}", e.getError().getErrorMsg()); throw ExceptionUtils.mpe(e.getError().getErrorMsg()); } } }
4, Front end access
4.1 route interception
Whether authorization page is required for noAuth configuration
router.beforeEach((to, from, next) => { // WeChat official account authorization if (!to.meta.noAuth) { // Route requires authorization if (_.isEmpty(store.getters.wechatUserInfo)) { // Get user information if ( !_.isEmpty(store.getters.openid) && !_.isEmpty(store.getters.accessToken) ) { // openid and accessToken exist, authorized // Determine whether the accessToken expires, refresh the token after expiration, and obtain the user information store.dispatch('getUserInfo') next() } else { // todo jump page authorization // Record the current page url localStorage.setItem('currentUrl', to.fullPath) next() } } else { // todo already has user information and needs to be updated regularly next() } } else { // Route does not require authorization next() } })
4.2 authorization page
{ path: '/auth', name: 'auth', component: resolve => { require(['@/views/auth/index.vue'], resolve) }, meta: { noAuth: true } },
<template></template> <script> import config from '@/config' import WechatService from '@/api/wechat' export default { mounted() { WechatService.auth(config.WechatAuthCallbackUrl).then(res => { if (res.ok()) { // Jump directly after obtaining the authorization page window.location.href = res.data } }) } } </script>
4.3 authorized store
Authorization and storage of user information 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 is saved to storage, set a certain date and update regularly 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, set valid time, default 30 days 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, authorized // Determine whether the accessToken expires, refresh the token after expiration, and obtain the user information const res = await WechatService.getUserInfo(openid, token) if (res.ok()) { // todo judgment res.data Is there any mistake commit('saveWechatUserInfo', res.data) } } }, // Initial authorization to obtain user information async getAuthUserInfo({ commit }, { code, state }) { if (!_.isEmpty(code) && !_.isEmpty(state)) { const res = await WechatService.getAuthUserInfo(code, state) if (res.ok()) { commit('saveWechatUserInfo', res.data) } } } } }
4.4 custom storage tools localStorageUtil.js
localStorageUtil.js : used to set save validity
Here, the user information settings are saved for 30 days. According to the previous 4.1 route interception judgment, the user information expires and needs to be authorized again. I don't think it's a good way, but the access to user information is limited to 50000 times a month. I don't want to call the interface every time to get user information. Is there a better solution here?
import _ from 'lodash' import moment from 'moment' export default { /** * Get the value in session storage * @param {*} key * @param {*} defaultValue */ get(key, defaultValue) { return this.parse(key, defaultValue) }, /** * Put it into session storage and automatically string obj * @param {*} key * @param {*} obj * @param expires Expiration 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 from session storage * @param {*} key */ remove(key) { localStorage.removeItem(key) }, /** * Take 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) { // If there is an expiration time, judge whether it expires: before the current time, it expires if (moment(valueObj.expires).isBefore(moment(), 'day')) { // delete this.remove(key) // Direct return return null } return valueObj.value } // Return objects directly without expiration time return valueObj } // Not object, return value return value || defaultValue } }
So far, we have achieved success. We can get user information in wechat developer tools, which is effective through personal testing.
Open source is not easy to use and cherish!
Sponsor authors, communicate with each other
Please indicate: My technology sharing » Spring Boot+Vue front and rear WeChat official account separation solution