整合钉钉扫码登录

整合钉钉扫码登录

一.构造扫码登录页面

官方参考网址
1.创建应用
链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.进入应用界面–配置回调域名。

进入已创建的应用详情页,在基础信息页面可以查看到应用的SuiteKey/SuiteSecret(第三方企业应用)或AppKey/AppSecret(企业内部应用)。
在这里插入图片描述

在应用详情页,然后单击钉钉登录与分享,添加应用回调的URL,以http或https开头。

在这里插入图片描述

官方示范使用的是frp内网穿透达到模拟公网访问的效果我这里是直接使用的nataapp操作起来比较方便一些

3.构造扫码登录页面。
Web系统可以通过两种方式实现钉钉扫码登录。

方式一:使用钉钉提供的扫码登录页面
在企业Web系统里,用户点击使用钉钉扫描登录时第三方Web系统跳转到如下地址:

https://oapi.dingtalk.com/connect/qrconnect?appid=SuiteKey&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=REDIRECT_URI

url里的参数需要换成第三方Web系统对应的参数。在钉钉用户扫码登录并确认后,会302到你指定的redirect_uri,并向url参数中追加临时授权码code及state两个参数。

重要
参数redirect_uri=REDIRECT_URI涉及的域名,需和登录配置的回调域名一致,否则会提示无权限>访问。
如果是企业内部应用,appid则为应用的AppKey;如果是第三方企业应用,appid则为应用的SuiteKey。

方式二:支持网站将钉钉登录二维码内嵌到自己页面中

用户使用钉钉扫码登录后JS会将loginTmpCode返回给网站。

JS钉钉登录主要用途:网站希望用户在网站内就能完成登录,无需跳转到钉钉域下登录后再返回,提升钉钉登录的流畅性与成功率。

网站内嵌二维码钉钉登录JS实现办法:
1.在页面中先引入如下JS文件(支持HTTPS)

<script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>

2.在需要使用钉钉登录的地方实例以下JS对象

/*
* 解释一下goto参数,参考以下例子:
* var url = encodeURIComponent('http://localhost.me/index.php?test=1&aa=2');
* var goto = encodeURIComponent('https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=SuiteKey&response_type=code&scope=snsapi_login&state=STATE&redirect_uri='+url)
*/
var obj = DDLogin({
     id:"login_container",//这里需要你在自己的页面定义一个HTML标签并设置id,例如<div id="login_container"></div>或<span id="login_container"></span>
     goto: "", //请参考注释里的方式
     style: "border:none;background-color:#FFFFFF;",
     width : "365",
     height: "400"
 });
参数说明
gotogoto参数结构:https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=SuiteKey&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=REDIRECT_URI,并且要将goto参数urlencode编码。
style渲染二维码的区域的样式,可以自定义去除背景颜色和边框。
width表示显示二维码的区域的宽。width和height不代表二维码的大小,二维码大小是固定的210px*210px。
height表示显示二维码的区域的高。width和height不代表二维码的大小,二维码大小是固定的210px*210px。

引入的js会在获取用户扫描之后将获取的loginTmpCode通过window.parent.postMessage(loginTmpCode,‘*’);返回给您的网站。

1.可以通过以下代码获取loginTmpCode:

var handleMessage = function (event) {
  var origin = event.origin;
  console.log("origin", event.origin);
  if( origin == "https://login.dingtalk.com" ) { //判断是否来自ddLogin扫码事件。
    var loginTmpCode = event.data; 
    //获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了
    
    console.log("loginTmpCode", loginTmpCode);
  }
};
if (typeof window.addEventListener != 'undefined') {
    window.addEventListener('message', handleMessage, false);
} else if (typeof window.attachEvent != 'undefined') {
    window.attachEvent('onmessage', handleMessage);
}

2.通过JS获取到loginTmpCode后,需要构造并跳转到如下链接:

https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=SuiteKey&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=REDIRECT_URI&loginTmpCode=loginTmpCode

3.此链接处理成功后,会302跳转到goto参数指定的redirect_uri,并向url参数中追加临时授权码code及state参数

参数是否必填说明
appid1.如果是企业内部应用,appid则为应用的AppKey。2.如果是第三方企业应用,appid则为应用的SuiteKey。
redirect_uri重定向地址。必须与开发者后台设置的回调域名保持一致。1.如果是第一种方式需要urlencode编码。2.如果是第二种方式则需要将JS goto参数整体urlencode编码,不要单独对redirect_uri编码,该地址为步骤一中配置的回调域名。 说明:重定向地址建议使用前端页面,否则可能出现重复追加code值的问题。
state用于防止重放攻击,开发者可以根据此信息来判断redirect_uri只能执行一次来避免重放攻击。
response_type固定为code。
scope固定为snsapi_login。
loginTmpCode通过js获取到的loginTmpCode。

3.服务端通过临时授权码获取授权用户的个人信息。
调用sns/getuserinfo_bycode接口获取授权用户的个人信息,详情请参考根据sns临时授权码获取用户信息。

4.根据unionid获取userid。
调用user/getbyunionid接口获取userid,详情请参考根据unionid获取用户userid。

5.根据userid获取用户详情。
调用user/get接口获取用户信息,详情请参考查询用户详情。

代码

参考网上若依开源项目改动而来使用的是vue2 不是3哦

前端代码
vue页面 login.vue
<template>
  <div class="login-container">
    <!-- 主要页面 -->
    <div class="login-weaper" v-if="loginif">
      <!--左边布局-->
      <div class="login-left">
        <p class="title">测试登录</p>
      </div>
      <!--右边布局-->
      <div class="login-border">
        <!--右上角图标-->
        <div class="right-top" @click="codeLogin" v-if="isHaveQRCode">
          <img src="../assets/image/code.png" v-if="!isCodeLogin"/>
          <img src="../assets/image/code2.jpg" v-else/>
        </div>
        <!-- 二维码登录 -->
        <div id="login_code" v-show="isCodeLogin"></div>
        <!--账号密码登录-->
        <div class="login-main" v-show="!isCodeLogin">
          <div class="login-title">用户登录</div>
          <el-form
            ref="loginForm"
            :model="loginForm"
            :rules="loginRules"
            size="medium"
          >
            <el-form-item prop="username">
              <el-input
                v-model="loginForm.username"
                type="text"
                auto-complete="off"
                placeholder="用户名"
              >
                <i class="el-icon-user" slot="prefix"></i>
              </el-input>
            </el-form-item>
            <el-form-item prop="password">
              <el-input
                v-model="loginForm.password"
                type="password"
                auto-complete="off"
                placeholder="密码"
                @keyup.enter.native="handleLogin"
              >
                <svg-icon
                  slot="prefix"
                  icon-class="password"
                  class="el-input__icon input-icon"
                />
              </el-input>
            </el-form-item>
            <el-form-item prop="code">
              <el-input
                v-model="loginForm.code"
                auto-complete="off"
                placeholder="验证码"
                style="width: 60%"
                @keyup.enter.native="handleLogin"
              >
                <svg-icon
                  slot="prefix"
                  icon-class="validCode"
                  class="el-input__icon input-icon"
                />
              </el-input>
              <!--验证码-->
              <div class="login-code">
                <img :src="codeUrl" @click="getCode" class="login-code-img"/>
              </div>
            </el-form-item>
            <el-checkbox
              v-model="loginForm.rememberMe"
              style="margin: 0px 0px 25px 0px"
            >记住密码
            </el-checkbox
            >
            <el-form-item style="width: 100%">
              <el-button
                :loading="loading"
                size="medium"
                type="primary"
                style="width: 100%; padding: 12px 20px"
                @click.native.prevent="handleLogin"
              >
                <span v-if="!loading">登 录</span>
                <span v-else>登 录 中...</span>
              </el-button>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </div>
    <div class="login_box" v-else>
      <span class="login_text">登录中请等待</span>
      <span v-loading="loading" class="loading"></span>
    </div>
  </div>
</template>
<script>
import { getConfig } from '@/api/demotoken/index'
import { getCodeImg } from '@/api/login'
import Cookies from 'js-cookie'
import { encrypt, decrypt } from '@/utils/jsencrypt'

export default {
  name: 'Login',
  data() {
    return {
      codeUrl: '',
      cookiePassword: '',
      loginForm: {
        username: '',//账号
        password: '',//密码
        rememberMe: false,//是否记住密码
        code: '',//输入框验证码
        uuid: ''//图片验证码信息
      },
      loginRules: {
        username: [
          { required: true, trigger: 'blur', message: '用户名不能为空' }
        ],
        password: [
          { required: true, trigger: 'blur', message: '密码不能为空' }
        ],
        code: [
          { required: true, trigger: 'change', message: '验证码不能为空' }
        ]
      },
      loading: false,
      redirect: undefined,
      loginif: true,
      //二维码登录
      isCodeLogin: false,
      //是否设置二维码
      isSet: false,
      //是否显示右上角登录方式
      isHaveQRCode: false
    }
  },
  /*监听器*/
  watch: {
    //监听当前路由的对象
    $route: {
      //当路由发生变化的时候handler就会被调用  这段代码主要是为了登录后,恢复至重定向之前的页面。
      handler: function(route) {
        this.redirect = route.query && route.query.redirect
      },
      immediate: true
    }
  },

  computed: {},
  //模块渲染后进行调用
  mounted() {
    //code是登录所需最终参数  判断当前的路由有没有携带关于code参数 有的话直接进行登录阶段
    if (this.$route.query && this.$route.query.code) {
      //登录接口
      this.handleCodeLogin(this.$route.query.code)
    } else {
      // this.ddLogin();
    }
  },
  //模块渲染之前进行调用
  created() {
    this.getCode()//生成验证码
    this.getCookie()//获取当前cookie的信息
    this.isHaveQRCode = true
    this.isCodeLogin = true
    //获取钉钉接口需要的参数
    getConfig().then((res) => {
      //获取会话中的值
      window.sessionStorage.setItem('appkey', res.data.appkey)
      window.sessionStorage.setItem('agentId', res.data.agentId)
      window.sessionStorage.setItem('corpId', res.data.corpId)
      if (res.data.accessKey) {
        //钉钉扫码登录appid
        window.sessionStorage.setItem('accessKey', res.data.accessKey)
        //钉钉二维码
        this.$nextTick((next) => {
          if (this.isCodeLogin && !this.isSet) {
            this.ddLogin()
          }
        })
      }
    })

  },
  methods: {
    //钉钉二维码生成
    ddLogin() {
      let url = 'http://' + location.host + '/login'
      let appid = window.sessionStorage.getItem('accessKey')
      let goto = `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${appid}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${url}`
      //生成钉钉二维码图片
      var obj = DDLogin({
        id: 'login_code', //这里需要你在自己的页面定义一个HTML标签并设置id,例如<div id="login_container"></div>或<span id="login_container"></span>
        goto: encodeURIComponent(goto), //请参考注释里的方式
        style: 'border:none;margin: 0 auto; background-color:inherit;',
        width: '500',
        height: '300'
      })
      //获取loginTmpCode
      var handleMessage = function(event) {
        var origin = event.origin
        if (origin == 'https://login.dingtalk.com') {
          //判断是否来自ddLogin扫码事件。
          var loginTmpCode = event.data
          //获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了
          //此步拿到临时loginTmpCode换取正式code 当前页面打开URL页面
          window.location.href = `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${appid}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${url}&loginTmpCode=${loginTmpCode}`
        }
      }
      if (typeof window.addEventListener != 'undefined') {
        window.addEventListener('message', handleMessage, false)
      } else if (typeof window.attachEvent != 'undefined') {
        window.attachEvent('onmessage', handleMessage)
      }
      this.isSet = true
    },

    /**
     * 登录
     * @param code免登code 后端会根据code来返回token
     */
    handleCodeLogin(code) {
      this.loginif = false
      let self = this
      this.$store.dispatch('getScanLogin', code).then(() => {
        self.$router.push({ path: '/' })
      })
        .catch(() => {
        })
    },
    //更换二维码登录
    codeLogin(res) {
      this.isCodeLogin = !this.isCodeLogin
    },

    /**
     * 生成验证码
     */
    getCode() {
      getCodeImg().then((res) => {
        this.codeUrl = 'data:image/gif;base64,' + res.img
        this.loginForm.uuid = res.uuid
      })
    },
    /**
     * 获取Cookies里面的信息
     */
    getCookie() {
      const username = Cookies.get('username')
      const password = Cookies.get('password')
      const rememberMe = Cookies.get('rememberMe')
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password:
          password === undefined ? this.loginForm.password : decrypt(password),
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
      }
    },
    handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.loading = true
          if (this.loginForm.rememberMe) {
            Cookies.set('username', this.loginForm.username, { expires: 30 })
            Cookies.set('password', encrypt(this.loginForm.password), {
              expires: 30
            })
            Cookies.set('rememberMe', this.loginForm.rememberMe, {
              expires: 30
            })
          } else {
            Cookies.remove('username')
            Cookies.remove('password')
            Cookies.remove('rememberMe')
          }
          this.$store
            .dispatch('Login', this.loginForm)
            .then(() => {
              this.$router.push({ path: this.redirect || '/' })
            })
            .catch(() => {
              this.loading = false
              this.getCode()
            })
        }
      })
    }
  }
}
</script>
<style scoped lang="scss">
.login-container {
  display: flex;
  align-items: center;
  -webkit-box-align: center;
  width: 100%;
  margin: 0 auto;
  background: url("../assets/image/login.png") no-repeat;
  background-color: #304175;
  position: relative;
  background-size: cover;
  height: 100vh;
  background-position: 50%;

  #particles {
    z-index: 1;
    width: 100%;
    height: 100%;
    position: absolute;
  }

  .login-weaper {
    width: 1000px;
    margin: 0 auto;
    box-shadow: -4px 5px 10px rgba(0, 0, 0, 0.4);
    z-index: 1000;

    .login-left {
      border-top-left-radius: 5px;
      border-bottom-left-radius: 5px;
      -webkit-box-pack: center;
      -webkit-box-orient: vertical;
      -webkit-box-direction: normal;
      background-color: rgba(64, 158, 255, 0);
      color: #ffffff;
      float: left;
      width: 50%;
      position: relative;
      min-height: 500px;
      -webkit-box-align: center;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;

      .login-time {
        position: absolute;
        left: 25px;
        top: 25px;
        width: 100%;
        color: #ffffff;
        opacity: 0.9;
        font-size: 18px;
        overflow: hidden;
        font-weight: 500;
      }

      .img {
        width: 120px;
        height: 120px;
        border-radius: 3px;
      }

      .title {
        text-align: center;
        color: #ffffff;
        letter-spacing: 2px;
        font-size: 30px;
        font-weight: 600;
      }
    }

    .login-border {
      display: flex;
      align-items: center;
      position: relative;
      min-height: 500px;
      -webkit-box-align: center;
      border-left: none;
      border-top-right-radius: 5px;
      border-bottom-right-radius: 5px;
      color: #ffffff;
      background-color: hsla(0, 0%, 100%, 0.9);
      width: 50%;
      float: left;

      .right-top {
        position: absolute;
        top: 0;
        right: 0;
        width: 53px;
        height: 53px;
        border-top-right-radius: 5px;

        > img {
          border-top-right-radius: 5px;
        }

      }

      .login-main {
        margin: 0 auto;
        width: 65%;

        .login-title {
          color: #333333;
          margin-bottom: 40px;
          font-weight: 500;
          font-size: 22px;
          text-align: center;
          letter-spacing: 4px;
        }

        /deep/ .el-input__inner {
          height: 50px;
          line-height: 50px;
          border: 1px solid rgba(0, 0, 0, 0.1);
          background: hsla(0, 0%, 100%, 0.8);
          border-radius: 5px;
          color: #454545;
          padding-left: 35px;
        }

        /deep/ .el-input__prefix {
          height: 100%;
          line-height: 50px;
          padding-left: 10px;
        }

        .login-code {
          width: 35%;
          height: 50px;
          float: right;

          > img {
            width: 100%;
            height: 100%;
            cursor: pointer;
            vertical-align: middle;
          }
        }
      }
    }
  }


}
</style>

code.png
在这里插入图片描述
code2.jpg
在这里插入图片描述

@/api/notoken/index
import axios from 'axios'
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000
})
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'


// 获取钉钉接口需要的参数--
export function getConfig() {
  return service({
    url: '/api/user/getDemoParameters',
    method: 'get'
  })
}

@/api/login
import request from '@/utils/request'

// 扫码登录
export function getDemoLogin(code) {

  return request({
    url: `/api/user/getDemoLogin?code=${code}`,
    method: 'post'
  })
}
// 登录方法
export function login(username, password, code, uuid) {
  const data = {
    username,
    password,
    code,
    uuid
  }
  return request({
    url: '/login',
    method: 'post',
    data: data
  })
}


// 获取用户详细信息
export function getInfo() {
  return request({
    url: '/getInfo',
    method: 'get'
  })
}

// 退出方法
export function logout() {
  return request({
    url: '/logout',
    method: 'post'
  })
}

// 获取验证码
export function getCodeImg() {
  return request({
    url: '/captchaImage',
    method: 'get'
  })
}

jsencrypt.js
import JSEncrypt from 'jsencrypt/bin/jsencrypt'

// todo 下面的???号可以参考密钥对生成 http://web.chacuo.net/netrsakeypair

const publicKey = '???'

const privateKey = '???'

// 加密
export function encrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey) // 设置公钥
  return encryptor.encrypt(txt) // 对数据进行加密
}

// 解密
export function decrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPrivateKey(privateKey) // 设置私钥
  return encryptor.decrypt(txt) // 对数据进行解密
}

user.js
import { login, logout, getInfo, newlogin ,getScanLogin} from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'

const user = {
  state: {
    token: getToken(),
    name: '',
    avatar: '',
    userid: '',
    roles: [],
    permissions: []
  },

  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_NAME: (state, name) => {
      state.name = name
    },
    SET_AVATAR: (state, avatar) => {
      state.avatar = avatar
    },
    SET_USERID: (state, userid) => {
      state.userid = userid
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    },
    SET_PERMISSIONS: (state, permissions) => {
      state.permissions = permissions
    }
  },

  actions: {
    // 扫码的登录
    getScanLogin({ commit }, code) {
      //异步请求
      return new Promise((resolve, reject) => {
        //将结果保存到本次会话中
        getScanLogin(code).then(res => {
          window.sessionStorage.setItem("userinfo", JSON.stringify(res.data));
          setToken(res.token)
          commit('SET_TOKEN', res.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },


    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid).then(res => {
          setToken(res.token)
          commit('SET_TOKEN', res.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 获取用户信息
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo(state.token).then(res => {
          // //console.log("getInfo")
          // //console.log(res)
          const user = res.user
          const avatar = user.avatar == "" ? require("@/assets/image/profile.jpg") : user.avatar;
          if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
            commit('SET_ROLES', res.roles)
            commit('SET_PERMISSIONS', res.permissions)
          } else {
            commit('SET_ROLES', ['ROLE_DEFAULT'])
          }
          commit('SET_NAME', user.nickName)
          commit('SET_USERID', user.userName)
          commit('SET_AVATAR', avatar)
          resolve(res)
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 退出系统
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        logout(state.token).then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          commit('SET_PERMISSIONS', [])
          removeToken()
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 前端 登出
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('SET_TOKEN', '')
        removeToken()
        resolve()
      })
    }
  }
}

export default user

public/index.html

引入https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <!-- <link rel="icon" href="<%= BASE_URL %>favicon.ico"> -->
  <script type="text/javascript"
          src="https://webapi.amap.com/maps?v=1.4.15&key=c318869d45386c7a83f4622d1d9925fb"></script>
  <script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
  <title><%= webpackConfig.name %></title>
  <style>
    html,
    body,
    #app {
      height: 100%;
      margin: 0px;
      padding: 0px;
    }

    .chromeframe {
      margin: 0.2em 0;
      background: #ccc;
      color: #000;
      padding: 0.2em 0;
    }

    #loader-wrapper {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 999999;
    }

    #loader {
      display: block;
      position: relative;
      left: 50%;
      top: 50%;
      width: 150px;
      height: 150px;
      margin: -75px 0 0 -75px;
      border-radius: 50%;
      border: 3px solid transparent;
      border-top-color: #FFF;
      -webkit-animation: spin 2s linear infinite;
      -ms-animation: spin 2s linear infinite;
      -moz-animation: spin 2s linear infinite;
      -o-animation: spin 2s linear infinite;
      animation: spin 2s linear infinite;
      z-index: 1001;
    }

    #loader:before {
      content: "";
      position: absolute;
      top: 5px;
      left: 5px;
      right: 5px;
      bottom: 5px;
      border-radius: 50%;
      border: 3px solid transparent;
      border-top-color: #FFF;
      -webkit-animation: spin 3s linear infinite;
      -moz-animation: spin 3s linear infinite;
      -o-animation: spin 3s linear infinite;
      -ms-animation: spin 3s linear infinite;
      animation: spin 3s linear infinite;
    }

    #loader:after {
      content: "";
      position: absolute;
      top: 15px;
      left: 15px;
      right: 15px;
      bottom: 15px;
      border-radius: 50%;
      border: 3px solid transparent;
      border-top-color: #FFF;
      -moz-animation: spin 1.5s linear infinite;
      -o-animation: spin 1.5s linear infinite;
      -ms-animation: spin 1.5s linear infinite;
      -webkit-animation: spin 1.5s linear infinite;
      animation: spin 1.5s linear infinite;
    }


    @-webkit-keyframes spin {
      0% {
        -webkit-transform: rotate(0deg);
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
        transform: rotate(360deg);
      }
    }

    @keyframes spin {
      0% {
        -webkit-transform: rotate(0deg);
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
        transform: rotate(360deg);
      }
    }


    #loader-wrapper .loader-section {
      position: fixed;
      top: 0;
      width: 51%;
      height: 100%;
      background: #7171C6;
      z-index: 1000;
      -webkit-transform: translateX(0);
      -ms-transform: translateX(0);
      transform: translateX(0);
    }

    #loader-wrapper .loader-section.section-left {
      left: 0;
    }

    #loader-wrapper .loader-section.section-right {
      right: 0;
    }


    .loaded #loader-wrapper .loader-section.section-left {
      -webkit-transform: translateX(-100%);
      -ms-transform: translateX(-100%);
      transform: translateX(-100%);
      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
    }

    .loaded #loader-wrapper .loader-section.section-right {
      -webkit-transform: translateX(100%);
      -ms-transform: translateX(100%);
      transform: translateX(100%);
      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
    }

    .loaded #loader {
      opacity: 0;
      -webkit-transition: all 0.3s ease-out;
      transition: all 0.3s ease-out;
    }

    .loaded #loader-wrapper {
      visibility: hidden;
      -webkit-transform: translateY(-100%);
      -ms-transform: translateY(-100%);
      transform: translateY(-100%);
      -webkit-transition: all 0.3s 1s ease-out;
      transition: all 0.3s 1s ease-out;
    }

    .no-js #loader-wrapper {
      display: none;
    }

    .no-js h1 {
      color: #222222;
    }

    #loader-wrapper .load_title {
      font-family: 'Open Sans';
      color: #FFF;
      font-size: 19px;
      width: 100%;
      text-align: center;
      z-index: 9999999999999;
      position: absolute;
      top: 60%;
      opacity: 1;
      line-height: 30px;
    }

    #loader-wrapper .load_title span {
      font-weight: normal;
      font-style: italic;
      font-size: 13px;
      color: #FFF;
      opacity: 0.5;
    }
  </style>
</head>
<body>
<div id="app">
  <div id="loader-wrapper">
    <div id="loader"></div>
    <div class="loader-section section-left"></div>
    <div class="loader-section section-right"></div>
    <div class="load_title">正在加载系统资源,请耐心等待</div>
  </div>
</div>
</body>
<script type="text/javascript">
  //第一步,创建XMLHttpRequest对象
  var xmlHttp = new XMLHttpRequest()

  function CommentAll() {
    //第二步,注册回调函数
    xmlHttp.onreadystatechange = callback1
    xmlHttp.open('get', '<%= process.env.VUE_APP_BASE_API %>/api/user/getSite', true)
    xmlHttp.send('')//"

  }

  //第五步,创建回调函数
  function callback1() {
    if (xmlHttp.readyState == 4) {
      if (xmlHttp.status == 200) {
        //取得返回的数据
        var data = xmlHttp.responseText
        //json字符串转为json格式
        data = JSON.parse(data)
        document.title = data.data.siteName
        var headHTML = document.getElementsByTagName('head')[0].innerHTML
        headHTML += '<link rel="icon" href="data:image/jpeg;base64,' + data.data.siteLogo + '">'
        document.getElementsByTagName('head')[0].innerHTML = headHTML
      }
    }
  }

  CommentAll()
</script>
</html>

有什么不对的可以在评论区留言,后期补充,我也是才接触第三方登录的一起学习一起进步ヾ(◍°∇°◍)ノ゙

后端代码
    @ApiOperation("demo钉钉扫码")
    @PostMapping("/getDemoLogin")
    @ApiImplicitParam(name = "code", value = "免登code", required = true, dataType = "String")
    @ResponseBody
    public AjaxResult getDemoLogin(String code) throws ApiException {
        AjaxResult ajaxResult = new AjaxResult();
        OapiUserGetResponse scanDdUser = DdUtils.getDemoUser(code);
        String token = loginController(scanDdUser);
        ajaxResult.put("code", 0);
        ajaxResult.put("msg", scanDdUser.getErrmsg());
        ajaxResult.put("data", scanDdUser);
        ajaxResult.put("token", token);
        return ajaxResult;
    }
@Component
public class DingDingUtils {


    public static String getAccessToken() throws ApiException {
        String appkey = "***";//这里的可以将对应的数据放在配置参数里面进行动态的获取
        String appsecret = "***";//这里的可以将对应的数据放在配置参数里面进行动态的获取
        DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
        OapiGettokenRequest request = new OapiGettokenRequest();
        request.setAppkey(appkey);
        request.setAppsecret(appsecret);
        request.setHttpMethod("GET");
        OapiGettokenResponse response = client.execute(request);
        return response.getAccessToken();
    }
   public static OapiUserGetResponse getDemoUser(String code) throws ApiException {
        String accessToken = DdUtils.getAccessToken();
        String accessKey = "***";
        String accessSecret = "***";
        // 通过临时授权码获取授权用户的个人信息
        DefaultDingTalkClient client2 = new DefaultDingTalkClient("https://oapi.dingtalk.com/sns/getuserinfo_bycode");
        OapiSnsGetuserinfoBycodeRequest reqBycodeRequest = new OapiSnsGetuserinfoBycodeRequest();
        // 通过扫描二维码,跳转指定的redirect_uri后,向url中追加的code临时授权码
        reqBycodeRequest.setTmpAuthCode(code);
        OapiSnsGetuserinfoBycodeResponse bycodeResponse = client2.execute(reqBycodeRequest, accessKey, accessSecret);
        // 根据unionid获取userid
        String unionid = bycodeResponse.getUserInfo().getUnionid();
        DingTalkClient clientDingTalkClient = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");
        OapiUserGetbyunionidRequest oapiUserGetbyunionIdRequest = new OapiUserGetbyunionidRequest();
        oapiUserGetbyunionIdRequest.setUnionid(unionid);
        OapiUserGetbyunionidResponse oapiUserGetbyunionidResponse = clientDingTalkClient.execute(oapiUserGetbyunionIdRequest, accessToken);
        String userid = oapiUserGetbyunionidResponse.getResult().getUserid();
        DingTalkClient client1 = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/get");
        OapiUserGetRequest request1 = new OapiUserGetRequest();
        request1.setUserid(userid);
        request1.setHttpMethod("GET");
        OapiUserGetResponse response = client1.execute(request1, accessToken);
        return response;
    }
}
 public String loginService(SysUser user)
    {
        Set<String> permissions = sysMenuService.selectMenuPermsByUserId(user.getUserId());
        LoginUser loginUser =new LoginUser();
        loginUser.setPermissions(permissions);
        loginUser.setUser(user);
        // 生成token
        String token = IdUtils.fastUUID();
        loginUser.setToken(token);
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + 30 * 60 * 1000);
        // 根据uuid将loginUser缓存
        String userKey ="login_tokens:" + loginUser.getToken();;
        
        redisCache.setCacheObject(userKey, loginUser, 60, TimeUnit.MINUTES);

        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);
        String token1 = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, "************").compact();
        return token1;
    }
    public String loginController(OapiUserGetResponse ddUser) {
        String token = null;
        if (ddUser != null && StringUtils.isNotEmpty(ddUser.getUserid())) {
            String userid = ddUser.getUserid();
            //查询数据库该用户是不是存在的 钉钉的userid匹配数据库对应的人员
            SysUser user1 = userService.selectUserByUserName(userid);
            if (user1 != null && user1.getUserId() != null && user1.getUserId() > 0) {
                // 老用户 生成令牌
                token = loginService.loginService(user1);
            } else {
               // todo  根据当前钉钉返回的人员信息将当前的人员保存到数据库里面人员表 部门表  角色表 以及其他的一些关联表
            }
        }
        return token;
    }
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个springboot整合微信扫码登录的示例: 1. 首先,在微信公众平台上创建一个应用,并获取到AppID和AppSecret。 2. 在Spring Boot项目中添加以下依赖: ``` <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>3.4.</version> </dependency> ``` 3. 在application.properties文件中配置AppID和AppSecret: ``` wechat.mp.appId=your_app_id wechat.mp.secret=your_app_secret ``` 4. 创建一个Controller,用于处理微信扫码登录的请求: ``` @RestController @RequestMapping("/wechat") public class WechatController { @Autowired private WxMpService wxMpService; @GetMapping("/login") public String login(HttpServletRequest request) throws WxErrorException { String redirectUrl = "http://your_domain.com/wechat/callback"; String state = UUID.randomUUID().toString(); String url = wxMpService.oauth2buildAuthorizationUrl(redirectUrl, WxConsts.OAuth2Scope.SNSAPI_USERINFO, state); return "redirect:" + url; } @GetMapping("/callback") public String callback(HttpServletRequest request) throws WxErrorException { String code = request.getParameter("code"); WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code); WxMpUser user = wxMpService.oauth2getUserInfo(accessToken, null); // TODO: 处理用户信息 return "success"; } } ``` 5. 在启动类中添加以下代码,初始化WxMpService: ``` @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public WxMpService wxMpService() { WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxMpConfigStorage()); return wxMpService; } @Bean public WxMpConfigStorage wxMpConfigStorage() { WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage(); configStorage.setAppId("your_app_id"); configStorage.setSecret("your_app_secret"); return configStorage; } } ``` 以上就是一个简单的springboot整合微信扫码登录的示例,希望对你有帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值