瀏覽代碼

backend for dingtalk QR login

LawyZHENG 4 年之前
父節點
當前提交
9b94192d99
共有 4 個文件被更改,包括 181 次插入3 次删除
  1. 75 0
      controllers/AccountController.go
  2. 1 0
      routers/router.go
  3. 101 3
      utils/dingtalk/dingtalk.go
  4. 4 0
      views/account/login.tpl

+ 75 - 0
controllers/AccountController.go

@@ -191,6 +191,81 @@ func (c *AccountController) DingTalkLogin() {
 	c.JsonResult(0, "ok", username)
 }
 
+// QR二维码登录
+func (c *AccountController) QRLogin() {
+	c.Prepare()
+
+	appName := c.Ctx.Input.Param(":app")
+
+	switch appName {
+	// 钉钉扫码登录
+	case "dingtalk":
+		code := c.GetString("code")
+		state := c.GetString("state")
+		if state != "1" || code == "" {
+			c.Redirect(conf.URLFor("AccountController.Login"), 302)
+			c.StopRun()
+		}
+		appKey := "dingoa0wp8qg6gyqtlyno1"
+		appSecret := "YPcijr8Wj47_N2jjEtE3wUsfiUwcTJqfJewgTs9mnHmaHAIsxAe92fmzy-XpdSMs"
+
+		qrDingtalk := dingtalk.NewDingtalkQRLogin(appSecret, appKey)
+		unionID, err := qrDingtalk.GetUnionIDByCode(code)
+		if err != nil {
+			beego.Warn("获取钉钉临时Token失败 ->", err)
+			c.Redirect(conf.URLFor("AccountController.Login"), 302)
+			c.StopRun()
+		}
+
+		appKey = beego.AppConfig.String("dingtalk_app_key")
+		appSecret = beego.AppConfig.String("dingtalk_app_secret")
+		tmpReader := beego.AppConfig.String("dingtalk_tmp_reader")
+
+		dingtalkAgent := dingtalk.NewDingTalkAgent(appSecret, appKey)
+		err = dingtalkAgent.GetAccesstoken()
+		if err != nil {
+			beego.Warn("获取钉钉临时Token失败 ->", err)
+			c.Redirect(conf.URLFor("AccountController.Login"), 302)
+			c.StopRun()
+		}
+
+		userid, err := dingtalkAgent.GetUserIDByUnionID(unionID)
+		if err != nil {
+			beego.Warn("获取钉钉临时Token失败 ->", err)
+			c.StopRun()
+		}
+
+		username, avatar, err := dingtalkAgent.GetUserNameAndAvatarByUserID(userid)
+		if err != nil {
+			beego.Warn("钉钉自动登录失败 ->", err)
+			c.Redirect(conf.URLFor("AccountController.Login"), 302)
+			c.StopRun()
+		}
+
+		member, err := models.NewMember().TmpLogin(tmpReader)
+		if err == nil {
+			member.LastLoginTime = time.Now()
+			_ = member.Update("last_login_time")
+			member.Account = username
+			if avatar != "" {
+				member.Avatar = avatar
+			}
+
+			c.SetMember(*member)
+			c.LoggedIn(false)
+			c.StopRun()
+		}
+		c.Redirect(conf.URLFor("AccountController.Login"), 302)
+
+		// fmt.Println(unionID)
+		// c.JsonResult(0, appName, nil)
+
+	default:
+		c.Redirect(conf.URLFor("AccountController.Login"), 302)
+		c.StopRun()
+	}
+}
+
 // 登录成功后的操作,如重定向到原始请求页面
 func (c *AccountController) LoggedIn(isPost bool) interface{} {
 

+ 1 - 0
routers/router.go

@@ -10,6 +10,7 @@ func init() {
 
 	beego.Router("/login", &controllers.AccountController{}, "*:Login")
 	beego.Router("/dingtalk_login", &controllers.AccountController{}, "*:DingTalkLogin")
+	beego.Router("/qrlogin/:app", &controllers.AccountController{}, "*:QRLogin")
 	beego.Router("/logout", &controllers.AccountController{}, "*:Logout")
 	beego.Router("/register", &controllers.AccountController{}, "*:Register")
 	beego.Router("/find_password", &controllers.AccountController{}, "*:FindPassword")

+ 101 - 3
utils/dingtalk/dingtalk.go

@@ -10,6 +10,9 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/url"
+	"strconv"
+	"strings"
+	"time"
 )
 
 // DingTalkAgent 用于钉钉交互
@@ -106,6 +109,46 @@ func (d *DingTalkAgent) GetUserNameAndAvatarByUserID(userid string) (string, str
 	return username, avatar, nil
 }
 
+// GetUserIDByUnionID 根据UnionID获取用户Userid
+func (d *DingTalkAgent) GetUserIDByUnionID(unionid string) (string, error) {
+	urlEndpoint, err := url.Parse("https://oapi.dingtalk.com/topapi/user/getbyunionid")
+	if err != nil {
+		return "", err
+	}
+
+	query := url.Values{}
+	query.Set("access_token", d.AccessToken)
+	urlEndpoint.RawQuery = query.Encode()
+	urlPath := urlEndpoint.String()
+
+	resp, err := http.PostForm(urlPath, url.Values{"unionid": {unionid}})
+	if err != nil {
+		return "", err
+	}
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	// 解析钉钉返回数据
+	var rdata map[string]interface{}
+	err = json.Unmarshal(body, &rdata)
+	if err != nil {
+		return "", err
+	}
+
+	errcode := rdata["errcode"].(float64)
+	if errcode != 0 {
+		return "", errors.New(fmt.Sprintf("登录错误: %.0f, %s", errcode, rdata["errmsg"].(string)))
+	}
+
+	result := rdata["result"].(map[string]interface{})
+	if result["contact_type"].(float64) != 0 {
+		return "", errors.New("该用户不属于企业内部员工,无法登录。")
+	}
+	userid := result["userid"].(string)
+	return userid, nil
+}
+
 // GetAccesstoken 获取钉钉请求Token
 func (d *DingTalkAgent) GetAccesstoken() (err error) {
 
@@ -132,16 +175,71 @@ func (d *DingTalkAgent) GetAccesstoken() (err error) {
 	return errors.New("accesstoken获取错误:" + i["errmsg"].(string))
 }
 
-func (d *DingTalkAgent) encodeSHA256(message string) string {
+// DingtalkQRLogin 用于钉钉扫码登录
+type DingtalkQRLogin struct {
+	AppSecret string
+	AppKey    string
+}
+
+// NewDingtalkQRLogin 构造钉钉扫码登录实例
+func NewDingtalkQRLogin(appSecret, appKey string) DingtalkQRLogin {
+	return DingtalkQRLogin{
+		AppSecret: appSecret,
+		AppKey:    appKey,
+	}
+}
+
+// GetUnionIDByCode 获取扫码用户UnionID
+func (d *DingtalkQRLogin) GetUnionIDByCode(code string) (userid string, err error) {
+	var resp *http.Response
+	//服务端通过临时授权码获取授权用户的个人信息
+	timestamp := strconv.FormatInt(time.Now().UnixNano()/1000000, 10) // 毫秒时间戳
+	signature := d.encodeSHA256(timestamp)                            // 加密签名
+	urlPath := fmt.Sprintf(
+		"https://oapi.dingtalk.com/sns/getuserinfo_bycode?accessKey=%s&timestamp=%s&signature=%s",
+		d.AppKey, timestamp, signature)
+
+	// 构造请求数据
+	param := struct {
+		Tmp_auth_code string `json:"tmp_auth_code"`
+	}{code}
+	paraByte, _ := json.Marshal(param)
+	paraString := string(paraByte)
+
+	resp, err = http.Post(urlPath, "application/json;charset=UTF-8", strings.NewReader(paraString))
+	if err != nil {
+		return "", err
+	}
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+
+	// 解析钉钉返回数据
+	var rdata map[string]interface{}
+	err = json.Unmarshal(body, &rdata)
+	if err != nil {
+		return "", err
+	}
+	errcode := rdata["errcode"].(float64)
+	if errcode != 0 {
+		return "", errors.New(fmt.Sprintf("登录错误: %.0f, %s", errcode, rdata["errmsg"].(string)))
+	}
+	unionid := rdata["user_info"].(map[string]interface{})["unionid"].(string)
+	return unionid, nil
+}
+
+func (d *DingtalkQRLogin) encodeSHA256(timestamp string) string {
 	// 钉钉签名算法实现
 	h := hmac.New(sha256.New, []byte(d.AppSecret))
-	h.Write([]byte(message))
+	h.Write([]byte(timestamp))
 	sum := h.Sum(nil) // 二进制流
 	tmpMsg := base64.StdEncoding.EncodeToString(sum)
 
 	uv := url.Values{}
 	uv.Add("0", tmpMsg)
-	message = uv.Encode()[2:]
+	message := uv.Encode()[2:]
 
 	return message
 }

+ 4 - 0
views/account/login.tpl

@@ -70,6 +70,10 @@
                 <div class="form-group">
                     <button type="button" id="btn-login" class="btn btn-success" style="width: 100%"  data-loading-text="正在登录..." autocomplete="off">立即登录</button>
                 </div>
+                <div class="form-group">
+                    <a href='https://oapi.dingtalk.com/connect/qrconnect?appid=dingoa0wp8qg6gyqtlyno1&response_type=code&scope=snsapi_login&state=1&redirect_uri={{ urlfor "AccountController.QRLogin" ":app" "dingtalk"}}' id="btn-dingtalk-qr" class="btn btn-default" style="width: 100%"  data-loading-text="" autocomplete="off">钉钉扫码登录</a>
+                    <!-- <a href='{{ urlfor "AccountController.QRLogin" ":app" "dingtalk"}}' id="btn-dingtalk-qr" class="btn btn-default" style="width: 100%"  data-loading-text="" autocomplete="off">钉钉扫码登录</a> -->
+                </div>
                 {{if .ENABLED_REGISTER}}
                 {{if ne .ENABLED_REGISTER "false"}}
                 <div class="form-group">