欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

微信公众号--授权相关

程序员文章站 2022-03-21 07:55:26
1.近期在做微信公众号相关授权,借此机会记录一下,以备后续1.测试接口号申请1.首先验证成功开发者 /** * @author: cc * @date: 2020/7/15 13:26 */ @GetMapping("/wxDomainToken") @ApiOperation(value = "微信接口域验证", httpMethod = "GET", notes = "微信接口域验证") public void show(HttpServle...

1.近期在做微信公众号相关授权,借此机会记录一下,以备后续

相关登录流程,请参考微信官方文档

1.测试接口号申请

微信公众号--授权相关

1.首先验证成功开发者

 /**
     * @author: cc
     * @date: 2020/7/15 13:26
     */
    @GetMapping("/wxDomainToken")
    @ApiOperation(value = "微信接口域验证", httpMethod = "GET", notes = "微信接口域验证")
    public void show(HttpServletRequest request, HttpServletResponse response) {
        try {
            //微信加密签名
            String signature = request.getParameter("signature");
            // 时间戳
            String timestamp = request.getParameter("timestamp");
            // 随机数
            String nonce = request.getParameter("nonce");
            // 随机字符串
            String echoStr = request.getParameter("echostr");
            PrintWriter out = response.getWriter();
            // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
            String asscii= WxPublicUtil.getAscii(WxPublicConfig.WX_PUBLIC_TOKEN, timestamp, nonce);
            String newSignature = WxPublicUtil.SHA1(asscii);
            if (newSignature.equals(signature)) {
                out.write(echoStr);
                log.info("微信公众号服务验证成功!" + echoStr );
            }else {
                out.print(echoStr);
                log.info("微信公众号服务验证失败!" + echoStr );
            }
            out.flush();
            out.close();
        }catch (Exception e){
            e.printStackTrace();
            log.error("微信公众号服务验证异常:" + e.getMessage());
        }
    }

当验证成功后,则成为开发者成功。

2.用户统一授权,获取Code

通过redirect_url跳转的地址,获取code值,为了后续获取openId做准备。
注意:1.该Code5分钟有效,且只能使用一次
2.只能在微信客户端打开
3.重定向的url如果有特殊符号需urlencode编码
4.跳转的地址需要和网页授权的地址一样
5.重定向之后,在jsp或html中获取code参数
微信公众号--授权相关

微信公众号--授权相关


跳转的地址:public final static String WX_REDIRECT_URI = "http://wghyqu.natappfree.cc/alipay/wappay/pay.jsp";
  /**
     * @author: cc
     * @date: 2020/7/15 13:26
     */
    @GetMapping("/getWxUserCode")
    @ApiOperation(value = "用户同意授权,并获取微信用户CODE", httpMethod = "POST", notes = "用户同意授权,获取微信用户CODE")
    public void getWxUserCode(HttpServletResponse response){
        StringBuffer sb = new StringBuffer();
        sb.append("https://open.weixin.qq.com/connect/oauth2/authorize?appid=");
        sb.append(WxPublicConfig.WX_APPID);
        try {
            //对重定向url进行编码,官方文档要求,没有编码也可以
            sb.append("&redirect_uri=").append(URLEncoder.encode(WxPublicConfig.WX_REDIRECT_URI, "utf-8"));
           // sb.append("&redirect_uri=").append(WxPublicConfig.WX_REDIRECT_URI);
            sb.append("&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect");
            log.info("获取用户CODE请求的地址:" + sb.toString());
            response.sendRedirect(sb.toString());
        } catch (Exception e) {
            log.error("response重定向失败:>>" + e.getMessage());
            e.printStackTrace();
        }
    }

jsp中获取code参数如下: http://wghyqu.natappfree.cc/?code=081Mia6N0ZQDU721Wy5N0Npa6N0Mia6U&state=STATE#/
微信公众号--授权相关
如果重定向的url地址是:http://wghyqu.natappfree.cc/#/login这种。那么微信会返回:

http://wghyqu.natappfree.cc/?code=081Mia6N0ZQDU721Wy5N0Npa6N0Mia6U&state=STATE#/pages/base/login
期望是这种:
http://wghyqu.natappfree.cc/?code=081Mia6N0ZQDU721Wy5N0Npa6N0Mia6U&state=STATE#/

解决方案是:redirect_url的#号问题

前端控制
const w = location.href.indexOf('?');
const j = location.href.indexOf('#');
let href = window.location.href;
// 处理微信回调url
if (w !== -1 && j > w) { 
  href = location.href.substr(0, w) + location.href.substr(j, location.href.length) + location.search;
 location.href = href;
}

3.根据Code,获取用户的openId、unionid等

   /**
     * @author: cc
     * @date: 2020/7/15 13:26
     */
    @PostMapping("/getWxUserInfo")
    @ApiOperation(value = "获取微信用户信息", httpMethod = "POST", notes = "获取微信用户信息")
    public BaseResponse<WxPublicUserInfoBean> getWxUserInfo(@RequestBody @Validated(value = {CustWxUserInDto.wxCode.class}) CustWxUserInDto custWxUserInDto){
        try {
            return wxPublicService.getWxUserInfo(custWxUserInDto.getCode());
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResponse.error(ResponseStatusEnum.ERROR);
        }
    }

3.1实现类

 /**
     * @description: 通过用户Code获取用户信息openID和unionID等
     * @param code
     * @return: com.fsk.common.response.BaseResponse<java.lang.Object>
     * @author: cc
     * @date: 2020/7/11 13:10
     **/
    @Override
    public BaseResponse<WxPublicUserInfoBean> getWxUserInfo(String code) throws Exception{
        BaseResponse<WxPublicUserInfoBean> response = new BaseResponse<>();
        //1.通过code换取网页授权access_token
        Map<String,String> codeMap = new HashMap<>();
        codeMap.put("appid", WxPublicConfig.WX_APPID);
        codeMap.put("secret",WxPublicConfig.WX_APPSECRET);
        codeMap.put("code",code);
        codeMap.put("grant_type","authorization_code");
        String codeResult = HttpUtil.sendRequest(WxPublicConfig.WX_ACCESS_TOKEN_URL, "GET",codeMap);
        WxUserAccessTokenBean wxUserAccessTokenBean = JSONObject.parseObject(codeResult, WxUserAccessTokenBean.class);
        if (EmptyTool.isNull(wxUserAccessTokenBean.getAccessToken())) {
            log.error(WxExceptionMsg.WX_ACCESS_TOKEN_EX);
            return BaseResponse.error(ResponseStatusEnum.ERROR.getCode(), WxExceptionMsg.WX_ACCESS_TOKEN_EX);
        }
        //2根据获取到的access_token和openId获取用户信息的性别、城市、unionId等
        Map<String,String> scopeMap = new HashMap<>();
        scopeMap.put("access_token", wxUserAccessTokenBean.getAccessToken());
        scopeMap.put("openid",wxUserAccessTokenBean.getOpenId());
        scopeMap.put("lang","zh_CN");
        String userResult = HttpUtil.sendRequest(WxPublicConfig.WX_USER_INFO_URL, "GET",scopeMap);
        WxPublicUserInfoBean wxPublicUserInfoBean = JSONObject.parseObject(userResult, WxPublicUserInfoBean.class);
        if (EmptyTool.isNull(wxPublicUserInfoBean.getOpenId())) {
            log.error(WxExceptionMsg.WX_UNION_EX);
            return BaseResponse.error(ResponseStatusEnum.ERROR.getCode(), WxExceptionMsg.WX_UNION_EX);
        }
        //用户手机号
        String customerTel = "";
        //3.根据openId判断获取用户信息手机号、如果没有手机号再用unionId查
        CustWxUserOutDto openIdDto = custWxUserMapper.selectCustWxUserByOpenId(wxPublicUserInfoBean.getOpenId());
        if (EmptyTool.isNull(openIdDto) || EmptyTool.isNull(openIdDto.getCustomerCode())) {
            //根据unionId判断获取用户信息手机号、如果没有手机号前端需dialog弹框绑定注册
            String unionid = wxPublicUserInfoBean.getUnionId();
            List<CustWxUserOutDto> unionIdDtoList = custWxUserMapper.selectCustWxUserByUnionId(unionid);
            if (EmptyTool.isNull(unionIdDtoList) || unionIdDtoList.size() == 0) {
                //新增微信客户信息表
                CustWxUserInDto custWxUserInDto = new CustWxUserInDto();
                custWxUserInDto.setId(Snowflake.nextId() + "");
                custWxUserInDto.setAppId(WxPublicConfig.WX_APPID);
                custWxUserInDto.setOpenId(wxPublicUserInfoBean.getOpenId());
                custWxUserInDto.setUnionId(wxPublicUserInfoBean.getUnionId());
                custWxUserInDto.setCreateDate(DateUtil.parse("yyyy-MM-dd HH:mm:ss",DateUtil.getNowDate()));
                custWxUserMapper.insertBaseCustWxUser(custWxUserInDto);
            }else {
                for (CustWxUserOutDto custWxUserOutDto: unionIdDtoList) {
                    customerTel = EmptyTool.isNull(custWxUserOutDto.getCustomerTel()) ? "" : custWxUserOutDto.getCustomerTel();
                    if (!EmptyTool.isNull(customerTel)) {
                        break;
                    }
                }
            }
        }else {
            customerTel = openIdDto.getCustomerTel();
        }
        wxPublicUserInfoBean.setCustomerTel(customerTel);
        log.info("微信公众号获取用户的信息:" + wxPublicUserInfoBean.toString());
        response.setResponseData(wxPublicUserInfoBean);
        return response;
    }

这个方法会返回用户的openId、和unionId等一些其它信息

4.获取微信全局access_token

注意:这里access_token与用户的access_token不一样
2.有效期为2小时。咱们要注意续期。这里采用定时任务 和 分布式锁实现

4.1 定时任务实现类

package com.fsk.systemCust.utils;

import com.fsk.systemCust.service.WxPublicService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;


@Component
@Slf4j
public class TimedTask {

    @Autowired
    private WxPublicService wxPublicService;

    /**
     * @description: 微信全局刷新Token,每隔一小时执行一次
     * @return: void
     * @author: cc
     * @date: 2020/7/13 17:51
     */
    @Scheduled(cron = "0 0 0/1 * * ? ")
    private void wxGlobalAccessToken(){
        log.info("=================================微信全局刷新Token开始=================================");
        try {
            wxPublicService.getWxGlobalAccessToken();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("=================================微信全局刷新Token结束=================================");
    }

}

4.2 access_token实现类和分布式锁实现

 /**
     * @description: 获取微信全局的accessToken
     * @return: BaseResponse<WxGlobalAccessTokenBean>
     * @author: cc
     * @date: 2020/7/13 13:48
     */
    @Override
    public BaseResponse<WxGlobalAccessTokenBean> getWxGlobalAccessToken() throws Exception{
        log.info("开始获取微信全局的accessToken");
        BaseResponse<WxGlobalAccessTokenBean> response = new BaseResponse<>();
        if (!redisService.setNX(WxPublicConfig.WX_REDIS_GLOBAL_ACCESS_TOKEN + "_","1",3600L)) {
            if (redisService.existsKey(WxPublicConfig.WX_REDIS_GLOBAL_ACCESS_TOKEN)) {
                WxGlobalAccessTokenBean wxGlobalAccessTokenBean = new WxGlobalAccessTokenBean();
                wxGlobalAccessTokenBean.setAccessToken(redisService.get(WxPublicConfig.WX_REDIS_GLOBAL_ACCESS_TOKEN));
                response.setResponseData(wxGlobalAccessTokenBean);
                log.info("redis获取微信全局accessToken:" + wxGlobalAccessTokenBean.toString());
                return response;
            }
        }
        Map<String,String> cMap = new HashMap<>();
        cMap.put("grant_type", "client_credential");
        cMap.put("appid", WxPublicConfig.WX_APPID);
        cMap.put("secret",WxPublicConfig.WX_APPSECRET);
        String request = HttpUtil.sendRequest(WxPublicConfig.WX_PUBLIC_ACCESS_TOKEN_URL, "GET", cMap);
        WxGlobalAccessTokenBean wxGlobalAccessTokenBean = JSONObject.parseObject(request, WxGlobalAccessTokenBean.class);
        redisService.putKeyValueExpire(WxPublicConfig.WX_REDIS_GLOBAL_ACCESS_TOKEN, wxGlobalAccessTokenBean.getAccessToken(), 7200L);
        response.setResponseData(wxGlobalAccessTokenBean);
        log.info("微信服务器获取微信全局的accessToken成功:" + wxGlobalAccessTokenBean.toString());
        return response;
    }

4.3 redis的分布式锁

采用setNX实现,具体可以看我另外redis分布式锁

package com.fsk.systemCust.service.redis.impl;

import com.fsk.systemCust.service.redis.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;


@Service
@Component
public class RedisServiceImpl implements RedisService {
    private final StringRedisTemplate stringRedisTemplate;

    private final RedisConnection redisConnection;

    @Autowired
    public RedisServiceImpl(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.redisConnection = this.stringRedisTemplate.getConnectionFactory().getConnection();
    }

    @Override
    public void putKeyValue(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    @Override
    public void putKeyValueExpire(String key, String value, Long timeout) {
        stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    @Override
    public String get(String key) {

        return stringRedisTemplate.opsForValue().get(key);
    }

    @Override
    public Boolean existsKey(String key) {
        return stringRedisTemplate.hasKey(key);
    }

    @Override
    public boolean deleteByKey(String key) {
        return stringRedisTemplate.delete(key);
    }

    public Boolean setNX(String key, String value,long timeout){
        Boolean isExit = redisConnection.setNX(key.getBytes(), value.getBytes());
        //如果设置成功,要设置其过期时间
        if(isExit){
            stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
        return isExit;
    }
}

相关基础util类:

5.WxPublicUtil.java

package com.fsk.systemCust.misc;

import com.fsk.common.utils.wxPay.MD5Utils;
import com.fsk.common.utils.wxPay.WxConfig;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.*;

/**
 * @description: 微信公众号相关
 * @author: Cc
 * @data: 2020/7/8 15:50
 */
@Slf4j
public class WxPublicUtil {

    /**
     * @description: ASCII码表字典排序
     * @param timestamp
     * @param nonce
     * @return: java.lang.String
     * @author: cc
     * @date: 2020/7/9 19:17
     **/
    public static String getAscii(String publicToken, String timestamp, String nonce){
        String[] src = {publicToken,timestamp,nonce};
        List<String> list = Arrays.asList(src);
        Collections.sort(list);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++){
            sb.append(list.get(i));
        }
        return sb.toString();
    }

    /**
     * @description: SHA1生成签名
     * @param decript 微信加密签名
     * @return: java.lang.String
     * @author: cc
     * @date: 2020/7/9 19:16
     **/
    public static String SHA1(String decript) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(decript.getBytes());
            byte messageDigest[] = digest.digest();
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }



    /**
     * 验证回调签名
     * @return
     */
    public static boolean isTenpaySign(Map<String, String> map) {
        String characterEncoding = "utf-8";
        String charset = "utf-8";
        String signFromAPIResponse = map.get("sign");
        if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
            System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
            return false;
        }
        System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
        //过滤空 设置 TreeMap
        SortedMap<String, String> packageParams = new TreeMap();

        for (String parameter : map.keySet()) {
            String parameterValue = map.get(parameter);
            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }

        StringBuffer sb = new StringBuffer();
        Set es = packageParams.entrySet();
        Iterator it = es.iterator();

        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (!"sign".equals(k) && null != v && !"".equals(v)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + WxConfig.APP_KEY);

        //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
        //算出签名
        String resultSign = "";
        String tobesign = sb.toString();

        if (null == charset || "".equals(charset)) {
            resultSign = MD5Utils.MD5Encode(tobesign, characterEncoding).toUpperCase();
        } else {
            try {
                resultSign = MD5Utils.MD5Encode(tobesign, characterEncoding).toUpperCase();
            } catch (Exception e) {
                resultSign = MD5Utils.MD5Encode(tobesign, characterEncoding).toUpperCase();
            }
        }
        String tenpaySign = ((String) packageParams.get("sign")).toUpperCase();
        return tenpaySign.equals(resultSign);
    }


    /**
     * @description: 小程序解密手机号、unionId
     * @param encryptedData 需要解密的数据
     * @param session_key   用户session_key、密钥
     * @param iv   解密数据一起的数据
     * @return: com.alibaba.fastjson.JSONObject
     * @author: cc
     * @date: 2020/7/15 13:23
     */
    public static String getPhoneNumber(String encryptedData, String session_key, String iv) {
        // 被加密的数据
        byte[] dataByte = com.sun.org.apache.xerces.internal.impl.dv.util.Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = com.sun.org.apache.xerces.internal.impl.dv.util.Base64.decode(session_key);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                return new String(resultByte, "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("小程序解密手机号异常:" + e.getMessage());
        }
        return null;
    }

}

5.2 WxPublicConfig.java

package com.fsk.systemCust.misc.wx.config;

/**
 * @description: 微信公众号相关
 * @author: Cc
 * @data: 2020/7/8 15:46
 */
public class WxPublicConfig {

    /**  微信公众号接口配置测试号Token  */
    public final static String WX_PUBLIC_TOKEN = "123_TOKEN";

    /**  微信的测试号APPID */
    public final static String WX_APPID = "wxa5";

    /**  微信的测试号密钥 */
    public final static String WX_APPSECRET = "e75e";

    /** 获取access_token填写client_credential */
    public final static String WX_GRANT_TYPE = "client_credential";

    /** 前端重定向地址:要求和配置域名同地址,具体页面可以不同 */
    public final static String WX_REDIRECT_URI = "http://wghyqu.natappfree.cc/alipay/wappay/pay.jsp";

    /** 微信公众号全局access_token */
    public final static String WX_PUBLIC_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";

    /** 1.用户授权的URL */
    public final static String WX_USER_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";

    /** 2.通过code换取网页授权access_token */
    public final static String WX_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";

    /** 3.刷新用户授权的access_token,微信有效期为30天 */
    public final static String WX_REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";

    /** 4.拉取用户信息(需scope为 snsapi_userinfo) */
    public final static String WX_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo";


    public final static String WX_REDIS_GLOBAL_ACCESS_TOKEN = "wxGlobalAccessToken";


}

至此差不多完成,写的非常粗糙,个人只是自己记录一下。

本文地址:https://blog.csdn.net/mufeng633/article/details/107393765

相关标签: 微信 公众号