OAUTH之钉钉第三方授权 | GO主题月

简介: hello,我是小魔童哪吒,欢迎点击关注,有更新,将第一时间呈现到你的面前胖sir:小魔童,我今天收到了一个需求,期望我们做一个第三方登录的功能,用户可以通过第三方授权来登录我们的web

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] 。

主要有如下四种方式,简单给你列举一下:

  • 授权码
  • 隐藏式
  • 密码式
  • 客户端凭证

画一个简图来你感受一下

image.png

当然我不是给你说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

此处的 appkeyappsecret 是H5微应用里面的应用数据

image.png

响应

{
    "errcode": 0,
    "access_token": "4dbda4ddb82dxxxxxx138afab15655",
    "errmsg": "ok",
    "expires_in": 7200
}


扫码 / 使用账号密码 -- 获取 临时 code

参数重要说明

  • appId
    登录应用的 appId
  • redirect_uri - 回调域名
    重定向的url地址,登录成功后,网页会重定向到redirect_uri
    redirect_uri 必须要在钉钉开放平台配置好,否则会没有权限访问如下地址 ,例如该参数填百度的地址
  • LOGO地址
    自己在网络上的一张可以访问的图片地址即可,如下:
    image.png

image.png

直接访问 扫码登录

go

复制代码

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

image.png

使用账号密码登录第三方网站

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

image.png

如上2种方式登录成功后,是如下效果,暂时我们使用跳转的域名为https://www.baidu.com/主要是为了获取 临时code

image.png

根据 sns 临时授权码获取用户信息

通过 access_token 和 临时code 获取unionid

前置条件

  • 需要在钉钉开放平台上设置自己的服务器的出口ip白名单

image.png

请求地址

https://oapi.dingtalk.com/sns/getuserinfo_bycode?signature=xxx&timestamp=xxx&accessKey=xxx
x
  • POST
  • query
  • accessKey
    钉钉开放平台中,登录应用的  appId
    image.pngx
  • timestamp
    单位: 毫秒
  • signature
    签名算法为HmacSHA256,签名数据是当前时间戳timestamp,密钥是appId对应的appSecret,使用密钥对timestamp计算签名值。
    发送HTTP请求时需要把signature进行urlEncode,如果您使用的是HTTP封装方法,请确保不要重复urlEncode
  • body
{
        "tmp_auth_code":"4a2c5695b78738d495f47bxxxxxx"
}

image.pngx

响应

{
        "errcode":0,
        "user_info":{
                "nick":"名字",
                "unionid":"dingdkjjojoixxxx",
                "openid":"dingsdsqwlklklxxxx",
                "main_org_auth_high_level":true
        },
        "errmsg":"ok"
}

image.png

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&timestamp=%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"
}

image.png

响应

{
    "errcode": 0,
    "errmsg": "ok",
    "result": {
        "contact_type": 0,
        "userid": "managerxxxx"
    },
    "request_id": "xxxxxx"
}

image.png

根据userid 获取 用户详情

请求地址


https://oapi.dingtalk.com/topapi/v2/user/get?access_token=xxxxx

请求方法

  • POST
  • query
  • access_token
  • body 请求
{
   "language":"zh_CN",
   "userid":"managerxxxxx"
}

image.png

响应

{
    "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
}

image.png

响应

{
    "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"
}

image.png

获取部门用户 userid 列表

请求地址

https://oapi.dingtalk.com/topapi/user/listid?access_token=xxxxxx

请求方法

  • POST
  • query
  • access_token
  • body 请求
{
 "dept_id":xx
}

image.png

响应

{
    "errcode": 0,
    "errmsg": "ok",
    "result": {
        "userid_list": [
            "managerxxxxx"
        ]
    },
    "request_id": "xxxxx"
}

image.png

好了,本期就到这里了,要是对你还有点作用的话,还请帮忙点赞,评论,要是能够点个关注 或 转发到你的朋友圈,这将是对我最大的鼓励

技术是开放的,我们的心态更应如此,我们拥抱变化,心向阳光,坚定不移的实践我们的每一个想法。

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

image.png

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~

相关文章
|
5月前
|
Java
钉钉第三方扫码登录提示 code: 403, 没有调用该接口的权限,接口权限申请参考
钉钉第三方扫码登录提示 code: 403, 没有调用该接口的权限,接口权限申请参考 ,但是我明明申请了Contact.User.Read 这个权限
184 1
|
6月前
|
移动开发 算法 编译器
OAUTH之 钉钉第三方授权登录
OAUTH之 钉钉第三方授权登录
285 0
|
6月前
|
存储 开发者
钉钉企业内部应用与第三方企业应用的主要区别
钉钉企业内部应用与第三方企业应用的主要区别
148 1
|
9月前
|
缓存 前端开发
钉钉授权套件如果前端如何判断是否需要调用授权组件?
如图所示,教程提示我们需要缓存授权结果避免每次都需要调用,我们为啥要缓存,不是应该钉钉来判断是否需要唤起授权套件吗?假如我缓存了授权记录?那我怎么判断当前用户是否有授权呢?获取用户信息需要authcode,但是authcode需要授权通过才能获取到。
|
9月前
|
缓存 搜索推荐 网络安全
钉钉登录页面网页自动跳转,显示对不起,你无权限查看该页面,需要使用钉钉账号登录才可以进行授权
钉钉登录页面网页自动跳转,显示对不起,你无权限查看该页面,需要使用钉钉账号登录才可以进行授权
2449 1
|
10月前
|
存储 JSON Go
|
10月前
|
存储 安全 编译器
|
19小时前
|
前端开发 Go
Golang深入浅出之-Go语言中的异步编程与Future/Promise模式
【5月更文挑战第3天】Go语言通过goroutines和channels实现异步编程,虽无内置Future/Promise,但可借助其特性模拟。本文探讨了如何使用channel实现Future模式,提供了异步获取URL内容长度的示例,并警示了Channel泄漏、错误处理和并发控制等常见问题。为避免这些问题,建议显式关闭channel、使用context.Context、并发控制机制及有效传播错误。理解并应用这些技巧能提升Go语言异步编程的效率和健壮性。
7 5
Golang深入浅出之-Go语言中的异步编程与Future/Promise模式
|
19小时前
|
监控 负载均衡 算法
Golang深入浅出之-Go语言中的协程池设计与实现
【5月更文挑战第3天】本文探讨了Go语言中的协程池设计,用于管理goroutine并优化并发性能。协程池通过限制同时运行的goroutine数量防止资源耗尽,包括任务队列和工作协程两部分。基本实现思路涉及使用channel作为任务队列,固定数量的工作协程处理任务。文章还列举了一个简单的协程池实现示例,并讨论了常见问题如任务队列溢出、协程泄露和任务调度不均,提出了解决方案。通过合理设置缓冲区大小、确保资源释放、优化任务调度以及监控与调试,可以避免这些问题,提升系统性能和稳定性。
8 5
|
19小时前
|
安全 Go
Golang深入浅出之-Go语言中的并发安全队列:实现与应用
【5月更文挑战第3天】本文探讨了Go语言中的并发安全队列,它是构建高性能并发系统的基础。文章介绍了两种实现方法:1) 使用`sync.Mutex`保护的简单队列,通过加锁解锁确保数据一致性;2) 使用通道(Channel)实现无锁队列,天生并发安全。同时,文中列举了并发编程中常见的死锁、数据竞争和通道阻塞问题,并给出了避免这些问题的策略,如明确锁边界、使用带缓冲通道、优雅处理关闭以及利用Go标准库。
8 5

热门文章

最新文章