zoukankan      html  css  js  c++  java
  • 微信开放平台开发第三方授权登陆(二):PC网页端

    本文转载自:https://blog.csdn.net/qq_34190023/article/details/81185143

    微信开放平台第三方授权登陆开发文档(PC网页端)

    当微信开放平台开发第三方授权登陆(一):开发前期准备完成后,已经获取到应用的AppID和AppSecret、且已经成功申请到微信登陆功能。可以进行第三方登陆授权开发。

    网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统。

    一、需求

    根据需求,需要拥有第三方微信登录功能,并获取到用户信息。

    二、开发流程

    1.网站应用:(微信客户端扫码授权登陆)

    1)第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;

    2)通过code参数加上AppID和AppSecret等,通过API换取access_token;

    3)通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

    网站应用第三方授权登陆获取用户信息 

    三、开发使用的技术及工具

    1、使用IDEA2017.2进行开发

    2、使用SpringBoot进行快速开发

    3、使用redis进行缓存。

    4、使用fastJson对json数据进行处理

    5、使用Maven对项目进行管理

    四、具体实现步骤

    1、网站应用

    创建工程

    打开IDEA,新建一个工程,选择Spring Initializr,Next。然后填写工程基本信息

    选择SpringBoot的版本已经需要添加的依赖,也可以直接跳过,手动添加依赖。由于是web工程,需要添加web相关依赖。模板引擎采用springboot推荐的thymeleaf。最后点击确认,进入工程。 

    添加相关依赖:

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.5</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.4</version>
    </dependency>
    <!-- 添加httpclient支持 -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.2</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.8.1</version>
        <type>jar</type>
    </dependency>

    添加配置文件

    SpringBoot默认会将resources下的application名字开头的properties作为配置文件。所以只需要添加application.properties就可以了

    由于是使用thymeleaf作为模板引擎。可以添加相应的配置:

    #thymelea模板配置
    spring.thymeleaf.prefix=classpath:/templates/
    spring.thymeleaf.suffix=.html
    spring.thymeleaf.mode=HTML5
    spring.thymeleaf.encoding=UTF-8
    spring.thymeleaf.content-type=text/html
    spring.thymeleaf.cache=false
    spring.resources.chain.strategy.content.enabled=true
    spring.resources.chain.strategy.content.paths=/**

    为了让系统更具有通用性,分别创建application-dev.properties、application-prod.properties对不同的环境进行配置。

    在application.properties中,使用spring.profiles.active进行配置环境的选择。

    如:spring.profiles.active = prod


    如:application-prod.properties

    ## 微信开放平台
    # APPID
    wechat.open.appid =
    # APPSECRET
    wechat.open.appsecret =
    # 回调地址
    wechat.open.redirect_uri =

    新建javaBean:WeChatUserInfo.java

    @Getter @Setter @ToString
    public class WeChatUserInfo {
        String openid;
        String nickname;
        Integer sex;
        String province;
        String city;
        String country;
        String headimgurl;
        String privilege;
        String unionid;
     
    }

    新建WeChatOpenLoginController.java实现微信开放平台第三方登录的主要逻辑。

    根据文档进行编写代码。

    1)请求获取Code

    前提:应用已经获取相应的网页授权作用域(scope=snsapi_login)

    开发:第三方网站引导用户打开链接

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

    参数说明:

    注意:若提示“该链接无法访问”,请检查参数是否填写错误,如redirect_uri的域名与审核时填写的授权域名不一致或scope不为snsapi_login。 

    @RequestMapping("/login")
        public String openWeChatLogin(HttpServletRequest httpServletRequest) {
            // 防止csrf攻击(跨站请求伪造攻击)
            String state = UUID.randomUUID().toString().replaceAll("-", "");
            // 采用redis等进行缓存state 使用sessionId为key 30分钟后过期,可配置
            RedisPoolUtil.setEx("wechat-open-state-" + httpServletRequest.getSession().getId(), state, Integer.parseInt(env.getProperty("wechat.open.exTime", "1800")));
            String url = "https://open.weixin.qq.com/connect/qrconnect?" +
                    "appid=" +
                    env.getProperty("wechat.open.pc.appid").trim() +
                    "&redirect_uri=" +
                    env.getProperty("application.url") +
                    env.getProperty("wechat.open.pc.redirect_uri").trim() +
                    "&response_type=code" +
                    "&scope=snsapi_login" +
                    "&state=" +
                    state +     // 由后台自动生成
                    "#wechat_redirect";
            return "redirect:" + url;
        }

    2)用户同意授权与否

    用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数

    redirect_uri?code=CODE&state=STATE

    若用户禁止授权,则不会重定向到我们提供的回调地址中

    成功授权后,将获得Code,通过Code可以获取access_token

    3)获取access_token

    通过上述方法获取的code获取access_token.

    Http Get请求

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

    参数说明:

    请求后,返回成功的json串为(样例):

    {
        "access_token":"ACCESS_TOKEN",        // 接口调用凭证
        "expires_in":7200,                      // access_token接口调用凭证超时时间,单位(秒)
        "refresh_token":"REFRESH_TOKEN",       //用户刷新access_token
        "openid":"OPENID",                                  //授权用户唯一标识
        "scope":"SCOPE",                                     //用户授权的作用域,使用逗号(,)分隔
        "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"  //当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段
    }

    失败返回的样例:

    {
        "errcode": 40029,
        "errmsg": "invalid code"
    }

    失败可能原因:  暂时不明

    @RequestMapping("/callback/pc")
        public String openWeChatCallback(HttpServletRequest httpServletRequest, Model model) {
            String code = httpServletRequest.getParameter("code");
            String state = httpServletRequest.getParameter("state");
            String url = null;
            // 判断state是否合法
            String stateStr = RedisPoolUtil.get("wechat-open-state-" + httpServletRequest.getSession().getId());
            if (StringUtils.isEmpty(code) || StringUtils.isEmpty(stateStr) || !state.equals(stateStr)) {
                throw new WechatParamException("非法参数,请重新登陆", "/");
            }
            url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
                    "appid=" +
                    env.getProperty("wechat.open.pc.appid").trim() +
                    "&secret=" +
                    env.getProperty("wechat.open.pc.appsecret").trim() +
                    "&code=" +
                    code +
                    "&grant_type=authorization_code";
            JSONObject wechatAccessToken = HttpClientUtils.httpGet(url);
            if (wechatAccessToken.get("errcode") != null) {
                throw new WechatParamException("获取accessToken失败", "/wechat/open/login");
            }
            String accessToken = (String) wechatAccessToken.get("access_token");
            String openid = (String) wechatAccessToken.get("openid");
            String unionid = (String) wechatAccessToken.get("unionid");
            if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openid) || StringUtils.isEmpty(unionid)) {
                throw new WechatParamException("获取accessToken失败", "/wechat/open/login");
            }
            // TODO:根据Openid或Unionid对数据库进行查询,如果查询到对应的用户数据,则不需要再向微信服务器发送请求去返回数据。
            // TODO: 建议使用Unionid作为查询条件。
            WeChatUserInfo weChatUserInfo = null;
            wechatAccessToken = null// FIXME: 这里应该是从数据库中查询获取用户信息逻辑。
            if (wechatAccessToken == null) {
                // 新用户
                weChatUserInfo = getUserInfoByAccessToken(accessToken);
                // 数据库插入的操作
            }
            if (weChatUserInfo != null) {
                model.addAttribute(weChatUserInfo);
                return "wechatUser";
            }
            throw new WechatParamException("获取用户信息失败", "/wechat/open/login");
        }

    4)通过access_token调用接口获取用户个人信息(UnionID机制)

    前提:

    1. access_token有效且未超时;

    2. 微信用户已授权给第三方应用帐号相应接口作用域(scope)。

    Http Get请求:

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

    返回成功的json结果(样例): 

    {
        "openid":"OPENID",        //普通用户的标识,对当前开发者帐号唯一
        "nickname":"NICKNAME",   //普通用户昵称
        "sex":1,                                //普通用户性别,1为男性,2为女性
        "province":"PROVINCE",      //普通用户个人资料填写的省份
        "city":"CITY",                        //普通用户个人资料填写的城市
        "country":"COUNTRY",         //国家,如中国为CN
        "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",       //用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
        "privilege":[   //用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
            "PRIVILEGE1",
            "PRIVILEGE2"
        ],
        "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"   //用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的
    }

    失败JSON样例:

    {
        "errcode": 40003,
        "errmsg": "invalid openid"
    }

    注意:在用户修改微信头像后,旧的微信头像URL将会失效,因此开发者应该自己在获取用户信息后,将头像图片保存下来,避免微信头像URL失效后的异常情况

    最好保存用户unionID信息,以便以后在不同应用中进行用户信息互通。

    private WeChatUserInfo getUserInfoByAccessToken(String accessToken) {
            if (StringUtils.isEmpty(accessToken)) {
                return null//"accessToken为空";
            }
            String get_userInfo_url = null;
            get_userInfo_url = "https://api.weixin.qq.com/sns/userinfo?" +
                    "access_token=" +
                    accessToken +
                    "&openid=" +
                    env.getProperty("wechat.open.pc.appid").trim();
            String userInfo_result = HttpClientUtils.httpGet(get_userInfo_url, "utf-8");
            if (!userInfo_result.equals("errcode")) {
                WeChatUserInfo weChatUserInfo = JSON.parseObject(userInfo_result, new TypeReference<WeChatUserInfo>() {
                });
                // TODO: 需要把头像信息下载到文件服务器,然后替换掉头像URL。微信的或许不可靠,假设微信用户更换了头像,旧头像URL是否会保存?而这个URL信息却存放在我们的数据库中,不可靠
                return weChatUserInfo;
            }
            return null//"获取用户信息失败"
        }

    5)刷新access_token

    由于access_token有效期(目前为2个小时)较短,当access_token超时后,可以使用refresh_token进行刷新,access_token刷新结果有两种:

    1. 若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;
    2. 若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。

    refresh_token拥有较长的有效期(30天),当refresh_token失效的后,需要用户重新授权。

    请求方法:

    获取第一步的code后,请求以下链接进行refresh_token:

    https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN

    参数说明:

    成功返回的结果: 

    {
        "access_token":"ACCESS_TOKEN",    //接口调用凭证
        "expires_in":7200,                             // access_token接口调用凭证超时时间,单位(秒)
        "refresh_token":"REFRESH_TOKEN",   //用户刷新access_token
        "openid":"OPENID",                          //授权用户唯一标识
        "scope":"SCOPE"                              //用户授权的作用域,使用逗号(,)分隔
    }

    失败样例:

    {
        "errcode": 40030,
        "errmsg": "invalid refresh_token"
    }

    注意:

    1、Appsecret 是应用接口使用密钥,泄漏后将可能导致应用数据泄漏、应用的用户数据泄漏等高风险后果;存储在客户端,极有可能被恶意窃取(如反编译获取Appsecret);
    2、access_token 为用户授权第三方应用发起接口调用的凭证(相当于用户登录态),存储在客户端,可能出现恶意获取access_token 后导致的用户数据泄漏、用户微信相关接口功能被恶意发起等行为;
    3、refresh_token 为用户授权第三方应用的长效凭证,仅用于刷新access_token,但泄漏后相当于access_token 泄漏,风险同上。

    建议将secret、用户数据(如access_token)放在App云端服务器,由云端中转接口调用请求。

    五、测试结果

    1、网站应用

    首先开启redis。然后运行代码。由于SpringBoot会使用内置的Tomcat容器进行管理,直接运行就可以了,然后点击微信登录。弹出扫码登录窗口,手机扫码同意登录后就可以获取到用户信息了.

    六、应用关键参数位置

    APPID和APPSecret以及回调地址位置,注:需要修改授权回调域,即回调地址 

  • 相关阅读:
    xadmin可视化上传图片
    home数据库设计
    xadmin后台管理
    静态目录
    Git线上操作
    python 学习之JavaScript
    python学习之CSS
    python学习之HTML
    Python之旅(day10&day11 各种运算及基本数据类型)
    python之旅(第一课day9)
  • 原文地址:https://www.cnblogs.com/alimayun/p/12427032.html
Copyright © 2011-2022 走看看