网站接入QQ第三方登录

第三方网站主要通过使用“QQ登录”接入QQ互联开放平台。“QQ登录”是QQ互联开放平台提供给第三方网站的一种服务。“QQ登录”可以让用户使用QQ帐号在第三方网站上登录,分享内容、同步信息,大大降低了用户注册的门槛。同时,第三方网站根据用户提供的绑定信息,将用户在网站上发布的内容同步发布到QQ空间的个人动态中,从而借助QQ空间庞大的用户群,使网站的信息能通过好友关系得到进一步的传播,提升网站的访问量和用户数。

授权流程.png

账号申请

进入QQ互联 https://connect.qq.com
右上角点击登录,登录之后点击头像会进入到申请页面。这里我们选择个人接入,需要准备一下手持身份证照片。

申请界面.png

完成注册提交审核之后会有几个小时的等待时间,一般情况都是可以下来的,主要是手持身份证

审核通过.png

点击应用管理->选择网站应用->创建应用,里面资料都可以随意填写。

创建应用.png

点击创建应用之后会跳到填写网站域名的地方,这里我们还是采用Natapp内网穿透

表单值.png

创建之后可以再应用管理查看,点击查看会看到相应的应用信息,主要关注appid和回调地址会用到。

2D8B4A5C-9982-48D9-80F1-CF2EB172D67A.png
762A855B-5BA8-49E4-942A-C293C4E22D53.png

下方的平台信息是可以随时修改的,方便我们测试,免费的Natapp会随时更换域名大家如果测试阶段发现错误可能就是映射域名已更换不符合配置域名。

81FBDEF5-E0F5-4284-BACC-D9E03FEA9159.png

项目代码

创建SpringBoot的Web项目,具体pom如下。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.animo.boot</groupId>
    <artifactId>springboot-qq</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-qq</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

YML和外部化配置类。

server:
  port: 8081
connect:
  appId: 101744617
  appKey: 8f7784b6b582b1d2117f34b549ad3193
  uri: http://v3gapy.natappfree.cc
  returnUri: ${connect.uri}/callback

@Component
@ConfigurationProperties(prefix = "connect")
public class ConnectConfig {
    /**
     * appID
     */
    private String appId;
    /**
     * appKey
     */
    private String appKey;
    /**
     * 回调的url
     */
    private String returnUri;
    /**
     * 项目的域名 为了方便测试
     */
    private String uri;
    //省略Get Set
}

工具类封装授权URL。

/**
 * @author 刘金泳
 * @Date 2019/8/5
 */
public class ConnectUtil {
    /**
     * 授权地址
     * @param appId
     * @param redirectUri
     * @return
     */
    public static String getAuthorize(String appId,String redirectUri){
        /**
         * 这里需要随机生成在和客户端传过来的state进行对比,防止CSRF攻击。
         */
        String state = "12n2x94h124vgh1249";
        String url = "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&state=%s";
        try {
            return String.format(url,appId,URLEncoder.encode(redirectUri,"UTF-8"),state);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 获取AccessToken
     * @param appId
     * @param appKey
     * @param code
     * @param redirectUri
     * @return
     */
    public static String getAccessToken(String appId,String appKey,String code,String redirectUri){
        String url = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s";
        try {
            return String.format(url,appId,appKey,code,URLEncoder.encode(redirectUri,"UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 获取openId
     * @param accessToken
     * @return
     */
    public static String getOpenId(String accessToken){
        return "https://graph.qq.com/oauth2.0/me?access_token="+accessToken;
    }
    /**
     * 获取用户信息
     * @param accessToken
     * @param appId
     * @param openId
     * @return
     */
    public static String getUserInfo(String accessToken , String appId, String openId){
        String url = "https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s";
        return String.format(url,accessToken,appId,openId);
    }
}

控制器层代码只是简单的作为测试例子,更多逻辑操作还得自行添加。

@Controller
public class CallbackController {
    @Autowired
    private ConnectConfig connectConfig;
    @RequestMapping("/login")
    public String qqLogin(){
        return "redirect:"+ ConnectUtil.getAuthorize(connectConfig.getAppId(),connectConfig.getReturnUri());
    }

    @GetMapping("/callback")
    @ResponseBody
    public String callBack(HttpServletRequest request){
        String code = request.getParameter("code");
        String state = request.getParameter("state");
        String accessTokenUrl = ConnectUtil.getAccessToken(connectConfig.getAppId(),connectConfig.getAppKey(),code,connectConfig.getReturnUri());
        /**
         * 此方法业务逻辑自行设计 需要结合缓存
         */
        try {
            //获取AccessToken
            HttpClientDto accessTokenDto = HttpClientUtil.doGet(accessTokenUrl);
            String [] strings = accessTokenDto.getContent().split("&");
            Map<String,String> map = new HashMap<>(16);
            map.put("access_token",strings[0].split("=")[1]);
            map.put("expires_in",strings[1].split("=")[1]);
            map.put("refresh_token",strings[2].split("=")[1]);
            //获取OpenId
            HttpClientDto openDto = HttpClientUtil.doGet(ConnectUtil.getOpenId(map.get("access_token")));
            String[] strings1 =  openDto.getContent().split(" ");
            Map<String,Object> openMap = GsonUtil.JsonToMap(strings1[1]);
            //获取用户信息
            HttpClientDto userInfo =  HttpClientUtil.doGet(ConnectUtil.getUserInfo(map.get("access_token"),connectConfig.getAppId(),openMap.get("openid").toString()));
            /**
             * 这里应该是ModeAndView  把头像和昵称放进map和页面一起返回
             */
            return userInfo.getContent();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "回调";
    }
}

前端页面代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>QQ登录</title>
</head>
<body>
    <img src="/image/Connect_logo_5.png" onclick="toLogin()">
</body>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script type="text/javascript">
    //在新标签页打开网站
    function toLogin() {
        window.open("/login", "TencentLogin",
            "width=450,height=320,menubar=0,scrollbars=1,resizable=1,status=1,titlebar=0,toolbar=0,location=1");
    }
</script>
</html>

项目测试

启动项目访问 http://p95rqj.natappfree.cc/ 具体地址看自己映射,这里我们选了个比较霸气大头的图标。

A920CE72-CA35-4DF9-A93E-267F108720FA.png

点击图标之后弹出我们常见的授权地址,只能用创建应用的QQ登录测试。

ADB63E15-F438-450C-BEC9-6890F33EAE55.png

选择登录的QQ之后选择登录会进入我们的回调http://p95rqj.natappfree.cc/callback 回调地址会携带code和state,这里我们代码只选择返回用户信息。

EE4396A7-968A-44A6-AABB-D6534C2AF359.png

注意事项

控制器层代码中HttpClientUtil 和 GsonUtil 、HttpClientDto并未贴出需要从GitHub中获取地址在下方。
实际上获取授权地址中的response_type,虽然官方文档指定说是code,但是我看jsdk里面写的是token但是里面返回的参数未不包含refresh_token,后端代码不确定这样做是否符合,这边和大家分享一下。

2D883913-6107-4F3A-A105-5DF5E351FFDB.png

官方文档:对接QQ登录文档

项目地址:SpringBoot-QQ

博客链接:http://www.ljyanimo.com/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容