diff --git a/controllers/AccountController.go b/controllers/AccountController.go index f5112dd9..5e801c1e 100644 --- a/controllers/AccountController.go +++ b/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{} { diff --git a/routers/router.go b/routers/router.go index 76ff51b8..d6f4cf36 100644 --- a/routers/router.go +++ b/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") diff --git a/utils/dingtalk/dingtalk.go b/utils/dingtalk/dingtalk.go index 53954abe..fa8e6883 100644 --- a/utils/dingtalk/dingtalk.go +++ b/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×tamp=%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 } diff --git a/views/account/login.tpl b/views/account/login.tpl index 04efc2a8..68d3a48a 100644 --- a/views/account/login.tpl +++ b/views/account/login.tpl @@ -70,6 +70,10 @@
+
+ 钉钉扫码登录 + +
{{if .ENABLED_REGISTER}} {{if ne .ENABLED_REGISTER "false"}}