在 Spring Boot 前后端分离系统中集成 JustAuth 实现第三方账号登录?

本文介绍了如何在前后端分离系统中使用开源组件JustAuth实现QQ登录。首先在QQ互联平台申请应用,然后在后台集成JustAuth并配置QQ应用信息。用户点击QQ登录后,前端将请求发送到后台接口,通过AuthQqRequest进行处理。回调地址在前端捕获,携带code和state参数请求后台。后台获取用户信息并认证,最终完成登录。涉及到的数据库表包括用户表、三方用户表和关联表。
摘要由CSDN通过智能技术生成

 JustAuth 是一个开箱即用的整合第三方登录的开源组件,网上没有搜到它在前后端分离系统中的使用案例,本篇文章将以 QQ 登录为例为大家讲解该场景下的使用步骤,建议收藏 7c7694481283f346f14f4f79f6ec4786.png

ba161df31b87bf7f690a9b3091817603.png

01

申请 QQ 应用

登录 QQ 互联平台

https://connect.qq.com

申请开发者

进入“应用管理”页面:https://connect.qq.com/manage.html#

如果是第一次使用,并且未进行过开发者认证,需要提交一下个人资料,待认证通过后方可创建应用。

添加应用

依次点击:应用管理 -> 网站应用 -> 创建应用,应用信息提交后,等待审核通过即可。

应用审核通过后如下:

ade178327b15db27922d4d1fb7f6e71d.png

此时可以在登录页面上放置 QQ 图标:

7d7c31efe2f4cb8b234a3c988c8e130c.png

02

后台集成 JustAuth

引入依赖

<dependency>
  <groupId>me.zhyd.oauth</groupId>
  <artifactId>JustAuth</artifactId>
  <version>${latest.version}</version>
</dependency>

${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 登录认证界面。

d86a9f08923a7665a466d4269cf626c5.png

认证成功之后,继续跳转至我们的项目中,当跳转到我们的项目之后,请求路径会同时会在地址栏带回登录需要的参数(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<AuthUser> response = authRequest.login(callback);
    log.info(JSONObject.toJSONString(response));


    if (response.ok()) {
        userService.save(response.getData());
        return new ModelAndView("redirect:/users");
    }


    Map<String, Object> map = new HashMap<>(1);
    map.put("errorMsg", response.getMsg());


    return new ModelAndView("error", map);
}


从上面可以看出,后台接收 QQ 回调地址,处理完成后直接返回 ModelAndView 到前端,这里的做法显然不符合前后端分离系统,所以我的做法是用前端代码去接收回调地址,并请求后台:

<template>
  <div class="bind-qq">
    <i class="link-qq-icon el-icon-link">正在登录,请稍等...</i>
  </div>
</template>
<script>
import { authorize, bind } from '@/request/user.js'
import Cookies from 'js-cookie'


export default {
  name: 'QQProcessing',
  data() {
    return {
      openid: '',
      access_token: '',
      waitInfo: ''
    }
  },
  created() {
    let code = this.$route.query.code
    let state = this.$route.query.state
  },
  methods: {
    authorize(code, state) {
      let data = {
        code: code,
        state: state
      }
      authorize(data).then(res => {
        if (res.success) {
          this.$store.dispatch('getUserInfo').then(() => {
            this.$router.push({
              path: '/'
            })
          })
        } else {
          this.$message.error(res.message)
        }
      })
    }
  }
}
</script>

上段代码便是从地址里面去拿到 code,state 这两个参数,并请求后台接口。

05

获取第三方用户信息并认证

后台拿到前台传过来的 code 和 state 参数之后,便可以获取第三方用户信息并认证,代码段如下:

@Operation(summary = "第三方平台登录",  tags = {"user"})
@MyLog(operation = "第三方平台登录")
@RequestMapping(value = "/authorize/{source}", method = RequestMethod.POST)
public RestResult<UserLoginVo> authorize(@PathVariable("source") String source, @RequestBody AuthorizeDto authorizeDto) {


    RestResult<UserLoginVo> restResult = new RestResult<UserLoginVo>();


    AuthRequest authRequest = getAuthRequest(source);
    AuthCallback authCallback = new AuthCallback();
    authCallback.setCode(authorizeDto.getCode());
    authCallback.setState(authorizeDto.getState());
    AuthResponse<AuthUser> 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 端网盘系统。

如果你想开发一个网盘系统,此门课程将手把手教你,通过实战的方式,提高你的技术水平。

以下为课程知识重点:

07698c81a08dd0d1bcb90c3938617a08.png

如果你想学习更多此本课程,欢迎通过文末方式领取八折优惠哦~

295a6321839c7049a900fe40c4cb1266.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值