对接微信公众(开放)平台,获取微信用户信息,实现第三方登录

一、主要流程

        实现第三方登录,从微信获取用户信息,用微信公众平台和微信开放平台,其实流程和原理都是一样的,就是调用的接口和对应的参数有点区别。

微信公众平台 和 微信开放平台 对应的官方文档如下:

微信开放文档

准备工作 | 微信开放文档

 本次用 公众平台 来测试,主要流程如下:

1、重定向微信接口,微信扫码确认

点击“微信登录”按钮,系统接口返回一个跳转地址(地址微信公众平台提供的,部分参数需要拼接,公众平台的接口只能用微信打开)如下,参数的详细介绍可以参考官网:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

注意:

1.redirect_uri后面携带的参数是微信的回调地址,用户点击“同意”后,微信会请求这个地址,需要用 URLEncoder.encode(path,"utf-8") 进行编码

 2、微信扫码确认后,调用回调接口

用手机微信访问上面返回的完整接口后,会提示是否同意登录,点击确认后,微信端会回调第一步中,接口里面设置的回调地址,即redirect_uri,并且携带code和state参数,如下:

redirect_uri?code=CODE&state=STATE

回调方法中,需要获取code以及state

注意:

1.验证当前state是否和跳转微信接口传递的state一致,来防止csrf攻击(可以不进行验证

2.可以通过state来传递我们自己需要的参数 

举例:设置的回调函数为:redirect_uri=http://www.aaa.com/getWeChatUserInfo

那么需要在后台定义一个接口,用来接受微信的回调,如下:

    @RequestMapping("/getWeChatUserInfo")
    public String getWeChatUserInfo(HttpServletRequest request){
        //校验state
        String state = request.getParameter("state");
        //微信返回的code授权码
        String code = request.getParameter("code");

        //请求token
        WeChatAccessToken token = weChatLoginService.getAccessTokenByCode(code);
        //根据token获取用户信息
        WeChatUserInfo userInfo = weChatLoginService.getWeixinUserInfo(token);

        System.out.println(JSON.toJSONString(userInfo));

        /**
         * 可以在此处,根据业务需求开发自己的功能
         */

        //重定向到其他页面
        return "redirect:http://www.baidu.com";
    }

3、根据code获得微信的access_token

上一步获取code后,携带code和appId等参数,调用微信接口,获得access_token,微信接口:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

 返回结果如下:

{
    "access_token": "52_mgvyGIVQGJoVw-JUo-3GeCQggL5g11rtduGu9lwMOp5sZOIanEVH9rpuabAu9oo8nu89TcZszwz10eRq5DDDdoIuXZGPyfMEdcynGP1gS0U",
    "expires_in": 7200,
    "refresh_token": "52_K6X9KzoYPlTUem-MCH05X0KCIzikZy-UiX4XdxhSW-pUgWjlAVn8BDGTfwmQ4mk1Bdhd0-SSxmP1f5HQYecxWBdcGLa7UhFer1pvDzUs1gU",
    "openid": "oGy6Y5tp9qxP5XLBvRU2OCIxFdvQ",
    "scope": "snsapi_userinfo"
}

4、获取到token后,再去调用微信接口,获取微信端用户的信息

https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID

 返回结果如下:

{
    "openid": "oGy6Y5tp9qxP5XLBvRU2OCIxFdvQ",
    "nickname": "A→枫火",
    "sex": 0,
    "language": "",
    "city": "",
    "province": "",
    "country": "",
    "headimgurl": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKr27Hpfq957HPIXHoReLn1OvVXWFchNdW1UiaicBOugzz5ax7Hn8VzGceC50NRRQ02ibhA4ibFXZX3zg/132",
    "privilege": []
}

二、准备工作

微信公众平台提供测试账号,而开放平台必须要有开发者资质认证,所以用公众平台来测试

1、获取APPID和appsecret 

上面提到的一些参数,appId和appsecret,是需要获取的,打开链接,扫码登录

微信公众平台

对接微信公众(开放)平台,获取微信用户信息,实现第三方登录_第1张图片

2、 关注公众号

公众平台,转发的请求地址,只能用手机微信打开,而访问请求,必须先关注下面的测试公众号

对接微信公众(开放)平台,获取微信用户信息,实现第三方登录_第2张图片3、设置微信回调域名 

通过:网页服务-网页帐号-网页授权获取用户基本信息,点击修改,进行回调域名的配置

设置回调函数的时候,域名必须要和这里设置的域名相同

对接微信公众(开放)平台,获取微信用户信息,实现第三方登录_第3张图片

4、设置花生壳的内网穿透

对接微信公众(开放)平台,获取微信用户信息,实现第三方登录_第4张图片

 因为在设置微信回调接口的时候,微信端是要访问我们设置的回调接口的,回调接口中的域名要和第三步中设置的域名保持一致

因此使用花生壳设置内网穿透,保证手机微信在调用此域名的时候,能够访问我们电脑端上的服务

三、代码实现

1、先创建一个springboot项目,结构如下

对接微信公众(开放)平台,获取微信用户信息,实现第三方登录_第5张图片

核心的代码是:ThirdLoginWeChatController 和 WeChatLoginService,其他的都是配置和工具类

 2、具体对应的代码

application.yml,配置信息,

server:
  port: 9200
oauth:
  weixin:
    #微信公众平台的应用id
    appID: 
    #微信公众平台的应用秘钥
    appsecret: 
    #获取授权码的URL
    authorizeUrl: https://open.weixin.qq.com/connect/oauth2/authorize
    #获取令牌的URL
    tokenUrl: https://api.weixin.qq.com/sns/oauth2/access_token
    #获取用户信息的URL
    userInfoUrl: https://api.weixin.qq.com/sns/userinfo
    #回调地址的URL
    backUrl: http://273mc88979.zicp.vip/wechat/getWeChatUserInfo

WeChatConfig,配置类,获取并封装配置文件中的信息,包括APPID,appsecret,访问微信的接口和回调域名等

@Data
@Configuration
@ConfigurationProperties(prefix = "oauth.weixin")
public class WeChatConfig {
    //微信公众平台的应用id
    private String appID;
    //微信公众平台的应用秘钥
    private String appsecret;
    //获取授权码的URL
    private String authorizeUrl;
    //获取令牌的URL
    private String tokenUrl;
    //获取用户信息的URL
    private String userInfoUrl;
    //回调地址的URL
    private String backUrl;
}

WeChatConstant,定义一些常量,主要是发送微信请求所需要的参数常量

@Data
public class WeChatConstant {
    public static final String SNSAPI_USERINFO = "snsapi_userinfo";
    public static final String RESPONSE_TYPE_CODE = "code";
    public static final String AUTHORIZATION_CODE = "authorization_code";

    //作为key,用来在redis中保存state
    public static final String STATE_KEY = "state_key";
}

WeChatAccessToken,实体类,封装微信返回的accesstoken

@Data
public class WeChatAccessToken {
    //授权用户唯一标识
    private String openid;
    //接口调用凭证
    private String access_token;
    //用户刷新access_token
    private String refresh_token;
    //用户授权的作用域,使用逗号(,)分隔
    private String scope;
    //凭证超时时间,单位(秒)
    private String expires_in;
    //当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。
    private String unionid;
}

WeChatUserInfo,实体类,封装微信返回的用户信息

@Data
public class WeChatUserInfo {
    //授权用户唯一标识
    private String openId;
    //昵称
    private String nickName;
    //性别,1:男,2:女,0:未知
    private String sex;
    //头像地址
    private String headImgUrl;
    //国籍
    private String country;
    //省
    private String province;
    //城市
    private String city;
    //语言
    private String language;
    //特权
    private String privilege;
    //关联内部用户的id
    private String systemUserId;
    //关联内部用户的用户名
    private String systemUserName;
}

ThirdLoginWeChatController,controller,对外提供的访问接口和微信回调接口

@Controller
@RequestMapping("/wechat")
public class ThirdLoginWeChatController {
    @Autowired
    WeChatLoginService weChatLoginService;

    /**
     * 点击第三方登录,使用微信登录
     * 跳转到微信扫码页面
     */
    @GetMapping("/toLogin")
    @ResponseBody
    public String login(){
        //获取跳转的扫码地址
        String redirectUrl = weChatLoginService.getWeixinQRUrl();

        return redirectUrl;
    }

    /**
     * 回调函数,用户扫码同意后,跳转到该接口,并携带code和state
     * 1.获取code和state
     * 2.通过code获取token
     * 3.再根据token获取微信端的用户信息
     */
    @RequestMapping("/getWeChatUserInfo")
    @ResponseBody
    public String getWeChatUserInfo(HttpServletRequest request){
        //校验state
        String state = request.getParameter("state");
        //微信返回的code授权码
        String code = request.getParameter("code");
        System.out.println("===================code:"+request.getParameter("code"));
        System.out.println("===================state:"+request.getParameter("state"));

        //从redis获取state信息,与回调接受的state对比,可用于防止csrf攻击(跨站请求伪造攻击)
//        String realState = redisService.getCacheObject(WeChatConstant.STATE_KEY);
//        if(!state.equals(realState)){
//            throw new ServiceException("微信回调地址,返回的state不一致");
//        }

        //请求token
        WeChatAccessToken token = weChatLoginService.getAccessTokenByCode(code);
        //根据token获取用户信息
        WeChatUserInfo userInfo = weChatLoginService.getWeixinUserInfo(token);

        System.out.println(JSON.toJSONString(userInfo));

        /**
         * 可以在此处,根据业务需求开发自己的功能
         * 如:关联本地账户,用来实现登录逻辑
         *      1.本地有账户,直接登录,返回token
         *      2.本地没有账户,需要跳转页面,用户注册后绑定微信
         */

        return JSON.toJSONString(userInfo);
        //重定向到其他页面
        //return "redirect:http://www.baidu.com";
    }
}

WeChatLoginService,service,用来获取token和用户信息等业务操作

@Service
public class WeChatLoginService {

    @Autowired
    WeChatConfig authWinxinConfig;

    /**
     * 获取第三方登录页面,即显示微信扫码页面的地址
     */
    public String getWeixinQRUrl(){
        //String redirectUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";

        //处理一下回调地址
        String backUrl = null;
        try {
            String path = authWinxinConfig.getBackUrl();
            backUrl = URLEncoder.encode(path,"utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        //生成随机的UUID,作为state,微信平台回调本地接口时,会携带此参数,可以校验此参数,可用于防止csrf攻击(跨站请求伪造攻击)
//        String state = UUID.randomUUID().toString();
//        redisService.setCacheObject(WeChatConstant.STATE_KEY,state);

        //拼接跳转的扫码地址
        StringBuffer redirectUrl = new StringBuffer();
        redirectUrl.append(authWinxinConfig.getAuthorizeUrl())
                .append("?appid=").append(authWinxinConfig.getAppID())
                .append("&redirect_uri=").append(backUrl)
                .append("&scope=").append(WeChatConstant.SNSAPI_USERINFO)
                .append("&response_type=").append(WeChatConstant.RESPONSE_TYPE_CODE)
                .append("&state=").append("123")
                .append("#wechat_redirect");

        return redirectUrl.toString();
    }

    /**
     * 根据code获取token和openid
     */
    public WeChatAccessToken getAccessTokenByCode(String code){
        //https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
        StringBuffer tokenUrl = new StringBuffer();
        tokenUrl.append(authWinxinConfig.getTokenUrl())
                .append("?appid=").append(authWinxinConfig.getAppID())
                .append("&secret=").append(authWinxinConfig.getAppsecret())
                .append("&code=").append(code)
                .append("&grant_type=").append(WeChatConstant.AUTHORIZATION_CODE);

        String result = HttpUtils.get(tokenUrl.toString());
        System.out.println(result);
        WeChatAccessToken token = JSON.parseObject(result, WeChatAccessToken.class);

        return token;
    }

    /**
     * 根据token和openID获取用户信息
     */
    public WeChatUserInfo getWeixinUserInfo(WeChatAccessToken token){
        //https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
        StringBuffer userInfoUrl = new StringBuffer();
        userInfoUrl.append(authWinxinConfig.getUserInfoUrl())
                .append("?access_token=").append(token.getAccess_token())
                .append("&openid=").append(token.getOpenid());

        String userStr = HttpUtils.get(userInfoUrl.toString());
        System.out.println(userStr);
        WeChatUserInfo weChartUserInfo = JSON.parseObject(userStr, WeChatUserInfo.class);

        return weChartUserInfo;
    }
}

HttpUtils,简单的工具类,封装了HttpClient方法的实现

public class HttpUtils {

    /**
     * get请求返回JSONObject
     * @param url
     * @return
     */
    public static JSONObject getJSON(String url){
        String resultStr = get(url);
        JSONObject res = JSONObject.parseObject(resultStr);
        return res;
    }

    /**
     * post请求返回JSONObject
     * @param url
     * @return
     */
    public static JSONObject postJSON(String url, String jsonStr){
        String resultStr = post(url, jsonStr);
        JSONObject res = JSONObject.parseObject(resultStr);
        return res;
    }

    /**
     * 发送 get 请求
     *
     * @param url 请求地址
     * @return 请求结果
     */
    public static String get(String url) {
        String result = null;
        CloseableHttpResponse response = null;
        CloseableHttpClient httpclient = HttpClients.createDefault();
        try {
            // 创建uri
            URIBuilder builder = new URIBuilder(url);
            URI uri = builder.build();
            // 创建http GET请求
            HttpGet httpGet = new HttpGet(uri);
            // 执行请求
            response = httpclient.execute(httpGet);
            if (response.getStatusLine().getStatusCode() == 200) {
                result = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 发送 post 请求
     *
     * @param url     请求地址
     * @param jsonStr Form表单json字符串
     * @return 请求结果
     */
    public static String post(String url, String jsonStr) {
        // 创建httpClient
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建post请求方式实例
        HttpPost httpPost = new HttpPost(url);

        // 设置请求头 发送的是json数据格式
        httpPost.setHeader("Content-type", "application/json;charset=utf-8");
        httpPost.setHeader("Connection", "Close");

        // 设置参数---设置消息实体 也就是携带的数据
        StringEntity entity = new StringEntity(jsonStr, Charset.forName("UTF-8"));
        // 设置编码格式
        entity.setContentEncoding("UTF-8");
        // 发送Json格式的数据请求
        entity.setContentType("application/json");
        // 把请求消息实体塞进去
        httpPost.setEntity(entity);
        // 执行http的post请求
        CloseableHttpResponse httpResponse;
        String result = null;
        try {
            httpResponse = httpClient.execute(httpPost);
            result = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }
}

你可能感兴趣的:(微信平台对接,微信公众平台,微信)