当前位置:首页 > 微信公众号 > 正文内容

PC端实现微信第三方登录

关中浪子7个月前 (11-22)微信公众号255
【腾讯云】2核2G4M云服务器新老同享99元/年,续费同价

一、主要流程

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


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


微信开放文档


准备工作 | 微信开放文档


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


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,是需要获取的,打开链接,扫码登录


微信公众平台




2、 关注公众号

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


3、设置微信回调域名 

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


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




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



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


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


三、代码实现

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




核心的代码是: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<T> {
 
    /**
     * 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;
    }
}




找梯子最重要的就是稳定,这个已经上线三年了,一直稳定没有被封过,赶紧下载备用吧!

扫描二维码推送至手机访问。

版权声明:本文由码农翻生发布,如需转载请注明出处。

本文链接:https://lubojian.cn/post/256.html

分享给朋友:

相关文章

如何获取微信公众号openid

方法一:用户主动授权1. 在公众号中添加网页授权的域名(在公众号设置中配置);2. 引导用户点击相关链接或按钮;3. 用户点击后,会跳转到你配置的网页;4. 你可以通过微信网页授权接口获取到用户的openid。<template>...

微信snsapi_base静默授权与snsapi_userinfo 的区别

微信snsapi_base静默授权与snsapi_userinfo 的区别

nsapi_base只能获取access_token和openID,流程走完即终止snsapi_userinfo可以获取更详细的用户资料,比如头像、昵称、性别等一,当 scope=snsapi_userinfo时;参考微信开发文档:http...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。