zoukankan      html  css  js  c++  java
  • SpringBoot+Vue实现第三方QQ登录(二)

    1 准备工作_OAuth2.0

    本步骤的作用

      接入QQ登录前,网站需首先进行申请,获得对应的appid与appkey,以保证后续流程中可正确对网站与用户进行验证与授权。

    本步骤在整个流程中的位置
    oauth2.0_guid_1.png

    1.1 申请appid和appkey

      appid:应用的唯一标识。在OAuth2.0认证过程中,appid的值即为oauth_consumer_key的值。

      appkey:appid对应的密钥,访问用户资源时用来验证应用的合法性。在OAuth2.0认证过程中,appkey的值即为oauth_consumer_secret的值。

    1.2 申请地址

      https://connect.qq.com/manage.html#/

    1.3 申请流程

    1. 开发者资质审核

      参考文章:开发者注册流程

    2. 申请appid(oauth_consumer_key/client_id)和appkey(auth_consumer_secret/client_secret)。

      (1)进入https://connect.qq.com/manage.html#/页面,点击“创建应用”,在弹出的对话框中填写网站或应用的详细资料(名称,域名,回调地址)。

      (2)点击“确定”按钮,提交资料后,获取appid和appkey。

        注意:申请appid时,登录的QQ号码将与申请到的appid绑定,后续维护均需要使用该号码。

        注意:对appid和appkey信息进行保密,不要随意泄漏。

    1.4 保证连接畅通

      接入QQ登录时,网站需要不停的和Qzone进行交互,发送请求和接受响应。

      1. 对于PC网站:

        请在你的服务器上ping graph.qq.com ,保证连接畅通。

      2. 移动应用无需此步骤

    2 放置“QQ登录”按钮_OAuth2.0

    本步骤的作用

      在网站页面上放置“QQ登录”按钮,并为按钮添加前台代码,实现点击按钮即弹出QQ登录对话框 。

    本步骤在整个流程中的位置
    oauth2.0_guid_2.png

    2.1 下载“QQ登录”按钮图片,并将按钮放置在页面合适的位置

      按钮图片下载: 点击这里下载 。

      可以到阿里矢量图库下载更多图标:阿里巴巴矢量图标库 。

      按照UI规范,将按钮放置在页面合适的位置:点击这里查看 。

    2.2 为“QQ登录”按钮添加前台代码

    2.2.1 效果演示

    2.2.2 前端代码(Vue)

      为了实现上述效果,应该为“QQ登录”按钮图片添加如下前台代码:

    <div style="line-height: 22px;margin:0 0 8px 0;color: #9b9b9b;">
            <span style="vertical-align:middle">第三方登录:</span>
            <img :src="qqIcon" width="22" height="22" style="vertical-align:middle;margin-left: 2px" title="QQ" @click="handleQqLogin">
            <a href=""><img :src="weixinIcon" width="22" height="22" style="vertical-align:middle;margin-left: 2px" title="微信"></a>
            <img :src="weiboIcon" width="22" height="22" style="vertical-align:middle;margin-left: 2px" title="微博" @click="handleWeiBoLogin">
            <a href=""><img :src="qyweixinIcon" width="22" height="22" style="vertical-align:middle;margin-left: 2px" title="企业微信"></a>
    </div>

     

     // qq登录
    // https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=1042&redirect_uri=https%3A%2F%2Fwww.lovezmy.link%2FqqCallback&state=3c6bc7df93a3c handleQqLogin() { getQQCode().then(res => { window.location.href = res; }) },

    2.2.2 后端代码(Java)

      qq登录配置文件信息:

    # QQ登录配置
    qq.appID = 116666602(替换成你的)
    qq.appKEY = c04360a666666666666666c06(替换成你的)
    qq.redirectURI = https://www.lovezmy.link/qqCallback(替换成你的)
    qq.scope = get_user_info,add_topic,add_one_blog,add_album,upload_pic,list_album,add_share,check_page_fans,add_t,add_pic_t,del_t,get_repost_list,get_info,get_other_info,get_fanslist,get_idollist,add_idol,del_ido,get_tenpay_addr(u8BF7u4FEEu6539u6B64u5904)
    qq.baseURL = https://graph.qq.com/
    qq.userInfoURL = https://graph.qq.com/user/get_user_info
    qq.accessTokenURL = https://graph.qq.com/oauth2.0/token
    qq.authorizeURL = https://graph.qq.com/oauth2.0/authorize
    qq.openIDURL = https://graph.qq.com/oauth2.0/me
    qq.addTopicURL = https://graph.qq.com/shuoshuo/add_topic
    qq.addBlogURL = https://graph.qq.com/blog/add_one_blog
    qq.addAlbumURL = https://graph.qq.com/photo/add_album
    qq.uploadPicURL = https://graph.qq.com/photo/upload_pic
    qq.listAlbumURL = https://graph.qq.com/photo/list_album
    qq.addShareURL = https://graph.qq.com/share/add_share
    qq.checkPageFansURL = https://graph.qq.com/user/check_page_fans
    qq.addTURL = https://graph.qq.com/t/add_t
    qq.addPicTURL = https://graph.qq.com/t/add_pic_t
    qq.delTURL = https://graph.qq.com/t/del_t
    qq.weiboUserInfoURL = https://graph.qq.com/user/get_info
    qq.otherUserInfoURL = https://graph.qq.com/user/get_other_info
    qq.fansListURL = https://graph.qq.com/relation/get_fanslist
    qq.idolsListURL = https://graph.qq.com/relation/get_idollist
    qq.addIdolURL = https://graph.qq.com/relation/add_idol
    qq.delIdolURL = https://graph.qq.com/relation/del_idol
    qq.tenpayAddrURL = https://graph.qq.com/cft_info/get_tenpay_addr
    qq.repostListURL = https://graph.qq.com/t/get_repost_list
    qq.version = 2.0.0.0

      读取配置文件信息常量类:

    package com.modules.security.constants;
    
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    
    /**
     * QQ登陆常量配置类
     */
    
    @Data
    @Configuration
    @PropertySource("classpath:thirdparty/config.properties")  // 指定配置文件的路径,属性文件需放在根目录的resources文件夹下面,才能被读取出来
    public class QQConstants {
    
        @Value("${qq.appID}")
        private String appID;
    
        @Value("${qq.appKEY}")
        private String appKEY;
    
        @Value("${qq.redirectURI}")
        private String redirectURI;
    
        @Value("${qq.authorizeURL}")
        private String authorizeURL;
    
        @Value("${qq.accessTokenURL}")
        private String accessTokenURL;
    
        @Value("${qq.openIDURL}")
        private String openIDURL;
    
        @Value("${qq.userInfoURL}")
        private String userInfoURL;
    
    }

      Conteoller(获取QQ登录的url)

       /**
         * 获得跳转到qq登录页的url,前台直接a连接访问
         *
         * @return
         * @throws Exception
         */
        @LogAnnotation("获得跳转到qq登录页的url")
        @ApiOperation("获得跳转到qq登录页的url")
        @AnonymousAccess
        @GetMapping("/getQQCode")
        public ResponseEntity<Object> getCode() throws Exception {
            // 授权地址 ,进行Encode转码
            String authorizeURL = qqConstants.getAuthorizeURL();
    
            // 回调地址 ,进行Encode转码
            String redirectUri = qqConstants.getRedirectURI();
    
            //用于第三方应用防止CSRF攻击
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            // 保存到Redis
            redisUtils.set(QQSTATE + "-" + uuid, uuid, expiration, TimeUnit.MINUTES);
    
            // 拼接url
            StringBuilder url = new StringBuilder();
            url.append(authorizeURL);
            url.append("?response_type=code");
            url.append("&client_id=" + qqConstants.getAppID());
            // 转码
            url.append("&redirect_uri=" + URLEncodeUtil.getURLEncoderString(redirectUri));
            url.append("&state=" + uuid);
    
            return ResponseEntity.ok(url);
        }

    3 使用Authorization_Code获取Access_Token

    本步骤的作用

      通过用户验证登录和授权,获取Access Token,为下一步获取用户的OpenID做准备。

      同时,Access Token是应用在调用OpenAPI访问和修改用户数据时必须传入的参数。

    移动端应用可以直接获得AccessToken,请参考使用Implicit_Grant方式获取Access_Token

    本步骤在整个流程中的位置

    oauth2.0_guid_3.png

    3.1 简介

      即server-side模式,是OAuth2.0认证的一种模式,又称Web Server Flow。

      适用于需要从web server访问的应用,例如Web网站。

    对于应用而言,需要进行两步:

      1. 获取Authorization Code。

      2. 通过Authorization Code获取Access Token。

    3.2 获取Authorization Code

    请求地址

      PC网站:https://graph.qq.com/oauth2.0/authorize

    请求方法

      GET

    请求参数

    参数是否必须含义
    response_type 必须 授权类型,此值固定为“code”。
    client_id 必须 申请QQ登录成功后,分配给应用的appid。
    redirect_uri 必须 成功授权后的回调地址,必须是注册appid时填写的主域名下的地址,建议设置为网站首页或网站的用户中心。注意需要将url进行URLEncode。
    state 必须 client端的状态值。用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回。请务必严格按照流程检查用户与state参数状态的绑定。
    scope 可选 请求用户授权时向用户显示的可进行授权的列表。
    可填写的值是API文档中列出的接口,如果要填写多个接口名称,请用逗号隔开。
    例如:scope=get_user_info,list_album,upload_pic
    不传则默认请求对接口get_user_info进行授权。
    建议控制授权项的数量,只传入必要的接口名称,因为授权项越多,用户越可能拒绝进行任何授权。
    display 可选 PC网站接入时使用。用于展示的样式。不传则默认展示为PC下的样式。如果传入“mobile”,则展示为mobile端下的样式。

    返回说明

    1. 如果用户成功登录并授权,则会跳转到指定的回调地址,并在redirect_uri地址后带上Authorization Code和原始的state值。如:  

      https://www.lovezmy.link/qqCallback?code=1E83738E79B0CEBF13FC7C3B1224D9B3C&state=cca3c152c527229409cccd889ad2963fe

      注意:此code会在10分钟内过期。

    2. 如果用户在登录授权过程中取消登录流程,对于PC网站,登录页面直接关闭。

    错误码说明

      接口调用有错误时,会返回code和msg字段,以url参数对的形式返回,value部分会进行url编码(UTF-8)。

      PC网站接入时,错误码详细信息请参见:100000-100031:PC网站接入时的公共返回码

    3.3 通过Authorization Code获取Access Token

    请求地址

      PC网站:https://graph.qq.com/oauth2.0/token

    请求方法

      GET

    请求参数

    参数是否必须含义
    grant_type 必须 授权类型,在本步骤中,此值为“authorization_code”。
    client_id 必须 申请QQ登录成功后,分配给网站的appid。
    client_secret 必须 申请QQ登录成功后,分配给网站的appkey。
    code 必须 上一步返回的authorization code。
    如果用户成功登录并授权,则会跳转到指定的回调地址,并在URL中带上Authorization Code。
    例如,回调地址为www.qq.com/my.php,则跳转到:
    http://www.qq.com/my.php?code=520DD95263C1CFEA087******
    注意此code会在10分钟内过期。
    redirect_uri 必须 与上面一步中传入的redirect_uri保持一致。
    fmt 可选 因历史原因,默认是x-www-form-urlencoded格式,如果填写json,则返回json格式

    返回说明

      如果成功返回,即可在返回包中获取到Access Token。 如(不指定fmt时):

      access_token=FE04************CCE2&expires_in=7776000&refresh_token=88E4****************BE14

    参数说明描述
    access_token 授权令牌,Access_Token。
    expires_in 该access token的有效期,单位为秒。
    refresh_token 在授权自动续期步骤中,获取新的Access_Token时需要提供的参数。

    注:refresh_token仅一次有效

    错误码说明

      接口调用有错误时,会返回code和msg字段,以url参数对的形式返回,value部分会进行url编码(UTF-8)。

      PC网站接入时,错误码详细信息请参见:100000-100031:PC网站接入时的公共返回码

    接口代码:

       /**
         * 获得token信息(授权,每个用户的都不一致) --> 获得token信息该步骤返回的token期限为一个月
         *
         * @return
         * @throws Exception
         */
        public Map<String, Object> getToken(String code) throws Exception {
            StringBuilder url = new StringBuilder();
            url.append(qqConstants.getAccessTokenURL());
            url.append("?grant_type=authorization_code");
            url.append("&client_id=" + qqConstants.getAppID());
            url.append("&client_secret=" + qqConstants.getAppKEY());
            url.append("&code=" + code);
    
            // 回调地址
            String redirectUri = qqConstants.getRedirectURI();
            // 转码
            url.append("&redirect_uri=" + URLEncodeUtil.getURLEncoderString(redirectUri));
            // 获得token
            String result = HttpClientUtils.get(url.toString(), "UTF-8");
            // 把token保存
            String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(result, "&");
            String accessToken = StringUtils.substringAfterLast(items[0], "=");
            Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
            String refreshToken = StringUtils.substringAfterLast(items[2], "=");
            //token信息
            Map<String, Object> qqProperties = new HashMap<String, Object>();
            qqProperties.put("accessToken", accessToken);
            qqProperties.put("expiresIn", String.valueOf(expiresIn));
            qqProperties.put("refreshToken", refreshToken);
            return qqProperties;
        }

    3.4(可选)权限自动续期,获取Access Token

      Access_Token的有效期默认是3个月,过期后需要用户重新授权才能获得新的Access_Token。本步骤可以实现授权自动续期,避免要求用户再次授权的操作,提升用户体验。

    请求地址

      PC网站:https://graph.qq.com/oauth2.0/token

    请求方法

      GET

    请求参数

    参数是否必须含义
    grant_type 必须 授权类型,在本步骤中,此值为“refresh_token”。
    client_id 必须 申请QQ登录成功后,分配给网站的appid。
    client_secret 必须 申请QQ登录成功后,分配给网站的appkey。
    refresh_token 必须 首次:使用在Step2中获取到的最新的refresh_token。

    后续:使用刷新后返回的最新refresh_token

    fmt 可选 因历史原因,默认是x-www-form-urlencoded格式,如果填写json,则返回json格式

    返回说明

      如果成功返回,即可在返回包中获取到Access Token。 如(不指定fmt时):

      access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14。

    参数说明描述
    access_token 授权令牌,Access_Token。
    expires_in 该access token的有效期,单位为秒。
    refresh_token 在授权自动续期步骤中,获取新的Access_Token时需要提供的参数。每次生成最新的refresh_token,且仅一次有效

    错误码说明

      接口调用有错误时,会返回code和msg字段,以url参数对的形式返回,value部分会进行url编码(UTF-8)。

      PC网站接入时,错误码详细信息请参见:100000-100031:PC网站接入时的公共返回码

    接口代码:

       /**
         * 刷新token 信息(token过期,重新授权)
         *
         * @return
         * @throws Exception
         */
        @GetMapping("/refreshToken")
        public Map<String, Object> refreshToken(Map<String, Object> qqProperties) throws Exception {
            // 获取refreshToken
            String refreshToken = (String) qqProperties.get("refreshToken");
            StringBuilder url = new StringBuilder(qqConstants.getAccessTokenURL());
            url.append("?grant_type=refresh_token");
            url.append("&client_id=" + qqConstants.getAppID());
            url.append("&client_secret=" + qqConstants.getAppKEY());
            url.append("&refresh_token=" + refreshToken);
            String result = HttpClientUtils.get(url.toString(), "UTF-8");
            // 把新获取的token存到map中
            String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(result, "&");
            String accessToken = StringUtils.substringAfterLast(items[0], "=");
            Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
            String newRefreshToken = StringUtils.substringAfterLast(items[2], "=");
            //重置信息
            qqProperties.put("accessToken", accessToken);
            qqProperties.put("expiresIn", String.valueOf(expiresIn));
            qqProperties.put("refreshToken", newRefreshToken);
            return qqProperties;
        }

    4 获取用户OpenID_OAuth2.0

    本步骤的作用

      通过输入在上一步获取的Access Token,得到对应用户身份的OpenID。

      OpenID是此网站上或应用中唯一对应用户身份的标识,网站或应用可将此ID进行存储,便于用户下次登录时辨识其身份,或将其与用户在网站上或应用中的原有账号进行绑定。

    本步骤在整个流程中的位置
    oauth2.0_guid_4.png

    请求地址:

      PC网站:https://graph.qq.com/oauth2.0/me

    请求方法:

      GET

    请求参数:

    参数是否必须含义
    access_token 必须 在Step1中获取到的access token。
    fmt 可选 因历史原因,默认是jsonpb格式,如果填写json,则返回json格式

    返回说明:

      PC网站接入时,获取到用户OpenID,返回包如下(如果fmt参数未指定):

      callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );

      openid是此网站上唯一对应用户身份的标识,网站可将此ID进行存储便于用户下次登录时辨识其身份,或将其与用户在网站上的原有账号进行绑定。

    错误码说明:

      接口调用有错误时,会返回code和msg字段,以url参数对的形式返回,value部分会进行url编码(UTF-8)。

      PC网站接入时,错误码详细信息请参见:100000-100031:PC网站接入时的公共返回码

    接口代码:

       /**
         * 获取用户openId(根据token)
         *
         * @return
         * @throws Exception
         */
        public String getOpenId(Map<String, Object> qqProperties) throws Exception {
            // 获取保存的用户的token
            String accessToken = (String) qqProperties.get("accessToken");
            if (StringUtils.isEmpty(accessToken)) {
                throw new BadRequestException("QQ登录信息异常,请重试!!!");
            }
            StringBuilder url = new StringBuilder(qqConstants.getOpenIDURL());
            url.append("?access_token=" + accessToken);
            String result = HttpClientUtils.get(url.toString(), "UTF-8");
            String openId = StringUtils.substringBetween(result, ""openid":"", ""}");
            return openId;
        }

    5 OpenAPI调用说明_OAuth2.0

    本步骤的作用

      获取到Access Token和OpenID后,可通过调用OpenAPI来获取或修改用户个人信息。

    本步骤在整个流程中的位置
    oauth2.0_guid_5.png

    5.1 前提说明

    1. 该appid已经开通了该OpenAPI的使用权限。

      从API列表的接口列表中可以看到,有的接口是完全开放的,有的接口则需要提前提交申请,以获取访问权限。

    2. 准备访问的资源是用户授权可访问的。

      网站调用该OpenAPI读写某个openid(用户)的信息时,必须是该用户已经对你的appid进行了该OpenAPI的授权(例如用户已经设置了相册不对外公开,则网站是无法读取照片信息的)。

      用户可以进入手机QQ->设置->隐私->授权管理,进行访问权限管理。

    3. 已经成功获取到Access Token,并且Access Token在有效期内。

    5.2 调用OpenAPI接口

      QQ登录提供了用户信息等OpenAPI(详见API列表),网站需要将请求发送到某个具体的OpenAPI接口,以访问或修改用户数据。

      调用所有OpenAPI时,除了各接口私有的参数外,所有OpenAPI都需要传入基于OAuth2.0协议的通用参数:

    参数含义
    access_token 可通过使用Authorization_Code获取Access_Token 或来获取。access_token有3个月有效期。
    oauth_consumer_key 申请QQ登录成功后,分配给应用的appid
    openid 用户的ID,与QQ号码一一对应。
    可通过调用https://graph.qq.com/oauth2.0/me?access_token=YOUR_ACCESS_TOKEN 来获取。

    5.3 示例

    1. 以get_user_info接口为例:(请将access_token,appid等参数值替换为你自己的)

      https://graph.qq.com/user/get_user_info?access_token=YOUR_ACCESS_TOKEN&oauth_consumer_key=YOUR_APP_ID&openid=YOUR_OPENID

    2. 成功返回后,即可获取到用户数据:

    package com.modules.security.entity;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    /**
     * QQ登录用户信息
     * @author Liyh
     * @date 2020/12/22
     */
    
    @Data
    public class QQUserInfo implements Serializable {
    
        private String ret;                  // 返回码
        private String msg;                  // 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。
        private String nickname;             // 用户在QQ空间的昵称。
        private String figureurl;            // 大小为30×30像素的QQ空间头像URL。
        private String figureurl_1;          // 大小为50×50像素的QQ空间头像URL。
        private String figureurl_2;          // 大小为100×100像素的QQ空间头像URL。
        private String figureurl_qq_1;       // 大小为40×40像素的QQ头像URL。
        private String figureurl_qq_2;       // 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100x100的头像,但40x40像素则是一定会有。
        private String gender;               // 性别。 如果获取不到则默认返回"男"
        private Integer gendertype;          // 性别 数字
        private String is_yellow_vip;        // 标识用户是否为黄钻用户(0:不是;1:是)。
        private String vip;                  // 标识用户是否为黄钻用户(0:不是;1:是)
        private String yellow_vip_level;     // 黄钻等级
        private String level;                // QQ等级
        private String is_yellow_year_vip;   // 标识是否为年费黄钻用户(0:不是; 1:是)
        private String province;             // 省
        private String city;                 // 市
    
    }

    3. 接口代码:

       /**
         * 根据token,openId获取用户信息
         */
        public QQUserInfo getUserInfo(Map<String, Object> qqProperties) throws Exception {
            // 取出token
            String accessToken = (String) qqProperties.get("accessToken");
            String openId = (String) qqProperties.get("openId");
            if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId)) {
                throw new BadRequestException("QQ登录信息异常,请重试!!!");
            }
            // 拼接url
            StringBuilder url = new StringBuilder(qqConstants.getUserInfoURL());
            url.append("?access_token=" + accessToken);
            url.append("&oauth_consumer_key=" + qqConstants.getAppID());
            url.append("&openid=" + openId);
            // 获取qq相关数据
            String result = HttpClientUtils.get(url.toString(), "UTF-8");
            Object json = JSON.parseObject(result, QQUserInfo.class);
            QQUserInfo userInfo = (QQUserInfo) json;
            return userInfo;
        }

    6 个人网站(YOUYOUSHOP)(用户名:admin,密码:adminliyh),QQ,微博等已经实现,需要的小伙伴可以测试 

    6.1 每个人做的项目需求不同,可能会出现不同的问题,文章可以参考,也可以留言你的问题,我会帮你解决,大家一起加油

  • 相关阅读:
    【转】对象持久化与数据序列化的联系?
    【转】Linux安装方法一(U盘引导)
    bash中的"-n"、"-z" 以及“[]” 、“[[]]”判断
    mysql获取行号
    IP白名单
    复合赋值位运算符“&=、| =”
    Java匿名内部类访问外部
    mysql的orde by 按照指定状态顺序排序
    Spring声明式事务
    定时任务总结
  • 原文地址:https://www.cnblogs.com/liyhbk/p/15250738.html
Copyright © 2011-2022 走看看