(十五)ATP应用测试平台——使用JustAuth快速集成前后端分离的第三方扫码授权登录功能

Posted 北溟溟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(十五)ATP应用测试平台——使用JustAuth快速集成前后端分离的第三方扫码授权登录功能相关的知识,希望对你有一定的参考价值。

前言

扫码登录已经是很多官方网站的标配,为了快速登录一个网站,避免繁琐的注册、登录流程,很多网站都会选择跳过自身的注册登录功能,授权用户第三方登录,如微信扫码登录、百度扫码登录等等。本节内容我们以百度扫码登录为例,便于演示。(微信扫码需要公司账号注册,这里就果断放弃了,有账号的小伙伴可自行配置验证)其它登录只需要加入相应的配置即可集成,作者这里使用java反射机制实例化对应的扫码登录实例,所以代码层面不需要任何修改,加入相关账号配置即可。

正文

  • 引入JustAuth第三方扫码集成依赖pom
<!-- https://mvnrepository.com/artifact/me.zhyd.oauth/JustAuth -->
<dependency>
	<groupId>me.zhyd.oauth</groupId>
	<artifactId>JustAuth</artifactId>
	<version>1.16.5</version>
</dependency>

  • 注册百度开发者账号,申请应用

(1)百度开发者账号注册

注册地址:注册百度帐号

(2)百度开发者登录地址

登录地址:百度开发者中心-汇聚、开放、助力、共赢

(3)应用oauth授权配置

配置地址:管理控制台 - 百度开放云平台

  • 后端代码 

①application.yml配置扫码登录参数,可配置不同方式的扫码登录,这里以百度为例

oauth:
  list:
    - appName: BAIDU
      clientId: ar46mCkZ77v03pfUkGhZGogD
      clientSecret: ulQPHyrbisRCSQcD2nXMx3ygOcO2GNU5
      redirectUri: http://4qfn3q.natappfree.cc/oauthUser/callback?appName=BAIDU

 ②OauthDataProperties.java加载扫码登录的配置参数

package com.yundi.atp.platform.util.oauth;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @Author: 北溟溟
 * @Description: 第三方登录授权配置
 * @Date: 2022/1/5 18:45
 * @Version: 1.0.0
 */
@Data
@Component
@ConfigurationProperties(prefix = "oauth")
public class OauthDataProperties 
    private List<OauthData> list;


    public static class OauthData 
        /**
         * 应用名称:微信、QQ、DingDing
         */
        private String appName;
        /**
         * 秘钥key
         */
        private String clientId;
        /**
         * 秘钥secret
         */
        private String clientSecret;
        /**
         * 回调地址
         */
        private String redirectUri;

        public String getAppName() 
            return appName;
        

        public void setAppName(String appName) 
            this.appName = appName;
        

        public String getClientId() 
            return clientId;
        

        public void setClientId(String clientId) 
            this.clientId = clientId;
        

        public String getClientSecret() 
            return clientSecret;
        

        public void setClientSecret(String clientSecret) 
            this.clientSecret = clientSecret;
        

        public String getRedirectUri() 
            return redirectUri;
        

        public void setRedirectUri(String redirectUri) 
            this.redirectUri = redirectUri;
        
    

③封装扫码接口工具类OauthUtil.java

package com.yundi.atp.platform.util.oauth;

import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.request.AuthDefaultRequest;
import me.zhyd.oauth.request.AuthRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Constructor;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author: 北溟溟
 * @Description: 第三方登录工具类
 * @Date: 2022/1/5 18:25
 * @Version: 1.0.0
 */
@Slf4j
@Component
public class OauthUtil 
    @Autowired
    private OauthDataProperties oauthDataProperties;

    public AuthRequest getAuthRequest(String appName) 
        try 
            List<OauthDataProperties.OauthData> collect = oauthDataProperties.getList().stream().filter(item -> item.getAppName().equals(appName)).collect(Collectors.toList());
            if (!collect.isEmpty()) 
                OauthDataProperties.OauthData oauthData = collect.get(0);
                AuthConfig authConfig = AuthConfig.builder()
                        .clientId(oauthData.getClientId())
                        .clientSecret(oauthData.getClientSecret())
                        .redirectUri(oauthData.getRedirectUri())
                        .build();
                AuthDefaultSource authDefaultSource = AuthDefaultSource.valueOf(oauthData.getAppName());
                Class<? extends AuthDefaultRequest> targetClass = authDefaultSource.getTargetClass();
                String className = targetClass.getName();
                Class<?> clazz = Class.forName(className);
                Constructor constructor = clazz.getConstructor(AuthConfig.class);
                AuthRequest authRequest = (AuthRequest) constructor.newInstance(authConfig);
                return authRequest;
            
         catch (Exception e) 
            log.info("登录参数初始化异常!");
        
        return null;
    



 ④实现扫码登录的三个接口OauthUserController.java

package com.yundi.atp.platform.module.sys.controller;

import com.yundi.atp.platform.common.Result;
import com.yundi.atp.platform.util.oauth.OauthUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: 北溟溟
 * @Description: 第三方授权登录
 * @Date: 2022/1/5 19:23
 * @Version: 1.0.0
 */
@Api(tags = "第三方登录")
@RestController
@RequestMapping(value = "/oauthUser")
public class OauthUserController 
    private static Map<String, AuthResponse> map = new HashMap<>(10000);

    @Autowired
    private OauthUtil oauthUtil;

    @ApiOperation(value = "第三方登录")
    @GetMapping(value = "/login/appName")
    public Result login(@PathVariable(value = "appName") String appName) 
        AuthRequest authRequest = oauthUtil.getAuthRequest(appName);
        String authorize = authRequest.authorize(AuthStateUtils.createState());
        return Result.success(authorize);
    

    @ApiOperation(value = "第三方登录回调地址,根据回调结果处理登录的后续流程")
    @GetMapping(value = "/callback")
    public String callback(@RequestParam(value = "appName") String appName, AuthCallback callback) 
        AuthRequest authRequest = oauthUtil.getAuthRequest(appName);
        AuthResponse authResponse = authRequest.login(callback);
        if (authResponse.getCode() == 2000) 
            map.put(callback.getState(), authResponse);
            return "登录成功!";
        
        return "登录失败!";
    

    @ApiOperation(value = "获取登录结果")
    @GetMapping(value = "/getLoginResult")
    public Result getLoginResult(@RequestParam(value = "state") String state) 
        AuthResponse authResponse = map.get(state);
        if (authResponse != null) 
            map.remove(state);
            return Result.success();
        
        return Result.fail("登录授权失败,请重新扫码登录!");
    


  • 前端代码

①安装vue-qr二维码组件

命令:npm install vue-qr --save

 ②前端代码

<template>
  <div class="container">
    <el-form ref="form" :model="form" :rules="rules" label-width="70px" class="login">
      <h3>ATP应用测试平台</h3>
      <el-form-item label="用户名" prop="name">
        <el-input v-model="form.name"></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="pass">
        <el-input v-model="form.pass" type="password" show-password></el-input>
      </el-form-item>
      <el-form-item label="验证" prop="isLock">
        <slider-verify-code v-model="form.isLock" @change="handlerLock"></slider-verify-code>
      </el-form-item>
      <el-button type="primary" @click="login" style="width: 100%;margin: 0;">立即登录</el-button>
      <div style="margin-top: 20px;text-align: left;">
        <img alt="百度" src="@/assets/baidu.png" style="width:30px;height:30px;margin: 0 0 0 10px;cursor: pointer;"
             @click="oauthLogin('BAIDU')"/>
      </div>
      <el-dialog
          @close='closeQrCodeDialog'
          :visible.sync="show"
          :fullscreen=true
          :modal=true
          width="100%">
        <h5 style="margin: 0 0 10px 0;font-size: 20px;letter-spacing: 1px; color: white; font-weight: normal;">
           qrCodeName 扫码认证中心</h5>
        <vue-qr :text="qrCode" :size="300" :logoSrc="logo"></vue-qr>
      </el-dialog>
    </el-form>
  </div>
</template>

<script>
import sliderVerifyCode from '@/components/slider-verify-code.vue';

export default 
  name: "Login",
  data() 
    const checkStatus = (rule, value, callback) => 
      if (!value) 
        return callback(new Error("请拖动滑块完成验证"));
       else 
        if (this.form.name == '' || this.form.pass == ''
            || !this.form.name || !this.form.pass) 
          setTimeout(() => 
            this.form.isLock = false;
            this.$refs.form.validateField('name');
            this.$refs.form.validateField('pass');
            return callback(new Error("验证未通过"));
          , 1);
        
        callback();
      
    ;
    return 
      form: 
        name: 'super_admin',
        pass: 'super_admin',
      ,
      rules: 
        name: [
          required: true, message: '用户名称不得为空!', trigger: 'blur',
          min: 6, max: 18, message: '长度在 6 到 18 个字符', trigger: 'blur'
        ],
        pass: [
          required: true, message: '密码不得为空!', trigger: 'blur',
          min: 6, max: 18, message: '长度在 6 到 18 个字符', trigger: 'blur'
        ],
        isLock: [
          validator: checkStatus, trigger: 'blur',
        ],
      ,
      qrCode: '',
      show: false,
      qrCodeName: '',
      logo: require('../assets/logo.png'),
      timer: '',
      refreshTimer: '',
    
  ,
  components: 
    'slider-verify-code': sliderVerifyCode
  ,
  methods: 
    //关闭扫码框
    closeQrCodeDialog() 
      this.show = false;
      clearInterval(this.timer);
      clearInterval(this.refreshTimer);
    ,
    //登录
    login() 
      this.$refs.form.validate((valid) => 
        if (valid) 
          this.$http.post('/sys/user/login', this.$qs.stringify(this.form)).then(res => 
            if (res.data.code === 1) 
              sessionStorage.setItem("username", this.form.name);
              this.$router.push('/home');
             else 
              this.$refs.form.resetFields();
              this.$message.warning(res.data.msg);
            
          ).catch(error => 
            this.$message.error(error);
          );
         else 
          return false;
        
      );
    ,
    //第三方登录
    oauthLogin(data) 
      this.qrCodeName = data;
      this.oauthLoginCommit(this.qrCodeName);
      this.refreshTimer = setInterval(() => 
        this.oauthLoginCommit(this.qrCodeName);
      , 1000*60*3);
    ,
    //登录接口
    oauthLoginCommit(data)
      this.$http.get('/oauthUser/login/' + data).then(res => 
        if (res.data.code === 1) 
          this.qrCode = res.data.data;
          this.show = true;
          this.checkOauthStatus(this.qrCode);
         else 
          this.$message.warning(res.data.msg);
        
      ).catch(error => 
        this.$message.error(error);
      );
    ,
    //检测登录状态
    checkOauthStatus(data) 
      const state = this.getQueryString(data, "state");
      clearInterval(this.timer);
      this.timer = setInterval(() => 
        this.$http.get('/oauthUser/getLoginResult?state=' + state).then(res => 
          if (res.data.code === 1) 
            this.$router.push('/home');
            clearInterval(this.timer);
            clearInterval(this.refreshTimer);
          
        ).catch(error => 
          this.$message.error(error);
        );
      , 2000);
    ,
    //获取访问路径中的参数
    getQueryString(url, key) 
      const reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)", "i");
      const r = url.substr(1).match(reg);
      if (r != null) return unescape(r[2]);
      return null;
    ,
    handlerLock(data) 
      if (data) 
        this.$refs.form.validateField('isLock');
      
    ,
  

</script>

<style scoped lang="scss">
.container 
  width: 100%;
  height: 100%;
  overflow: auto;
  background: rgb(84, 92, 100);

  ::v-deep .el-dialog, .el-pager li 
    background: #6d6d6d !important;
  


  .login 
    text-align: center;
    padding: 20px 30px 30px 30px;
    margin: 10% auto;
    width: 320px;
    background: white;
    border-radius: 10px;

    h3 
      margin: 30px 0px;
    

    .el-form-item 
      margin-bottom: 35px;
    
  

</style>

  •  测试效果

结语

OK,本节内容到这里就结束了。通过使用JustAuth我们可以快速的集成我们第三方的一些扫码登录功能,其本质是对于各种第三方扫码功能的封装,我们也可以按照自己的需求自行封装,原则上都是调用第三方认证中心的授权接口,完成授权功能,获取到第三方的用户信息。本节我们通过java反射机制获取我们的第三方授权实例对象,并通过前端定时器定时更新我们的二维码,并实时检测扫码登录的回调结果,从而实现前后端分离的扫码登录功能。

以上是关于(十五)ATP应用测试平台——使用JustAuth快速集成前后端分离的第三方扫码授权登录功能的主要内容,如果未能解决你的问题,请参考以下文章

(十四)ATP应用测试平台——使用docker-compose一键式安装ATP应用测试平台的依赖服务

ATP应用测试平台——关于axios的配置使用

ATP应用测试平台——关于vue-router前端路由的配置使用案例

(十九)ATP应用测试平台——springboot集成RocketMQ案例实战

(二十三)ATP应用测试平台——阿里云短信发送功能集成

ATP应用测试平台——关于vue中Vue-Quill-EditorMavon-EditorTinymce等多种富文本编辑器的集成使用