基于springboot+vue的微信公众号页面授权案例

微信授权登录是一个非常常见的场景,利用微信授权登录,我们可以很容易获取用户的一些信息,通过用户对公众号的唯一openid从而建立数据库绑定用户身份;
一、开发前准备
1、需要申请一个公众号(https://mp.weixin.qq.com/),拿到AppID和AppSecret;
2、进入公众号开发者中心页配置授权回调域名。具体位置:接口权限-网页服务-网页账号-网页授权获取用户基本信息-修改。注意,这里仅需填写全域名(例:www.qq.com),勿加 http:// 等协议头及具体的地址;
3、常用微信开发者工具,开发人员,可申请公众平台测试账号,直接体验和测试公众平台所有高级接口;可使用官方提供的web开发者工具直接在浏览器中进行调试;

二、微信公众平台(测试号)配置接口信息,接入微信公众号
开发者工具->公众平台测试账号->接口配置信息


接口配置信息.jpg

具体实现,新增Controller类

@Controller
@RequestMapping("/wx")
@Api(value = "MainController", description = "微收发主模块")
public class MainController extends BaseController {
    private String TOKEN = "wsf";
    /**
     * 接收并校验四个请求参数
     */
    @RequestMapping(value = "/chk", method = RequestMethod.GET)
    public void checkName(@RequestParam(name = "signature") String signature,
                          @RequestParam(name = "timestamp") String timestamp,
                          @RequestParam(name = "nonce") String nonce,
                          @RequestParam(name = "echostr") String echostr, HttpServletResponse response) throws IOException {
        System.out.println("-----------------------开始校验------------------------");
        //排序
        String sortString = sort(TOKEN, timestamp, nonce);
        //加密
        String myString = sha1(sortString);
        //校验
        if (myString != null && myString != "" && myString.equals(signature)) {
            System.out.println("签名校验通过");
            //如果检验成功原样返回echostr,微信服务器接收到此输出,才会确认检验完成。
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
            out.write(echostr.getBytes());
            out.flush();
            out.close();
        } else {
            System.out.println("签名校验失败");
        }
    }

   /**
     * 排序方法
     */
    public String sort(String token, String timestamp, String nonce) {
        String[] strArray = {token, timestamp, nonce};
        Arrays.sort(strArray);
        StringBuilder sb = new StringBuilder();
        for (String str : strArray) {
            sb.append(str);
        }
        return sb.toString();

    }

    /**
     * 将字符串进行sha1加密
     *
     * @param str 需要加密的字符串
     * @return 加密后的内容
     */
    public String sha1(String str) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(str.getBytes());
            byte messageDigest[] = digest.digest();
            // Create Hex String
            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 "";
    }
}

注意:SpringBoot项目中,一般会另外配置json解析器,使用@responsebody注解并且返回值类型为String时,返回的string字符串会带有双引号,导致微信接口验证不通过;可以通过返回HttpServletResponse 对象来解决;

三、通过网页授权机制获取用户信息
主要有3步:
1)引导用户进入授权页面同意授权,获取code;
2)通过code换取网页授权access_token;
3)拉取用户信息(需scope为 snsapi_userinfo);

  • snsapi_base:只能获取到用户openid。好处是静默认证,无需用户手动点击认证按钮,感觉上像是直接进入网站一样。
  • snsapi_userinfo:可以获取到openid、昵称、头像、所在地等信息。需要用户手动点击认证按钮。

1、Vue微信授权插件
wechatAuth.js

class wechatAuth {
    constructor(config) {
        let defaultConfig = {
            appid: '',
            responseType: 'code',
            redirect_uri: '',
            error_uri: '',
            scope: 'snsapi_base ',
            getCodeCallback: () => { },
        }
        this.config = Object.assign(defaultConfig, config)
    }

    //调取微信获取code接口
    getCode() {
        let authPageBaseUri = 'https://open.weixin.qq.com/connect/oauth2/authorize';
        let authParams = `?appid=${this.config.appid}&redirect_uri=${this.config.redirectUri}&response_type=${this.config.responseType}&scope=${this.config.scope}#wechat_redirect`;
        window.location.href = authPageBaseUri + authParams;
    }

    next(next) {
        return (to, code) => {
            if (code) {
                window.sessionStorage.setItem('wxcode', code);
                to ? next(to) : next();
            } else {
                to && next(to);
            }
        }
    }

    getCodeCallback(next, code) {
        return this.config.getCodeCallback(this.next(next), code);
    }
}
export default wechatAuth;

index.js

import wechatAuth from './wechatAuth';
import url from 'url'
import querystring from 'querystring';
export default {
    install(Vue, options) {
        let router = options.router;
        let wechatPlugin = new wechatAuth(options)
        if (!router) return false;
        //绑定到路由上
        router.beforeEach((to, from, next) => {
            if (to.matched.some(record => record.meta.wechatAuth)) {// 判断是否需要授权
                let query = querystring.parse(url.parse(window.location.href).query);
                let code = query.code;
                if (window.sessionStorage.getItem('wxcode')) {// 判断是否已经有授权
                    next();
                } else if (code) {
                    wechatPlugin.getCodeCallback(next, code);
                } else { //去获取code
                    wechatPlugin.getCode();
                }
            } else {
                next();
            }
        });
    }
}

vueWechatAuth.js

//入口文件
let vueWechatAuth = require('./component/index')
module.exports = vueWechatAuth

2、Vue微信授权插件初始化
在router.js文件,进行相关操作,并将code传递给后端;

// 微信授权插件初始化
Vue.use(wechatPlugin, {
  router, // 路由实例对象
  appid: 'wxad2cb5ca6f48a285', // 您的微信appid
  responseType: 'code', // 返回类型,请填写code
  scope: 'snsapi_userinfo', // 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
  redirectUri: 'http://pcd.ngrok.i668.top', //微信回调地址
  getCodeCallback(next, code) {
    // 用户同意授权后回调方法
    // code:用户同意授权后,获得code值
    // code说明: code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
    // next: 无论access_token是否获取成功,一定要调用该方法
    // next说明:next方法接收两个参数
    // 参数1(必填,切换到的路由地址,空字符串为当前路由,指定切换对象 next('/') 或者 next({ path: '/' })
    // 参数2为通过code值请求后端获取access_token的结果,true或者false,默认为false
    Api.post('/mobile/wx/auth', {
      code1: code,
      state1: ''
    }, response => {
      let data = response.data;
      if (data.isSucc) {
        if (data.obj != null){
          localStorage.setItem(
            "userInfoData",
            JSON.stringify(data.obj)
          );
          localStorage.setItem("accessToken", data.obj.ticket);
          next('/main', code); // 获取access_toeken成功,转至主页
        }else{
          next('', code); // 获取access_toeken成功
        }
        
      } else {
        next('/login'); // 获取access_token失败
      }
    });
  }
})

3、Java后端,通过code换取网页授权access_token;

    @RequestMapping(value = "/auth", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult wechatLogin(@RequestBody PostRequest request) throws Exception {
        ResponseResult result = new ResponseResult();
        if (request == null || request.body == null)
            return  result;
        String code1 = request.body.getString("code1");
        String state1 = request.body.getString("state1");
        // 1. 用户同意授权,获取code
        logger.info("收到微信重定向跳转.");
        logger.info("用户同意授权,获取code1:{} , state1:{}", code1, state1);

        // 2. 通过code换取网页授权access_token
        if (code1 != null || !(code1.equals(""))) {

            String APPID = WX_APPID;
            String SECRET = WX_APPSECRET;
            String CODE = code1;
            String WebAccessToken = "";
            String openId = "";
            String nickName, sex, openid = "";
            String REDIRECT_URI = "http://pcd.ngrok.i668.top";
            String SCOPE = "snsapi_userinfo";

           /* String getCodeUrl = UserInfoUtil.getCode(APPID, REDIRECT_URI, SCOPE);
            logger.info("第一步:用户授权, get Code URL:{}", getCodeUrl);*/

            // 替换字符串,获得请求access token URL
            String tokenUrl = UserInfoUtil.getWebAccess(APPID, SECRET, CODE);
            logger.info("第二步:get Access Token URL:{}", tokenUrl);

            // 通过https方式请求获得web_access_token
            String response = HttpsUtil.httpsRequestToString(tokenUrl, "GET", null);

            JSONObject jsonObject = JSON.parseObject(response);
            logger.info("请求到的Access Token:{}", jsonObject.toJSONString());

            if (null != jsonObject) {
                try {
                    WebAccessToken = jsonObject.getString("access_token");
                    openId = jsonObject.getString("openid");
                    logger.info("获取access_token成功!");
                    logger.info("WebAccessToken:{} , openId:{}", WebAccessToken, openId);

                    // 3. 使用获取到的 Access_token 和 openid 拉取用户信息
                    String userMessageUrl = UserInfoUtil.getUserMessage(WebAccessToken, openId);
                    logger.info("第三步:获取用户信息的URL:{}", userMessageUrl);

                    // 通过https方式请求获得用户信息响应
                    String userMessageResponse = HttpsUtil.httpsRequestToString(userMessageUrl, "GET", null);

                    JSONObject userMessageJsonObject = JSON.parseObject(userMessageResponse);

                    logger.info("用户信息:{}", userMessageJsonObject.toJSONString());

                    if (userMessageJsonObject != null) {
                        if ("40001".equals(userMessageJsonObject.getString("errcode"))){
                            result.setIsSucc(Boolean.FALSE);
                            result.setCode(ResultCode.ACCESS_TOKEN__INVALID_CODE);
                            result.setMsg(ResultCode.ACCESS_TOKEN__INVALID_MSG);
                            return result;
                        }
                        //用户昵称
                        nickName = userMessageJsonObject.getString("nickname");
                        //用户性别
                        sex = userMessageJsonObject.getString("sex");
                        sex = (sex.equals("1")) ? "男" : "女";
                        //用户唯一标识
                        openid = userMessageJsonObject.getString("openid");

                        logger.info("用户昵称:{}", nickName);
                        logger.info("用户性别:{}", sex);
                        logger.info("OpenId:{}", openid);

                        //根据OpenId获取用户信息
                        UserVO userVO = wxUserService.findUserByOpenID(openid);
                        if (userVO != null){
                            UserInfo userInfo = createTokenForUserInfo(userVO.getName(),userVO.getPassword(),false);
                            result.setObj(userInfo);
                        }
                    }
                } catch (JSONException e) {
                    logger.error("获取Web Access Token失败");
                    result.setIsSucc(Boolean.FALSE);
                    result.setCode(ResultCode.JSSON_TRANSFER_ERROR_CODE);
                    result.setMsg(ResultCode.JSSON_TRANSFER_ERROR_MSG);
                }
            }
        }
        result.setIsSucc(Boolean.TRUE);
        result.setCode(ResultCode.SUCCESS_CODE);
        result.setMsg(ResultCode.SUCCESS_MSG);
        return result;
    }

HttpsUtil.java

package top.i668.mobile.util;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.*;
import java.net.URL;

public class HttpsUtil {

    /**
     * 以https方式发送请求并将请求响应内容以String方式返回
     *
     * @param path   请求路径
     * @param method 请求方法
     * @param body   请求数据体
     * @return 请求响应内容转换成字符串信息
     */
    public static String httpsRequestToString(String path, String method, String body) {
        if (path == null || method == null) {
            return null;
        }
        String response = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        HttpsURLConnection conn = null;
        try {
            // 创建SSLConrext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = {new JEEWeiXinX509TrustManager()};
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 从上述对象中的到SSLSocketFactory
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            System.out.println(path);
            URL url = new URL(path);
            conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            //设置请求方式(git|post)
            conn.setRequestMethod(method);
            //有数据提交时
            if (null != body) {
                OutputStream outputStream = conn.getOutputStream();
                outputStream.write(body.getBytes("UTF-8"));
                outputStream.close();
            }
            // 将返回的输入流转换成字符串
            inputStream = conn.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
            bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }

            response = buffer.toString();
        } catch (Exception e) {
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
            try {
                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
            } catch (IOException execption) {
            }
        }
        return response;
    }
}

UserInfoUtil.java

 // 2.获取Web_access_tokenhttps的请求地址
    public static String Web_access_tokenhttps = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";

    // 替换字符串
    public static String getWebAccess(String APPID, String SECRET, String CODE) {
        return String.format(Web_access_tokenhttps, APPID, SECRET, CODE);
    }

    // 3.拉取用户信息的请求地址
    public static String User_Message = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";

    // 替换字符串
    public static String getUserMessage(String access_token, String openid) {
        return String.format(User_Message, access_token, openid);
    }

你可能感兴趣的:(基于springboot+vue的微信公众号页面授权案例)