“ JustAuth 是一个开箱即用的整合第三方登录的开源组件,网上没有搜到它在前后端分离系统中的使用案例,本篇文章将以 QQ 登录为例为大家讲解该场景下的使用步骤,建议收藏 ”
01
—
申请 QQ 应用
登录 QQ 互联平台
https://connect.qq.com
申请开发者
进入“应用管理”页面:https://connect.qq.com/manage.html#
如果是第一次使用,并且未进行过开发者认证,需要提交一下个人资料,待认证通过后方可创建应用。
依次点击:应用管理 -> 网站应用 -> 创建应用,应用信息提交后,等待审核通过即可。
应用审核通过后如下:
此时可以在登录页面上放置 QQ 图标:
02
—
后台集成 JustAuth
引入依赖
me.zhyd.oauth
JustAuth
${latest.version}
${latest.version}
表示当前最新的版本
配置 QQ 公众平台信息
#你的appid
oauth.qq.client_id=101***893
#你的appkey
oauth.qq.client_secret=e45862****************008c244ec5
#你接收响应code码地址
oauth.qq.redirect_uri=https://account.qiwenshare.com/qqprocessing
#腾讯获取code码地址
oauth.qq.code_callback_uri=https://graph.qq.com/oauth2.0/authorize
#腾讯获取access_token地址
oauth.qq.access_token_callback_uri=https://graph.qq.com/oauth2.0/token
#腾讯获取openid地址
oauth.qq.openid_callback_uri=https://graph.qq.com/oauth2.0/me
#腾讯获取用户信息地址
oauth.qq.user_info_callback_uri=https://graph.qq.com/user/get_user_info
配置类,用来读取配置信息
@Component
@ConfigurationProperties(prefix = "oauth")
@Data
public class OAuthProperties {
private QQProperties qq = new QQProperties();
//这里可以定义很多其他三方账户配置
}
@Data
public class QQProperties {
private String client_id;
private String client_secret;
private String redirect_uri;
private String code_callback_uri;
private String access_token_callback_uri;
private String openid_callback_uri;
private String user_info_callback_uri;
}
03
—
QQ 登录认证操作
当用户点击登录界面点击 QQ 登录按钮之后,调用后台接口,后台接口如下:
@Operation(summary = "第三方登陆对外接口")
@GetMapping(value = "/render/{source}")
public void render(@PathVariable("source") String source, HttpServletResponse response) throws IOException {
AuthRequest authRequest = getAuthRequest(source);
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
response.sendRedirect(authorizeUrl);
}
从上面代码可以看出,这里使用了JustAuth 提供的 AuthRequest 方法,source参数用来区分三方账户类型。
这里使用 QQ 登录方式,那么传进来的请求路径实际上是 /render/qq , 接下来是 getAuthRequest 方法 :
private AuthRequest getAuthRequest(String source) {
AuthRequest authRequest = null;
switch (source.toLowerCase()) {
case "qq":
authRequest = new AuthQqRequest(AuthConfig.builder()
.clientId(oauth.getQQ().getClient_id())
.clientSecret(oauth.getQQ().getClient_secret())
.redirectUri(oauth.getQQ().getRedirect_uri())
.build());
break;
case "wechat_open":
authRequest = new AuthWeChatOpenRequest(AuthConfig.builder()
.clientId("")
.clientSecret("")
.redirectUri("http://www.zhyd.me/oauth/callback/wechat")
.build());
break;
case "csdn":
authRequest = new AuthCsdnRequest(AuthConfig.builder()
.clientId("")
.clientSecret("")
.redirectUri("http://dblog-web.zhyd.me/oauth/callback/csdn")
.build());
break;
//这里还有好多case省略
default:
break;
}
if (null == authRequest) {
throw new AuthException("未获取到有效的Auth配置");
}
return authRequest;
}
通过上面代码可以看出,定义一个接口即可兼容多种三方账户的登录,上面的AuthQqRequest 需要传入的参数。
可以在 https://connect.qq.com/index.html 页面去创建网址应用并申请得到,其他的三方账号也是同样的操作。
这个接口执行完成之后,会跳转至 QQ 登录认证界面。
认证成功之后,继续跳转至我们的项目中,当跳转到我们的项目之后,请求路径会同时会在地址栏带回登录需要的参数(code,state),如下:
https://www.qiwenshare.com/qqprocess?code=9A5F************************06AF&state=test
04
—
回调地址的处理
这个回调的地址,可以在 QQ 互联中心进行设置。
但是这里有一个需要注意的点,因为这篇文章题目讲的是前后端分离系统,那么回调地址是设置为前端还是后台,JustAuth 文档和案例都是通过后台来接收这个回调请求的,我们先来看一下它的代码段源码,如下:
/**
* oauth平台中配置的授权回调地址,以本项目为例,在创建github授权应用时的回调地址应为:http://127.0.0.1:8443/oauth/callback/github
*/
@RequestMapping("/callback/{source}")
public ModelAndView login(@PathVariable("source") String source, AuthCallback callback, HttpServletRequest request) {
log.info("进入callback:" + source + " callback params:" + JSONObject.toJSONString(callback));
AuthRequest authRequest = getAuthRequest(source);
AuthResponse response = authRequest.login(callback);
log.info(JSONObject.toJSONString(response));
if (response.ok()) {
userService.save(response.getData());
return new ModelAndView("redirect:/users");
}
Map map = new HashMap<>(1);
map.put("errorMsg", response.getMsg());
return new ModelAndView("error", map);
}
从上面可以看出,后台接收 QQ 回调地址,处理完成后直接返回 ModelAndView 到前端,这里的做法显然不符合前后端分离系统,所以我的做法是用前端代码去接收回调地址,并请求后台:
正在登录,请稍等...
上段代码便是从地址里面去拿到 code,state 这两个参数,并请求后台接口。
05
—
获取第三方用户信息并认证
后台拿到前台传过来的 code 和 state 参数之后,便可以获取第三方用户信息并认证,代码段如下:
@Operation(summary = "第三方平台登录", tags = {"user"})
@MyLog(operation = "第三方平台登录")
@RequestMapping(value = "/authorize/{source}", method = RequestMethod.POST)
public RestResult authorize(@PathVariable("source") String source, @RequestBody AuthorizeDto authorizeDto) {
RestResult restResult = new RestResult();
AuthRequest authRequest = getAuthRequest(source);
AuthCallback authCallback = new AuthCallback();
authCallback.setCode(authorizeDto.getCode());
authCallback.setState(authorizeDto.getState());
AuthResponse response = authRequest.login(authCallback);
AuthUser authUser = response.getData();
String openId = authUser.getUuid();
//1、使用openid查询用户
SocialUser socialUser = socialUserService.getSocialUserByUUID(openId, source);
if (socialUser == null) { //没有注册过
socialUser = socialUserService.createAndInitSocialUser(authUser, source);
}
socialUser.setAvatar(authUser.getAvatar());
socialUser.setUsername(authUser.getUsername());
socialUser.setNickname(authUser.getNickname());
socialUserService.updateById(socialUser);
long userId = socialUserAuthService.getUserIdBySocialUserId(socialUser.getSocialUserId());
userSourceMap.put(userId, source);
String jwt = getToken(userId);
UserBean userLoginTime = new UserBean();
userLoginTime.setUserId(userId);
userLoginTime.setLastLoginTime(DateUtil.getCurrentTime());
userService.updateUserInfo(userLoginTime);
UserBean user = userService.getById(userId);
UserLoginVo userLoginVo = new UserLoginVo();
BeanUtil.copyProperties(user, userLoginVo);
userLoginVo.setToken(jwt);
restResult.setData(userLoginVo);
restResult.setSuccess(true);
return restResult;
}
认证完成之后,后台会生成token并完成整个登录过程。
06
—
附录:数据库表
一共涉及三张表,分别是:
用户表:user
三方用户表: socialuser
关联表:social_user_auth
因为一个用户可以绑定多个三方账号,QQ,微信,微博等等,所以用户表和三方用户表实际上是一对多的关系,表设计如下:
CREATE TABLE `user` (
`userId` BIGINT(20) NOT NULL AUTO_INCREMENT,
`addrarea` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`addrcity` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`addrprovince` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`birthday` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`industry` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`intro` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`password` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`position` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`salt` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`sex` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`telephone` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`username` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`email` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`imageurl` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`registertime` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`lastlogintime` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`editortype` INT(11) NULL DEFAULT NULL,
`userStateId` INT(11) NULL DEFAULT NULL,
`available` INT(11) NULL DEFAULT NULL,
`modifyTime` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`modifyUserId` BIGINT(20) NULL DEFAULT NULL,
`openId` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`realname` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`qqPassword` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`qqStatus` BIGINT(20) NULL DEFAULT NULL COMMENT '1迁移数据,0废弃数据',
PRIMARY KEY (`userId`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2229
;
CREATE TABLE `socialuser` (
`socialUserId` BIGINT(20) NOT NULL AUTO_INCREMENT,
`accessCode` VARCHAR(200) NULL DEFAULT NULL COMMENT '个别平台的授权信息' COLLATE 'utf8mb4_general_ci',
`accessToken` VARCHAR(200) NULL DEFAULT NULL COMMENT '用户的授权令牌' COLLATE 'utf8mb4_general_ci',
`code` VARCHAR(200) NULL DEFAULT NULL COMMENT '用户的授权code' COLLATE 'utf8mb4_general_ci',
`expireIn` INT(11) NULL DEFAULT NULL COMMENT '第三方用户的授权令牌的有效期',
`idToken` VARCHAR(200) NULL DEFAULT NULL COMMENT 'id token' COLLATE 'utf8mb4_general_ci',
`macAlgorithm` VARCHAR(200) NULL DEFAULT NULL COMMENT '小米平台用户的附带属性' COLLATE 'utf8mb4_general_ci',
`macKey` VARCHAR(200) NULL DEFAULT NULL COMMENT '小米平台用户的福袋属性' COLLATE 'utf8mb4_general_ci',
`oauthToken` VARCHAR(200) NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性' COLLATE 'utf8mb4_general_ci',
`oauthTokenSecret` VARCHAR(200) NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性' COLLATE 'utf8mb4_general_ci',
`openId` VARCHAR(200) NULL DEFAULT NULL COMMENT '第三方用户的openId' COLLATE 'utf8mb4_general_ci',
`refreshToken` VARCHAR(200) NULL DEFAULT NULL COMMENT '刷新令牌' COLLATE 'utf8mb4_general_ci',
`scope` VARCHAR(200) NULL DEFAULT NULL COMMENT '第三方用户授予的权限' COLLATE 'utf8mb4_general_ci',
`source` VARCHAR(20) NULL DEFAULT NULL COMMENT '第三方用户来源 GITHUB,QQ,GITEE,具体参考 AuthDefaultSource' COLLATE 'utf8mb4_general_ci',
`tokenType` VARCHAR(200) NULL DEFAULT NULL COMMENT '个别平台的授权信息' COLLATE 'utf8mb4_general_ci',
`uId` VARCHAR(200) NULL DEFAULT NULL COMMENT '第三方用户的ID' COLLATE 'utf8mb4_general_ci',
`unionId` VARCHAR(200) NULL DEFAULT NULL COMMENT '第三方用户的unionid' COLLATE 'utf8mb4_general_ci',
`uuid` VARCHAR(200) NULL DEFAULT NULL COMMENT '第三方系统的唯一ID' COLLATE 'utf8mb4_general_ci',
`nickname` VARCHAR(200) NULL DEFAULT NULL COMMENT '第三方用户的nickname' COLLATE 'utf8mb4_general_ci',
`username` VARCHAR(200) NULL DEFAULT NULL COMMENT '第三方用户的username' COLLATE 'utf8mb4_general_ci',
`avatar` VARCHAR(200) NULL DEFAULT NULL COMMENT '第三方用户的头像' COLLATE 'utf8mb4_general_ci',
PRIMARY KEY (`socialUserId`) USING BTREE,
UNIQUE INDEX `UK_62b04yxuaqg0qfflxxfgjrd42` (`uuid`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=415
;
CREATE TABLE `social_user_auth` (
`socialUserAuthId` BIGINT(20) NOT NULL AUTO_INCREMENT,
`socialUserId` BIGINT(20) NULL DEFAULT NULL,
`userId` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`socialUserAuthId`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=424
;
07
—
号外!号外!
本文作者在蓝桥云课上线《经典项目:前后端分离网盘系统实战》课程啦!本课程主要使用 Spring Boot 2 和 Vue CLI@4 来开发 Web 端网盘系统。
如果你想开发一个网盘系统,此门课程将手把手教你,通过实战的方式,提高你的技术水平。
以下为课程知识重点:
如果你想学习更多此本课程,欢迎通过文末方式领取八折优惠哦~