OAUTH之钉钉第三方授权 | GO主题月
OAUTH之钉钉第三方授权
hello,我是小魔童哪吒,欢迎点击关注,有更新,将第一时间呈现到你的面前
胖sir:小魔童,我今天收到了一个需求,期望我们做一个第三方登录的功能,用户可以通过第三方授权来登录我们的web
小魔童:啊哈?你有眉目吗
胖sir:那当然,我知道可以通过微信登录,钉钉登录,github登录等等呢
小魔童:那你知道都是咋实现的吗?说给我听听,让我也学一下
胖sir:你带我跑飞车吗?
小魔童:这。。 你。。 我教你如何一骑绝尘把,前提是你给我讲明白咋弄
胖sir:稳了,成交。
其实这种第三方登录的原理属于OAuth机制,主要用来颁发令牌(token),现在OAuth已经到 OAuth2.0了
用百度百科的话说:
OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC 6749 [1] 。
主要有如下四种方式,简单给你列举一下:
- 授权码
- 隐藏式
- 密码式
- 客户端凭证
画一个简图来你感受一下
当然我不是给你说OAuth自身的原理和涉及的技术,我是要来直接给你说咱们咋实现我刚才说的对接钉钉的接口,因为钉钉的开发文档中间有修改过好几次,
另外文档中的表述也存在晦涩难懂的地方,鉴于你带我飞车 一骑绝尘,我就给你说说 如何获取到钉钉的授权,以及拿到使用钉钉对应公司(必须有公司管理员的权限)下的组织结构
钉钉开发文档没有golang版本的SDK和源码,那么我们就自己来实现
前期用到的工具
- postman 做接口调试
- golang 语言 通过 goland 编译器 做
通过 access_token 和 临时 code 获取 unionid
的功能
获取access_token
请求地址
https://oapi.dingtalk.com/gettoken?appkey=xxx&appsecret=xxx
请求方法
- GET
- query
- appkey
- appsecret
此处的 appkey
和 appsecret
是H5微应用里面的应用数据
响应
{ "errcode": 0, "access_token": "4dbda4ddb82dxxxxxx138afab15655", "errmsg": "ok", "expires_in": 7200 }
扫码 / 使用账号密码 -- 获取 临时 code
参数重要说明
- appId
登录应用的 appId - redirect_uri - 回调域名
重定向的url地址,登录成功后,网页会重定向到redirect_uri
redirect_uri 必须要在钉钉开放平台配置好,否则会没有权限访问如下地址 ,例如该参数填百度的地址 - LOGO地址
自己在网络上的一张可以访问的图片地址即可,如下:
直接访问 扫码登录
go
复制代码
https://oapi.dingtalk.com/connect/qrconnect?appid=xxxx&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=https://www.baidu.com/
使用账号密码登录第三方网站
https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=xxx&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=https://www.baidu.com/ x
如上2种方式登录成功后,是如下效果,暂时我们使用跳转的域名为https://www.baidu.com/
主要是为了获取 临时code
根据 sns 临时授权码获取用户信息
通过 access_token 和 临时code 获取unionid
前置条件
- 需要在钉钉开放平台上设置自己的服务器的出口ip白名单
请求地址
https://oapi.dingtalk.com/sns/getuserinfo_bycode?signature=xxx×tamp=xxx&accessKey=xxx x
- POST
- query
- accessKey
钉钉开放平台中,登录应用的 appId
x - timestamp
单位: 毫秒 - signature
签名算法为HmacSHA256,签名数据是当前时间戳timestamp,密钥是appId对应的appSecret,使用密钥对timestamp计算签名值。
发送HTTP请求时需要把signature进行urlEncode,如果您使用的是HTTP封装方法,请确保不要重复urlEncode
- body
{ "tmp_auth_code":"4a2c5695b78738d495f47bxxxxxx" }
x
响应
{ "errcode":0, "user_info":{ "nick":"名字", "unionid":"dingdkjjojoixxxx", "openid":"dingsdsqwlklklxxxx", "main_org_auth_high_level":true }, "errmsg":"ok" }
golang 具体操作和逻辑
对于这个接口的签名计算方式,需要给你看看是如何实现的,具体实现很简单,但是对于时间戳的取值,需要注意是毫秒级别的
package main import ( "bytes" "crypto/hmac" "crypto/sha256" "crypto/tls" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "time" ) func main() { // 登录应用的 appSecret secret := "xxxx" key := []byte(secret) h := hmac.New(sha256.New, key) timestamp := time.Now().UnixNano()/1e6 + 120000 //因为我的环境时间比钉钉服务器慢2分钟,所以我这里加了2分钟 strTimeStamp := fmt.Sprintf("%d", timestamp) h.Write([]byte(strTimeStamp)) sha := h.Sum(nil) sig := base64.StdEncoding.EncodeToString(sha) mysig := url.QueryEscape(sig) url := fmt.Sprintf("https://oapi.dingtalk.com/sns/getuserinfo_bycode?signature=%s×tamp=%d&accessKey=%s", mysig, timestamp, "xxxx") fmt.Println(url) tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } spaceClient := http.Client{Timeout: time.Second * time.Duration(5), Transport: tr} m := map[string]string{"tmp_auth_code": "67d86cb135ee3bd18d756c2d2fa1a350"} res, err := json.Marshal(m) if err != nil { fmt.Println(res) return } fmt.Println(string(res)) req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(res)) req.Header.Set("Content-Type", "application/json") rawResp, err := spaceClient.Do(req) if err != nil { fmt.Println(rawResp) return } if rawResp.Status != "200 OK" { fmt.Println("rawResp.Status != 200 ok", rawResp) return } body, readErr := ioutil.ReadAll(rawResp.Body) if readErr != nil { fmt.Println("ReadAll error ", readErr) return } fmt.Println("result --", string(body)) }
根据 unionid 获取用户 userid
请求地址
https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=xxxxxx
请求方法
- POST
- query
- access_token
- body 请求
{ "unionid":"xxxxxx" }
响应
{ "errcode": 0, "errmsg": "ok", "result": { "contact_type": 0, "userid": "managerxxxx" }, "request_id": "xxxxxx" }
根据userid 获取 用户详情
请求地址
https://oapi.dingtalk.com/topapi/v2/user/get?access_token=xxxxx
请求方法
- POST
- query
- access_token
- body 请求
{ "language":"zh_CN", "userid":"managerxxxxx" }
响应
{ "errcode": 0, "errmsg": "ok", "result": { "active": true, "admin": true, "avatar": "", "boss": false, "dept_id_list": [ 1 ], "dept_order_list": [ { "dept_id": 1, "order": 176299320823645512 } ], "email": "", "exclusive_account": false, "hide_mobile": false, "leader_in_dept": [ { "dept_id": 1, "leader": false } ], "mobile": "xxxxx", "name": "xxx", "real_authed": true, "role_list": [ { "group_name": "默认", "id": 1993003008, "name": "主管理员" } ], "senior": false, "state_code": "86", "unionid": "xxxxx", "userid": "managerxxxxx" }, "request_id": "xxxxx" }
获取部门列表
请求地址
https://oapi.dingtalk.com/topapi/v2/department/listsub?access_token=xxxxx
请求方法
- POST
- query
- access_token
- body请求
{ "language":"zh_CN", "dept_id":1 }
响应
{ "errcode": 0, "errmsg": "ok", "result": [ { "auto_add_user": true, "create_dept_group": true, "dept_id": 477856721, "name": "运营部", "parent_id": 1 }, { "auto_add_user": true, "create_dept_group": true, "dept_id": 477856722, "name": "设计部", "parent_id": 1 } ], "request_id": "evcmse04h8op" }
获取部门用户 userid 列表
请求地址
https://oapi.dingtalk.com/topapi/user/listid?access_token=xxxxxx
请求方法
- POST
- query
- access_token
- body 请求
{ "dept_id":xx }
响应
{ "errcode": 0, "errmsg": "ok", "result": { "userid_list": [ "managerxxxxx" ] }, "request_id": "xxxxx" }
好了,本期就到这里了,要是对你还有点作用的话,还请帮忙点赞,评论,要是能够点个关注 或 转发到你的朋友圈,这将是对我最大的鼓励
技术是开放的,我们的心态更应如此,我们拥抱变化,心向阳光,坚定不移的实践我们的每一个想法。
欢迎点赞,关注,收藏
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是阿兵云原生,欢迎点赞关注收藏,下次见~