在我们的项目开发中,使用
第三方登录
(如QQ登录、微信登录等)可以更加方便、轻松地实现用户登录。
在以往的开发过程中,如果要使项目实现第三方登录功能,一般过程是阅读官网的开发文档
,并下载其JDK
(或者依赖pom),然后进行开发实现。
但是,如果网站要实现多个
第三方平台的登录功能,则需要很高的学习成本
。
所以,就有开发者实现了一款基于Spring Boot的开箱即用的整合第三方登录的开源组件:JustAuth
该插件的网址:https://justauth.wiki
本文将基于Spring Boot
,使用JustAuth组件
实现第三方快捷登录,并获取用户的uid。
1. 插件简介
首先给出几个链接:
- 组件的帮助文档:https://justauth.wiki
- 组件的GitHub:https://github.com/justauth/JustAuth
- 组件的Gitee组织:https://gitee.com/justauth/
在组件的各个网址,都可以看到关于该组件的自述:
小而全而美的第三方登录开源组件。目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软、今日头条、Teambition、StackOverflow、Pinterest、人人、华为、企业微信、酷家乐、Gitlab、美团、饿了么和推特等第三方平台的授权登录。 Login, so easy!
可以看出,此组件支持的第三方登录平台,是非常全的。
下面,以QQ
和微博
为例,测试Spring Boot
上使用该组件完成第三方登录快速开发。
- 建议参考组件的帮助文档,均有详细说明。
- 或者直接参考官方的demo:https://github.com/justauth/JustAuth-demo
- 开发之前,需要前往
QQ开放平台
和微博开放平台
申请应用,并获取appid
、APP secret
,设置callback url
。参考:
2. 项目创建与导包
关于Spring Boot项目如何创建,可参考以下几篇文章:
只看前两个就可以了。
创建完成项目之后,在pom.xml
文件中,导入依赖:
<!--JustAuth-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.15.9</version>
</dependency>
<!--http请求相关-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.3.9</version>
</dependency>
<!--导入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
这里导入了4个依赖,说明如下:
第一个是
JustAuth组件
必须的,版本就默认使用帮助文档中的最新版,如下图所示。
第二个是
http请求
的实现组件,这是因为JustAuth从 v1.14.0
开始不会默认集成hutool-http
,需要单独添加;
第三个是
lombok
,即自动生成模型的set
和get
。因为项目中授权登录成功后,返回的用户信息模型
,使用了@Getter
和@Setter
注解。添加此组件,方便获取用户的单个信息(如uuid、nickname等)。
第四个是阿里巴巴的
fastjson
组件,因为在返回的结果中,有些信息是封装成JSON格式
的,需要使用该组件来转换成map键值对
,从而获取对我们有用的信息。
3. QQ登录实现
1、 新建一个名为 PluginController
的控制类,用来实现第三方插件的登录回调功能。
package com.demo.controller;
import org.springframework.web.bind.annotation.*;
/**
* Info:登录组件测试
*
* @author: 拾年之璐
* @date: 2021-03-15 0015 19:27
*/
@RestController
@RequestMapping(value = "/plugin")
public class PluginController {
}
2、创建一个AuthRequest
接口的实现类,用于生成登录链接
、登录并获取用户信息
等
/**
* 授权接口类
*
* @return 各种请求的结果
*/
private AuthRequest getQQAuthRequest() {
return new AuthQqRequest(AuthConfig.builder()
.clientId("APPID")
.clientSecret("Your APP Secret")
.redirectUri("http://XXX.com/plugin/qqlogin/callback")
.unionId(true) //如果获取用户的UnionID,则设置为true
.build());
}
3、生成登录链接
并跳转至登录页面
/**
* 跳转至登录页面
*
* @param response 页面跳转
* @throws IOException
*/
@RequestMapping("/qqlogin")
public void qqlogin(HttpServletResponse response) throws IOException {
//获取对象
AuthRequest authRequest = getQQAuthRequest();
//打印生成的链接
System.out.println("生成登录链接:" + authRequest.authorize("yourState"));
//页面跳转,其中state参数可自定义
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
}
这时,控制台可以打印出生成的登录URL
浏览器访问http://XXX.com/plugin/qqlogin
,即可跳转至此登录页面
4、回调页面plugin/qqlogin/callback
获取返回的code
,并通过返回的code
获取当前登录用户的信息
如下:
/**
* QQ登录成功后返回到此页
*
* @param callback 登录用户的信息
* @return
*/
@RequestMapping("/qqlogin/callback")
public Object qqloginCallback(AuthCallback callback) {
AuthRequest authRequest = getQQAuthRequest();
// 打印返回的授权信息(QQ登录为code,通过code获取用户信息)
System.out.println(callback.getCode());
//根据返回的参数,执行登录请求(获取用户信息)
AuthResponse<AuthUser> authResponse = authRequest.login(callback);
// 将JSON包返回到前端页面显示
return authResponse;
}
上面的图,点击登录后,即可跳转至回调页面,显示出所有的JSON数据,如下图所示:
我们对该JSON数据
进行分析:
- 返回的
code
是2000,表示获取数据成功(其他代码,可以在https://justauth.wiki/quickstart/error_code.html查看); - 返回的
data
数据,包含所有有用的信息,如用户IDuuid
、用户昵称username
、头像avatar
、性别gender
等等; data
下的toke
n,包含所有的获取token
、刷新token
和过期时间
等信息;data下
的rawUserInf
o,是第三方平台返回的原始用户信息
;- 关于返回的JSON数据的每个子段的
详细含义
,都可以在模型AuthUser
中查看。
5、后台获取指定信息
上面可以获取所有的用户信息,但这些信息,我们并不能这样使用,而是筛选有用的信息,然后保存到自己的数据库。
所以下面这个完整示例,演示如何获取上述JSON中的指定信息。
/**
* QQ登录成功后返回到此页
*
* @param callback 登录用户的信息
* @return
*/
@RequestMapping("/qqlogin/callback")
public Object qqloginCallback(AuthCallback callback) {
//获取实例
AuthRequest authRequest = getQQAuthRequest();
// 打印返回的授权信息(QQ登录为code,通过code获取用户信息)
System.out.println(callback.getCode());
//根据返回的参数,执行登录请求(获取用户信息)
AuthResponse<AuthUser> authResponse = authRequest.login(callback);
//打印授权返回代码(2000 表示成功,可以用来判断用户登录成功与否)
System.out.println("状态码:"+authResponse.getCode());
//打印用户的昵称、ID、头像等基本信息
System.out.println("用户的UnionID:" + authResponse.getData().getUuid());
System.out.println("用户的昵称:" + authResponse.getData().getNickname());
System.out.println("用户的头像:" + authResponse.getData().getAvatar());
//打印用户的Token中的信息
System.out.println("access_token:" + authResponse.getData().getToken().getAccessToken());
System.out.println("用户的OpenId:" + authResponse.getData().getToken().getOpenId());
// 打印更加详细的信息(第三方平台返回的原始用户信息)
//getInnerMap():将JSONObject转换成Map键值对
System.out.println("用户的城市:" + authResponse.getData().getRawUserInfo().getInnerMap().get("city"));
System.out.println("用户的年份:" + authResponse.getData().getRawUserInfo().getInnerMap().get("year"));
// 将JSON包返回到前端页面显示
return authResponse;
}
其实这段代码,和JSON数据,是一一对应的,如下图所示:
打印结果:
4. 微博登录实现
有了上面QQ登录的详细分析,微博登录也是类似的,这里速战速决。
1、创建授权请求类
/**
* 生成微博授权登录的类getWbAuthRequest
*
* @return
*/
private AuthRequest getWbAuthRequest() {
return new AuthWeiboRequest(AuthConfig.builder()
.clientId("appid")
.clientSecret("app secret")
.redirectUri("http://xxx.com/plugin/sinalogin/callback")
.build());
}
2、生成并跳转登录链接
/**
* 微博登录页面,生成登录链接
*
* @param response 页面跳转
* @throws IOException
*/
@RequestMapping("/wblogin")
public void wblogin(HttpServletResponse response) throws IOException {
//生成对象
AuthRequest authRequest = getWbAuthRequest();
//生成链接并返回,其中state可以自定义,可以用来实现第四方登录
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
}
结果:
3、回调页面获取信息,以及返回JSON数据
/**
* 回调页面,获取登录用户新
*
* @param callback 详细JSON信息
* @return
*/
@RequestMapping("/sinalogin/callback")
public Object wbloginCallback(AuthCallback callback) {
//生成对象
AuthRequest authRequest = getWbAuthRequest();
//获取登录后返回的用户信息结果
AuthResponse<AuthUser> authResponse = authRequest.login(callback);
//打印授权返回代码(2000 表示成功,可以用来判断用户登录成功与否)
System.out.println("状态码:" + authResponse.getCode());
//打印用户的昵称、ID、头像等基本信息
System.out.println("用户的UnionID:" + authResponse.getData().getUuid());
System.out.println("用户的昵称:" + authResponse.getData().getNickname());
System.out.println("用户的头像:" + authResponse.getData().getAvatar());
//打印用户的Token中的信息
System.out.println("access_token:" + authResponse.getData().getToken().getAccessToken());
System.out.println("用户的OpenId:" + authResponse.getData().getToken().getOpenId());
// 打印更加详细的信息(第三方平台返回的原始用户信息)
System.out.println("用户的城市:" + authResponse.getData().getRawUserInfo().getInnerMap().get("city"));
System.out.println("用户的年份:" + authResponse.getData().getRawUserInfo().getInnerMap().get("year"));
//返回JSON信息
return authResponse;
}
结果:
5. 完整Controller源码
package com.demo.controller;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthQqRequest;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWeiboRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Info:登录组件测试
*
* @author: 拾年之璐
* @date: 2021-03-22 0022 19:31
*/
@RestController
@RequestMapping(value = "/plugin")
public class PluginController {
/**
* QQ登录request类
*
* @return 链接
*/
private AuthRequest getQQAuthRequest() {
return new AuthQqRequest(AuthConfig.builder()
.clientId("appid")
.clientSecret("appsecret")
.redirectUri("http://xxx.com/plugin/qqlogin/callback")
.unionId(true)
.build());
}
/**
* 跳转至登录页面
*
* @param response 页面跳转
* @throws IOException
*/
@RequestMapping("/qqlogin")
public void qqlogin(HttpServletResponse response) throws IOException {
//获取实例
AuthRequest authRequest = getQQAuthRequest();
//打印生成的链接
System.out.println("生成登录链接:" + authRequest.authorize("yourState"));
System.out.println();
//页面跳转,其中state参数可自定义
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
}
/**
* QQ登录成功后返回到此页
*
* @param callback 登录用户的信息
* @return
*/
@RequestMapping("/qqlogin/callback")
public Object qqloginCallback(AuthCallback callback) {
//获取实例
AuthRequest authRequest = getQQAuthRequest();
// 打印返回的授权信息(QQ登录为code,通过code获取用户信息)
System.out.println(callback.getCode());
//根据返回的参数,执行登录请求(获取用户信息)
AuthResponse<AuthUser> authResponse = authRequest.login(callback);
//打印授权返回代码(2000 表示成功,可以用来判断用户登录成功与否)
System.out.println("状态码:" + authResponse.getCode());
//打印用户的昵称、ID、头像等基本信息
System.out.println("用户的UnionID:" + authResponse.getData().getUuid());
System.out.println("用户的昵称:" + authResponse.getData().getNickname());
System.out.println("用户的头像:" + authResponse.getData().getAvatar());
//打印用户的Token中的信息
System.out.println("access_token:" + authResponse.getData().getToken().getAccessToken());
System.out.println("用户的OpenId:" + authResponse.getData().getToken().getOpenId());
// 打印更加详细的信息(第三方平台返回的原始用户信息)
System.out.println("用户的城市:" + authResponse.getData().getRawUserInfo().getInnerMap().get("city"));
System.out.println("用户的年份:" + authResponse.getData().getRawUserInfo().getInnerMap().get("year"));
// 将JSON包返回到前端页面显示
return authResponse;
}
/**
* 生成微博授权登录的类getWbAuthRequest
*
* @return
*/
private AuthRequest getWbAuthRequest() {
return new AuthWeiboRequest(AuthConfig.builder()
.clientId("appid")
.clientSecret("appsecret")
.redirectUri("http://xxx.com/plugin/sinalogin/callback")
.build());
}
/**
* 微博登录页面,生成登录链接
*
* @param response 页面跳转
* @throws IOException
*/
@RequestMapping("/wblogin")
public void wblogin(HttpServletResponse response) throws IOException {
//生成对象
AuthRequest authRequest = getWbAuthRequest();
//生成链接并返回,其中state可以自定义,可以用来实现第四方登录
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
}
/**
* 回调页面,获取登录用户新
*
* @param callback 详细JSON信息
* @return
*/
@RequestMapping("/sinalogin/callback")
public Object wbloginCallback(AuthCallback callback) {
//生成对象
AuthRequest authRequest = getWbAuthRequest();
//获取登录后返回的用户信息结果
AuthResponse<AuthUser> authResponse = authRequest.login(callback);
//打印授权返回代码(2000 表示成功,可以用来判断用户登录成功与否)
System.out.println("状态码:" + authResponse.getCode());
//打印用户的昵称、ID、头像等基本信息
System.out.println("用户的UnionID:" + authResponse.getData().getUuid());
System.out.println("用户的昵称:" + authResponse.getData().getNickname());
System.out.println("用户的头像:" + authResponse.getData().getAvatar());
//打印用户的Token中的信息
System.out.println("access_token:" + authResponse.getData().getToken().getAccessToken());
System.out.println("用户的OpenId:" + authResponse.getData().getToken().getOpenId());
// 打印更加详细的信息(第三方平台返回的原始用户信息)
System.out.println("用户的城市:" + authResponse.getData().getRawUserInfo().getInnerMap().get("city"));
System.out.println("用户的年份:" + authResponse.getData().getRawUserInfo().getInnerMap().get("year"));
//返回JSON信息
return authResponse;
}
/**
* 销毁
*/
@RequestMapping("/revoke/{token}")
public Object revokeAuth(@PathVariable("token") String token) throws IOException {
AuthRequest authRequest = getWbAuthRequest();
return authRequest.revoke(AuthToken.builder().accessToken(token).build());
}
}
当然,还有其他各个平台,如:
每个平台的授权登录实现方式大同小异,此处不再赘述。
参考资料:
- https://justauth.wiki/quickstart/explain.html
- https://justauth.wiki/oauth/qq.html
- https://justauth.wiki/oauth/weibo.html