当前位置: 移动技术网 > IT编程>开发语言>Java > 微信公众号--授权相关

微信公众号--授权相关

2020年07月17日  | 移动技术网IT编程  | 我要评论

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网