钉钉扫码登录接入WEB第三方系统---前端 vue 后台golang - Go语言中文社区

钉钉扫码登录接入WEB第三方系统---前端 vue 后台golang


1、首先在钉钉开放平台,配置移动接入应用的登录,域名,获取appid 和 appSecret,(后台服务需要)

 

 

2、我在前端的登录页面放置了钉钉的扫码登录的二维码,

login.vue代码

<template>
  <div class="login-container">
      <div id="login_container" class="dingding_scan" ></div> 
  </div>
</template>



<script>



import { ddlogin } from "@/api/dashboard";
import { getQueryVariable } from "@/utils";



export default {
  name: "Login",
  
  data() {
    return {}
      
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect;
      },
      immediate: true
    }
  },
  created() {
  },
  mounted() {
    this.ddLogin();

  },
  destroyed() {
  },
  methods: {
    ddLogin() {
      let code = getQueryVariable("code"); //查看当前Url中有没有state的这个参数,如果有这个参数证明扫码登录成功重定向地址已经调转完成
      if (code) {
        let state = getQueryVariable("state");
        let temp = {
          code: code,
          state: state
        };
        //钉钉跳转的url后会携带code,state,要传给后台,即调用后台api 
        //因为系统需要,我在状态里面调用并报错了,你们可以自行写自己的接口调用就行
        this.$store.dispatch("user/ddlogin", temp).then(() => {

          //调用后,会获取当前系统的权限信息和登录用户的信息
          // 因为我是跳转到当前页面,所以,当前页面直接回带codehe state,
          //我调用api后,就不需要这些参数了,就删除了了url后携带的参数,直接跳转首页
          var url = window.location.href; //获取当前页面的url
          if (url.indexOf("?") != -1) {
            //判断是否存在参数
            url = url.replace(/(?|#)[^'"]*/, ""); //去除参数
            window.history.pushState({}, 0, url);
          }
          //然后跳转首页
          this.$router.replace({ path: "/" });
        });
      } else {
        //默认二维码的显示
        let appid='dingoasvam8tvfkuezexjo'
        let strurl= window.location.protocol+"//"+window.location.host+'/'
        let url = encodeURIComponent(strurl);
        console.log('window.location.host  跳转',url)
        let goto = encodeURIComponent(
          "https://oapi.dingtalk.com/connect/qrconnect?appid="+appid +"&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=" +
            url
        );
        var obj = DDLogin({
          id: "login_container",
          goto: goto,
          style: "border:none;background-color:#FFFFFF;",
          width: "365",
          height: "400"
        });

        var hanndleMessage = function(event) {
          var origin = event.origin;
          // console.log("origin", event.origin);
            //判断是否来自ddLogin扫码事件。
          if (origin == "https://login.dingtalk.com") {
            var loginTmpCode = event.data; //拿到loginTmpCode后就可以在这里构造跳转链接进行跳转了
            // console.log("loginTmpCode", loginTmpCode);
            if (loginTmpCode) {
              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;
              //拿到用户唯一标示然后在进行当前页面的调转,注意这里跳转以后还是会在当前页面只不过Url上带了参数了这时候咱们去看上面的条件
            }
          }
        };
        if (typeof window.addEventListener != "undefined") {
          window.addEventListener("message", hanndleMessage, false);
        } else if (typeof window.attachEvent != "undefined") {
          window.attachEvent("onmessage", hanndleMessage);
        }
      }
    },
  }
};
</script>

<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;
.dingding_scan{
  margin-top: 10%;
  float: right;
  width: 500px;
  height: 500px;
}
}
</style>

 

2、utils文件里面跑出的方法:getQueryVariable

 

//判断当前url是否携带参数
export function getQueryVariable(variable)
{
       var query = window.location.search.substring(1);
       var vars = query.split("&");
       for (var i=0;i<vars.length;i++) {
               var pair = vars[i].split("=");
               if(pair[0] == variable){return pair[1];}
       }
       return(false);
}

go 配置文件dingding.yaml

AppKey: dingoasxxxxxxxxxxxuezexjo
AppSecret: kdLP8FbJY2xxxxxxxxxxxxxxx5gRUyU_7gHL432hpuL6Qul2jF
DDServerAddress: https://oapi.dingtalk.com/sns/getuserinfo_bycode //钉钉后台用code请求当前扫码人的信息链接

 

读取配置文件

 

package dd

import (
	"io/ioutil"

	log "github.com/Sirupsen/logrus"
	"gopkg.in/yaml.v2"
)

var DdConf DDConf

type DDConf struct {
	AppKey         string `yaml:"AppKey"`
	AppSecret string `yaml:"AppSecret"`
	DDServerAddress           string `yaml:"DDServerAddress"`
}

func init() {
	yamlFile, err := ioutil.ReadFile("./conf/dingding.yaml")
	if err != nil {
		log.Fatal("init conf/dingding.yaml发生错误:", err)
	}
	err = yaml.Unmarshal(yamlFile, &DdConf)
	if err != nil {
		log.Fatal("init conf/dingding.yaml发生错误:", err)
	}
	return
}

go 后台服务api

 

package user

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	dd "server/common/dd"
	jwt "server/common/jwt"
	models "server/models"
	table_user "server/models/table/user"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/gin-gonic/gin"
)

type DdLogin struct {
}

type dDLoginReq struct {
	TmpAuthCode string `json:"tmp_auth_code"` //临时授权码
}
type RetDDUserInfo struct {
	Nick                 string `json:"nick"`                     //钉钉昵称
	Unionid              string `json:"unionid"`                  //Unionid
	DingId               string `json:"dingId"`                   //DingId
	Openid               string `json:","openid"`                 //Openid
	MainOrgAuthHighLevel bool   `json:"main_org_auth_high_level"` //MainOrgAuthHighLevel
}

type DDResp struct {
	Errcode  int64         `json:"errcode"`   //错误码
	Errmsg   string        `json:"errmsg"`    //错误信息
	UserInfo RetDDUserInfo `json:"user_info"` //用户信息
}

type RetddModule struct {
	Id   int64  `json:"id"`
	Name string `json:"name"` //模块名字
	Op   int64  `json:"op"`   //模块权限定义值
}
type dDLoginResp struct {
	StatusCode int    `json:"status_code"` //状态码
	StatusMsg  string `json:"status_msg"`  //状态信息
	UserId     int64  `json:"user_id"`     //用户id
	Name       string `json:"name"`        //用户名称
	RoleId     int64  `json:"role_id"`     //用户所属角色id
	RoleName   string `json:"role_name"`   //用户所属角色名称

	Phone     string        `json:"phone"`
	Email     string        `json:"email"`
	Avatar    string        `json:"avatar"`
	JobNumber string        `json:"job_number"`
	Token     string        `json:"token"`   //登录token
	Modules   []RetddModule `json:"modules"` //模块定义
}

// @Summary  钉钉登录接口
// @Description 无
// @Tags user
// @Accept  json
// @Produce json
// @Param 请求体 body user.dDLoginReq true "请求体"
// @Success 200 {object} user.dDLoginResp "返回体"
// @Router /algorithm_platform_api/v1/user/dd_login [post]
func (this *DdLogin) DdLogin(c *gin.Context) {
	var resp dDLoginResp
	str_code, _ := c.GetQuery("code")
	timestamp := time.Now().UnixNano() / 1e6
	strTimeStamp := fmt.Sprintf("%d", timestamp)

	appKey := dd.DdConf.AppKey // 读取配置文件 appid
	appSecret := dd.DdConf.AppSecret
	signature := ComputeHmacSha256(strTimeStamp, appSecret) //签名
	signature = url.QueryEscape(signature)
	//post请求提交json数据
	var ddreq dDLoginReq
	ddreq.TmpAuthCode = str_code
	ba, _ := json.Marshal(ddreq)
	targetUrl := fmt.Sprintf("%s?accessKey=%s&timestamp=%d&signature=%s", dd.DdConf.DDServerAddress, appKey, timestamp, signature)

	tr := &http.Transport{
		////把从服务器传过来的非叶子证书,添加到中间证书的池中,使用设置的根证书和中间证书对叶子证书进行验证。
		// TLSClientConfig: &tls.Config{RootCAs: pool},
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //InsecureSkipVerify用来控制客户端是否证书和服务器主机名。如果设置为true,//
		//则不会校验证书以及证书中的主机名和服务器主机名是否一致。
	}
	client := &http.Client{Transport: tr}
	resp_dingding, err := client.Post(targetUrl, "application/json", bytes.NewBuffer([]byte(ba)))
	if err != nil {
		log.Error(err)
		resp.StatusCode = 1001
		resp.StatusMsg = "请求参数错误"
		c.JSON(http.StatusOK, resp)
		return
	}

	defer resp_dingding.Body.Close()
	body, err := ioutil.ReadAll(resp_dingding.Body)
	if err != nil {
		log.Error(err)
		resp.StatusCode = 1001
		resp.StatusMsg = "请求参数错误"
		c.JSON(http.StatusOK, resp)
		return
	}

	log.Debug("到这里    钉钉登录=user信息==========================:", string(body))
	log.Debug("下面是我平台系统的逻辑  ==========================:")



















	var ddResp DDResp
	//解析json结构体
	json.Unmarshal([]byte(body), &ddResp)


	//先查找钉钉用户表,用Unionid查找,找到返回信息,找不到插入信息
	diongdingUser := new(table_user.DdUser)
	log.Debug("ddResp=先查找钉钉用户表,用Unionid查找,找到返回信息,找不到插入信息==================================:")
	has, err := models.UserDb.Where("`unionid` = ?", ddResp.UserInfo.Unionid).Get(diongdingUser)
	if err != nil {
		log.Error("查询DdUser表发生错误:", err.Error())
		resp.StatusCode = 1003
		resp.StatusMsg = "系统内部错误"
		c.JSON(http.StatusOK, resp)
		return
	}
	//之前存在系统中按协议返回,不存在则新加user表和dd_user
	if has {
		user := new(table_user.User)
		//has, err := models.UserDb.Id(diongdingUser.UserId).Get(user)
		has, err := models.UserDb.Where("`id` = ?", diongdingUser.UserId).Get(user)
		if err != nil {
			log.Error("查询user表发生错误:", err.Error())
			resp.StatusCode = 1004
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}
		if !has {
			log.Error("user表中没发现id:", diongdingUser.UserId)
			resp.StatusCode = 1005
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}
		//是否是禁止登录用户
		if user.State == PROHIBIT_LOGIN {
			log.Warn("系统禁止登录用户:", diongdingUser.Name)
			resp.StatusCode = 1006
			resp.StatusMsg = "系统禁止登录用户"
			c.JSON(http.StatusOK, resp)
			return
		}
		role := new(table_user.Role)
		//has, err = models.UserDb.Id(user.RoleId).Get(role)
		has, err = models.UserDb.Where("`id` = ?", user.RoleId).Get(role)
		if err != nil {
			log.Error(err)
			resp.StatusCode = 1007
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}
		if !has {
			log.Warn(fmt.Sprintf("role表中没有id:%d", user.RoleId))
			resp.StatusCode = 1008
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}

		permissions := make([]table_user.Permission, 0)
		err = models.UserDb.Where("`role_id` = ?", user.RoleId).Find(&permissions)
		if err != nil {
			log.Error(err)
			resp.StatusCode = 1009
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}
		modules := make([]RetddModule, 0)
		for i := 0; i < len(permissions); i++ {
			module_id := permissions[i].ModuleId
			module := new(table_user.Module)
			//has, err = models.UserDb.Id(module_id).Get(module)
			has, err = models.UserDb.Where("`id` = ?", module_id).Get(module)
			if err != nil {
				log.Error(err)
				resp.StatusCode = 1010
				resp.StatusMsg = "系统内部错误"
				c.JSON(http.StatusOK, resp)
				return
			}
			if !has {
				log.Warn(fmt.Sprintf("moudle表中没有发现id:%d", module_id))
				resp.StatusCode = 1011
				resp.StatusMsg = "系统内部错误"
				c.JSON(http.StatusOK, resp)
				return
			}
			var retModule RetddModule
			retModule.Id = module.Id
			retModule.Name = module.Name
			retModule.Op = permissions[i].CrudOperation
			modules = append(modules, retModule)
		}

		token := jwt.GenToken(user.Id)

		resp.StatusCode = 1000
		resp.StatusMsg = "ok"
		resp.UserId = user.Id
		resp.Name = ddResp.UserInfo.Nick

		//从钉钉后台获取的信息
		// resp.Phone = userInfo.Tel
		// resp.Email = userInfo.Email
		// resp.Avatar = userInfo.Avatar
		// resp.JobNumber = userInfo.JobNumber

		resp.RoleId = role.Id
		resp.RoleName = role.Name
		resp.Token = token
		resp.Modules = modules
		c.JSON(http.StatusOK, resp)
		return

	} else {
		//这里看是否要按名称绑定,现在不添加绑定逻辑,只有添加新的
		//先拿到默认角色
		role := new(table_user.Role)
		has, err = models.UserDb.Where("`level` = ?", DEFAULT_USER_ROLE_LEVEL).Get(role)
		if err != nil {
			log.Error(err)
			resp.StatusCode = 1012
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}
		if !has {
			log.Warn(fmt.Sprintf("role表中没有level:%d", DEFAULT_USER_ROLE_LEVEL))
			resp.StatusCode = 1013
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}

		//先生成user表记录
		//这块最好的做法是能过事务来做,但golang xorm没有发现没有Flush方法,没法获取到先提交的用户id
		//不存在系统中增加到系统中,生成默认角色
		var user table_user.User
		user.Name = ddResp.UserInfo.Nick
		// user.Unionid = ddResp.UserInfo.Unionid
		// user.Email = userInfo.Email
		// user.Phone = userInfo.Tel
		user.RoleId = role.Id
		//开始事务时这里不能用models.DB.Insert得用session.Insert
		_, err = models.UserDb.Insert(&user)
		if err != nil {
			log.Error(err)
			resp.StatusCode = 1014
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}

		//再生成dd_user表记录
		var diongdingUser table_user.DdUser
		//这个id能自动映射

		diongdingUser.Unionid = ddResp.UserInfo.Unionid
		diongdingUser.UserId = user.Id
		diongdingUser.Name = ddResp.UserInfo.Nick
		diongdingUser.Openid = ddResp.UserInfo.Openid
		// diongdingUser.Email = userInfo.Email
		// diongdingUser.Phone = userInfo.Tel
		_, err = models.UserDb.Insert(&diongdingUser)
		if err != nil {
			log.Error("增加dd用户表记录发生错误:", err)
			//要删除之前添加的用户
			_, err = models.UserDb.Where("id = ?", user.Id).Delete(new(table_user.User))
			if err != nil {
				log.Error("增加dd用户表记录失败,在删除对应的user表中记录时发生错误:", err)
			}
			resp.StatusCode = 1015
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}

		//user role都有了,下面逻辑和上面一样
		permissions := make([]table_user.Permission, 0)
		err = models.UserDb.Where("`role_id` = ?", user.RoleId).Find(&permissions)
		if err != nil {
			log.Error(err)
			resp.StatusCode = 1009
			resp.StatusMsg = "系统内部错误"
			c.JSON(http.StatusOK, resp)
			return
		}
		modules := make([]RetddModule, 0)
		for i := 0; i < len(permissions); i++ {
			module_id := permissions[i].ModuleId
			module := new(table_user.Module)
			has, err = models.UserDb.Id(module_id).Get(module)
			if err != nil {
				log.Error(err)
				resp.StatusCode = 1010
				resp.StatusMsg = "系统内部错误"
				c.JSON(http.StatusOK, resp)
				return
			}
			if !has {
				log.Warn(fmt.Sprintf("moudle表中没有发现id:%d", module_id))
				resp.StatusCode = 1011
				resp.StatusMsg = "系统内部错误"
				c.JSON(http.StatusOK, resp)
				return
			}
			var retModule RetddModule
			retModule.Id = module.Id
			retModule.Name = module.Name
			retModule.Op = permissions[i].CrudOperation
			modules = append(modules, retModule)
		}

		token := jwt.GenToken(user.Id)

		resp.StatusCode = 1000
		resp.StatusMsg = "ok"
		resp.UserId = user.Id
		resp.Name = user.Name
		resp.RoleId = role.Id
		resp.RoleName = role.Name

		//从钉钉后台获取的信息
		// resp.Phone = userInfo.Tel
		// resp.Email = userInfo.Email
		// resp.Avatar = userInfo.Avatar
		// resp.JobNumber = userInfo.JobNumber

		resp.Token = token
		resp.Modules = modules
		c.JSON(http.StatusOK, resp)
		return
	}

}


//钉钉签名
func ComputeHmacSha256(message string, secret string) string {
	key := []byte(secret)
	h := hmac.New(sha256.New, key)
	h.Write([]byte(message))
	sha := h.Sum(nil)
	return base64.StdEncoding.EncodeToString([]byte(sha))
}

 

 

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_33878858/article/details/106429741
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-30 08:34:02
  • 阅读 ( 1852 )
  • 分类:前端

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢