{"id":37103913,"url":"https://github.com/offer365/odin","last_synced_at":"2026-01-14T12:34:25.140Z","repository":{"id":57557296,"uuid":"209997805","full_name":"offer365/odin","owner":"offer365","description":"license auth server","archived":false,"fork":false,"pushed_at":"2020-01-11T05:25:53.000Z","size":5294,"stargazers_count":13,"open_issues_count":0,"forks_count":6,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-22T15:10:13.662Z","etag":null,"topics":["auth","authorization","edda","embedded","ember","etcd","go","golang","license","licenses","linux","odin","odin-api"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/offer365.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-21T14:19:01.000Z","updated_at":"2024-06-27T19:14:58.000Z","dependencies_parsed_at":"2022-09-15T19:00:28.848Z","dependency_job_id":null,"html_url":"https://github.com/offer365/odin","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/offer365/odin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offer365%2Fodin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offer365%2Fodin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offer365%2Fodin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offer365%2Fodin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/offer365","download_url":"https://codeload.github.com/offer365/odin/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offer365%2Fodin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28420810,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T10:47:48.104Z","status":"ssl_error","status_checked_at":"2026-01-14T10:46:19.031Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auth","authorization","edda","embedded","ember","etcd","go","golang","license","licenses","linux","odin","odin-api"],"created_at":"2026-01-14T12:34:24.413Z","updated_at":"2026-01-14T12:34:25.131Z","avatar_url":"https://github.com/offer365.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# odin #\n\n----\n\n## what this?\n- 应用在局域网中或互联网中的分布式授权服务。\n- odin 是一个 license server  用于给多种应用的多个客户端提供授权服务。\n- 目前只能运行在 linux 系统中。\n- 这个应用需要与[edda](https://github.com/offer365/edda) 配合使用。\n- 在 [edda](https://github.com/offer365/edda) 中生成授权码，在odin中激活。多个客户端或应用从odin 获取授权信息。\n- 被授权的应用可以通过 restful 或 gRPC 与 odin 交互。\n- 更多内容请阅读 [MORE.md](https://github.com/offer365/odin/blob/master/MORE.md)\n\n\n\n## 特点\n- 分布式。\n- 可以通过绑定应用的硬件信息鉴权。\n- 支持 https 加密通信。\n- 部署简单，无依赖。\n\n#### odin\n\u003e odin 可以根据 客户端提供的token 限制产品安装的位置。\n\n#### edda\n\u003e edda 不提供授权管理，审批流程，客户关系等功能\\\n\u003e 只实现了授权相关四个核心功能。\n\n**gRpc接口**\n\n```\nservice Authorization {\n    rpc Resolved (Cipher) returns (SerialNum); // 解析序列号\n    rpc Authorized (AuthReq) returns (AuthResp); // 授权\n    rpc Untied (UntiedReq) returns (Cipher); // 解绑\n    rpc Cleared (Cipher) returns (Clear);  // 清除\n}\n```\n\n\n## 技术栈\n- 使用 GoLang 语言开发。\n- 使用 gin web 框架。\n- 嵌入etcd。\n- 支持 RestFul 和 GRPC 传输数据。\n- 前后端分离 bootstrap + jquery + ajax 。\n\n## 安全\n- RestFul 和 gRPC 都使用 https 传输数据。\n- 序列号和授权码使用 aes32+rsa2048加密。Auth步骤可以采用多种加密方式。\n- 通过 ==到期时间== 与 ==生存周期== 相互印证防止时间篡改。\n- odin 不会存储明文，存储加密过或hash后的值。\n- N个节点(奇数)部署时，可以允许 (N-1)/2 个节点挂掉，且数据不会丢失。\n- 内嵌 etcd 支持 用户认证。\n\n## 性能\n- CPU:Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz * 2\n- MEM: 2G\n\n\u003e 单节点下1000个客户端同时访问。cpu:27%  mem:5%\\\n\u003e 三节点下1000个客户端同时访问。cpu:33%  mem:7%\n\n\n\n## 应用交互流程\n在demo/proto/auth.proto 包里定义了request 和 reponse 消息体\n\n###### request\n```\nmessage Request {\n    string app = 1; // 应用名称\n    string id = 2; // 应用ID\n    int64 date = 3; // 当前时间戳,客户端服务端误差不能超过600s\n    string verify = 4; // 该字段是密文,用于校验request的参数,客户端要根据 app,id,date,token(唯一且固定的值)加密生成;eg: {\"app\":\"nlp\",\"date\":1571987046,\"id\":\"app01\",\"token\":\"xxxxxx\"} 对此加密\n    string umd5 = 5; // response中返回的 data.cipher 解密后的值的md5;在active 步骤中此参数无效,仅在 keepline 与 offline中有效\n    int64 lease = 6; // 租约ID 在active 步骤中此参数无效,仅在 keepline 与 offline中有效\n}\n```\n###### response\n```\nmessage Data {\n    bytes auth = 1; // 解密后是应用的一些属性;eg:{\"attrs\":[{\"Name\":\"热词\",\"Key\":\"hotword\",\"Value\":111},{\"Name\":\"类热词\",\"Key\":\"classword\",\"Value\":111}],\"time\":1571994906931717352}\n    int64 lease = 2; // 租约ID\n    bytes cipher = 3; // 加密UUID生成的密文。\n}\n\nmessage Response {\n    int32 code = 1; // 返回状态码 200 OK;\n    Data data = 2;\n    string msg = 3; // 错误 或 成功的消息\n}\n```\n\n#### Auth 认证\n**request**\n\u003e request 必须包含 app,id,date,verify参数。\\\n\u003e verify: 该字段是密文,用于校验request的参数,app端要根据 app,id,date,token加密并base64编码生成。\\\n\u003e eg: {\"app\":\"nlp\",\"date\":1571987046,\"id\":\"app01\",\"token\":\"xxxxxx\"}\\\n\u003e token要求唯一且不可变，app端可以根据硬件信息，比如:Mac地址，机器码，主板编号，硬盘编号等生成。\\\n\u003e odin不会记录token的值，而是保存token的hash值，用来校验app端的身份。\\\n\u003e 一个app端只能有一个token,如果token变了需要解绑。\\\n\u003e odin会解密并校验verify的值与时间戳，值必须相等，时间戳误差不能\n超过600s.\n\n**response**\n\u003e 通过验证后，odin会将UUID和应用属性加密返回给app,分别是 response.data.cipher 和 response.data.auth字段\\\n\u003e例： \n```\n{\"attrs\":[{\"Name\":\"热词\",\"Key\":\"hotword\",\"Value\":111},{\"Name\":\"类热词\",\"Key\":\"classword\",\"Value\":111}],\"time\":1571994906931717352}\n```\n\u003e data 中包含 lease 表示租约id,这个字段会在keepline中用到。\\\n\u003e response.data.cipher 和 response.data.auth 建议采用不同的加密方式。\\\n\u003e 并且约定好 hash算法 和 是否加盐。\n\u003e app端收到 response 后解密，得到 uuid和认证信息 \n\n```\nsequenceDiagram\nApp-\u003e\u003eOdin: request消息\nOdin-\u003e\u003eApp: response消息\n```\n\n#### 在线 Keepline\n**request**\n\u003e app端将 在auth中获得的uuid hash计算后与 lease 一起发送给odin，分别对应request.umd5和reques.lease 字段。requset 中verify字段留空即可。其他的与auth步骤中一样。\n\u003e\n\n**response**\n\u003e odin会校验umd5值与lease，然后续租。如果app端超过10秒未发送数据。会认为app端下线，将app的信息删除。此时app端需要重新进行auth步骤。\n\u003e odin仅返回lease id。\n\n```\nsequenceDiagram\nApp-\u003e\u003eOdin: request消息\n```\n\n\n#### 下线 Offline\n**request**\n\u003e app端将 在auth步骤中获得的uuid hash 后与 lease 一起发送给odin，分别对应request.umd5和reques.lease 字段。requset 中verify字段留空即可。其他的与auth步骤中一样。\n\u003e\n\n**response**\n\u003e odin会校验umd5值与lease，然后将app的信息删除，认为app端下线。\\\n\u003e odin仅返回lease id。\n\n```\nsequenceDiagram\nApp-\u003e\u003eOdin: request消息\n```\n\n#### 绑定与解绑\n**绑定**\n\u003e 即：将 app/id : token  的对应关系存储到硬盘。\\\n\u003e 当一个新的app端发起auth认证时，如果是第一次认证就注册该app的id与token。\\\n\u003e 如果不是第一次就检查该app的id与存储的token是否对应，直到该应用的实例用尽。\\\n\u003e 所以要求app端生成的token固定且唯一。\\\n\u003e 该token可以是根据某个硬件信息生成，可以是密文或者明文。\\\n\u003e 总之odin 并不关心该token 的值，存储的仅仅是token的hash值。\\\n\u003e 对于不想使用绑定功能的应用可以 将token的值设为 \"app/id\" eg: \"nlp/app01\"。\\\n\u003e token 需要放在 auth 步骤的verify字段中。\\\n\u003e 在实际存储中app/id与toekn的对应关系可能是  \n```\neg: /NcJEs2UCgYEA/mh0SYJIGyadW/hash(app,salt)/hash(id,salt)    hash(token,salt)\n```\n\n**解绑**\n\u003e 解绑常常发生在app端硬件信息改变或app端迁移或其他问题导致token值发生改变。\\\n\u003e 需要将 app/id 与token 对应关系解除。\\\n\u003e 在 [edda](https://github.com/offer365/edda) 中输入 app/id,会产生一段密文。\\\n\u003e 这个密文里包含该app/id 在odin在的路径，odin解密后，比对路径与时间戳。\\\n\u003e 如果合法就将该 路径 与对应的token删除，从而解绑。app端再发次auth认证即可注册新的token。\n\n## 安装运行 ##\n#### 安装odin\n\n```\nunzip odin-xxx-linux.amd64.zip\ncd odin\nsh install.sh\n# 请先修改odin.yaml,appctl.sh 中的IP地址为本机IP。\n./appctl.sh resetcode \n./appctl.sh getcode\n```\n\n\u003e\n\u003e 访问 https://127.0.0.1:9527\n\n\n#### 相关说明\n\u003e 配置文件是 odin.yaml。\\\n\u003e 修改odin.service 可以指定程序与配置文件的位置。\\\n\u003e appctl.sh 封装了linux下的一些常用api操作。\\\n\u003e app端的测试，请参考 demo。\\\n\u003e 建议使用三个节点提供服务。\n\n## Demo\n- 目前仅实现了go语言的restful 和 grpc demo。在demo 目录下。\n\n## TODO\n* 序列号/授权码采用 椭圆曲线ECC加密算法 (maybe)\n* 支持 圣天诺Sentinel Time时钟锁 (maybe)\n* 前端交互支持 session，不再使用 BasicAtuh。Linux下，curl访问时 根据 UserAgent区分 使用 BasicAtuh 认证。(maybe)\n* 提高测试代码覆盖率。\n* 内嵌 etcd 支持https。\n* 支持 windows。实现获取硬件信息接口。(maybe)\n\n## 使用介绍 ##\n1. 先安装odin 并运行。访问web端口，默认账号密码：admin:123 可在配置文件odin.yaml 中修改。\n2. 使用web 或访问api 接口生成 序列号。\n3. 在 [edda](https://github.com/offer365/edda) 里根据约定新建应用，并配置该应用的属性。\n4. 将序列号在 [edda](https://github.com/offer365/edda) 解析，根据实际情况填写，生成license。\n5. 在 odin 的激活页面或相关api 导入序列号激活。\n6. 客户端或应用访问 odin 的client api。详细示例见API.md和demo\n\n\n## License\n[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foffer365%2Fodin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foffer365%2Fodin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foffer365%2Fodin/lists"}