1
0
mirror of synced 2026-05-22 21:53:18 +00:00

Compare commits

...

205 Commits

Author SHA1 Message Date
yadong.zhang a75bac2cf7 🔖 发布1.9.2,修改AuthConfig类,适配spring starter配置 2019-07-22 17:30:18 +08:00
yadong.zhang 666d50b461 Merge branch 'master' into dev 2019-07-22 17:21:00 +08:00
yadong.zhang 7515301f50 Merge pull request #26 from xkcoding/refactor-AuthConfig
♻️ 添加注解,方便 justauth-spring-boot-starter 直接使用
2019-07-22 16:39:13 +08:00
Yangkai.Shen cab222059c ♻️ 添加注解,方便 justauth-spring-boot-starter 直接使用 2019-07-22 15:29:48 +08:00
yadong.zhang 798e7556ba 🔖 增加stackoverflow参数校验、解决Pinterest获取用户失败的问题,发布v1.9.1 2019-07-20 17:21:21 +08:00
yadong.zhang 1244524da0 Merge branch 'master' of https://github.com/zhangyd-c/JustAuth 2019-07-19 22:28:35 +08:00
yadong.zhang ce1f71c707 📌 version 2019-07-19 22:27:49 +08:00
yadong.zhang 93c301e05b 优化代码 2019-07-19 22:25:44 +08:00
yadong.zhang 53df992474 优化代码、修复google获取用户信息失败的问题 2019-07-19 22:00:07 +08:00
yadong.zhang e29df531fa Merge branch 'dev'
# Conflicts:
#	example.md
#	src/main/java/me/zhyd/oauth/request/AuthMiRequest.java
#	src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java
#	src/main/java/me/zhyd/oauth/utils/UrlBuilder.java
2019-07-19 19:03:41 +08:00
yadong.zhang 3a04098e4c 优化代码 2019-07-19 18:05:24 +08:00
yadong.zhang 81691a3462 🍻 优化代码 2019-07-19 14:26:42 +08:00
yadong.zhang 6708525d99 Merge pull request #25 from dyc12ii/master
升级fastjson版本至1.2.58,避免安全漏洞
2019-07-19 11:19:44 +08:00
dingyanchao a599ced6b0 fastjson version 2019-07-19 11:15:08 +08:00
yadong.zhang f815f12acc 🍻 调整部分内容 2019-07-18 23:41:34 +08:00
yadong.zhang 78cb32a9f2 Merge branch 'dev' of https://gitee.com/yadong.zhang/JustAuth into dev 2019-07-18 22:48:56 +08:00
yadong.zhang 6505df15a7 🍻 调整部分内容 2019-07-18 22:48:23 +08:00
yadong.zhang d7d87c7f04 更新 example.md 2019-07-18 22:42:14 +08:00
yadong.zhang 188e98c0f2 🍻 醉酒写代码 2019-07-18 21:59:05 +08:00
yadong.zhang f2fb942808 🍻 醉酒写代码 2019-07-18 21:57:29 +08:00
yadong.zhang d1d7587f4e 🔀 merge 2019-07-18 21:55:23 +08:00
yadong.zhang d5e60165c0 Merge pull request #23 from xkcoding/remove-urlbuilder
♻️ 重构部分代码
2019-07-18 08:22:04 -05:00
yadong.zhang d5189aa30c Merge branch 'dev' into remove-urlbuilder 2019-07-18 08:21:13 -05:00
yadong.zhang 32c540cf1e Merge pull request #22 from pengisgood/dev
add refresh token method
2019-07-18 08:13:10 -05:00
Hongwei Peng 9aa693d2da add refresh token method 2019-07-18 21:06:23 +08:00
Yangkai.Shen 0d4e52d615 ♻️ 移除 UrlBuilder 2019-07-18 20:37:41 +08:00
Yangkai.Shen 72792c3c83 🚨 新增编辑器规范,规范代码风格 2019-07-18 20:37:12 +08:00
yadong.zhang a6f25ec312 更新 example.md 2019-07-18 19:27:08 +08:00
yadong.zhang 6dc42ac8b3 📝 编写文档 2019-07-18 19:25:23 +08:00
yadong.zhang b67483c263 Merge pull request #21 from pengisgood/dev
Integrate Stack Overflow
2019-07-18 06:03:07 -05:00
Hongwei Peng 66ee684e1f refactor: extract common method 2019-07-18 18:23:45 +08:00
Hongwei Peng d0375ce31a update document 2019-07-18 18:13:34 +08:00
Hongwei Peng 698c922b6b integrate stack overflow 2019-07-18 18:08:05 +08:00
Yangkai.Shen 5694d48288 🎨 增加部分工具类及方法、重载AuthState部分方法 2019-07-18 16:53:54 +08:00
yadong.zhang 44d4323fb2 更新 example.md 2019-07-18 15:51:42 +08:00
yadong.zhang f7561e97fc 更新 example.md 2019-07-18 15:50:54 +08:00
yadong.zhang e8cadef4ce 📝 编写文档 2019-07-18 15:29:16 +08:00
yadong.zhang 49f169abb9 📝 编写文档 2019-07-18 15:26:07 +08:00
yadong.zhang 917239a89a 📝 编写文档 2019-07-18 15:22:52 +08:00
yadong.zhang 5a5827a705 Merge branch 'dev' of https://github.com/zhangyd-c/JustAuth into dev 2019-07-18 15:14:38 +08:00
yadong.zhang 27583b15cc Merge pull request #20 from pengisgood/dev
Integrate Pinterest
2019-07-18 15:12:50 +08:00
Hongwei Peng 090d7d3662 integrate Pinterest 2019-07-18 14:58:27 +08:00
Hongwei Peng b5d8dbaede update contributor 2019-07-18 12:50:57 +08:00
yadong.zhang a5da3b9765 🔀 合并github pr#19 2019-07-18 09:27:59 +08:00
yadong.zhang ae1b5815d8 Merge branch 'dev' of https://github.com/zhangyd-c/JustAuth into dev 2019-07-18 09:20:26 +08:00
yadong.zhang 350f9d70ae Merge pull request #19 from pengisgood/dev
Integrate Renren
2019-07-18 09:19:59 +08:00
Hongwei Peng 0ed3092d53 fix spelling 2019-07-17 21:37:36 +08:00
Hongwei Peng 8d1f700e7f integrate Renren 2019-07-17 21:36:16 +08:00
Hongwei Peng d977e05a3d fix bug and enhance 2019-07-17 21:36:16 +08:00
yadong.zhang abcdc105e0 📝 编写文档 2019-07-17 16:51:30 +08:00
yadong.zhang 267200c434 集成teambition 2019-07-17 15:29:18 +08:00
yadong.zhang e0242f9929 集成teambition 2019-07-17 15:25:48 +08:00
yadong.zhang 59b5ba5a1b 🍻 醉酒写代码 2019-07-17 06:45:21 +08:00
yadong.zhang 12c6930182 🎨 调整throw信息的格式 2019-07-16 22:12:04 +08:00
yadong.zhang a518e8c0cb Merge pull request #18 from xkcoding/patch-5
♻️ 修复小米回调错误问题 同时 支持微信获取 unionId
2019-07-16 19:51:26 +08:00
yadong.zhang 4c1fdfc62d 🔀 手动合并PR #18 到dev分支 2019-07-16 19:49:18 +08:00
yadong.zhang dc74501b1b 🔖 升级到1.9.0版本 2019-07-16 19:40:20 +08:00
yadong.zhang 8a4861075e ♻️ rename 2019-07-16 17:59:36 +08:00
yadong.zhang 65334d0f3f ♻️ 优化代码 2019-07-16 17:42:52 +08:00
yadong.zhang 67c668b740 ♻️ authorize方法提到父类中 2019-07-16 16:46:31 +08:00
yadong.zhang 7fe8a4d4bb ♻️ 从UrlBuilder中拆分alipay、baidu、coding、csdn、dingtalk、douyin、facebook和gitee相关的URL 2019-07-16 16:42:37 +08:00
yadong.zhang 4f272348da ♻️ 从UrlBuilder中拆分github、google、linkedin和microsoft相关的URL 2019-07-16 16:12:56 +08:00
yadong.zhang ec31b7cbf7 ♻️ 从UrlBuilder中拆分小米和OSChina相关的URL 2019-07-16 15:50:44 +08:00
yadong.zhang 6b196456a1 ♻️ 从UrlBuilder中拆分qq、淘宝、腾讯云和头条相关的URL 2019-07-16 15:37:44 +08:00
yadong.zhang af723a8b08 ♻️ 从UrlBuilder中拆分微信相关的URL 2019-07-16 15:11:18 +08:00
yadong.zhang 1360db60ae 💡 添加源码注释 2019-07-16 14:59:51 +08:00
Yangkai.Shen 8d1b329c63 微信登录获取unionId 2019-07-16 10:30:29 +08:00
yadong.zhang 47971438c7 拆分UrlBuilder工具类,已拆分WeiboRequest 2019-07-16 10:04:00 +08:00
Yangkai.Shen e2e1a082d1 🐛 修复小米回调错误的问题 2019-07-16 10:01:17 +08:00
yadong.zhang 3b80e11824 💡 添加源码注释 2019-07-15 19:17:22 +08:00
yadong.zhang 44bb03d63f 新增AuthState类,内置默认的state生成规则和校验规则 2019-07-15 17:46:37 +08:00
yadong.zhang bfe2122962 🎨 将枚举类移到enums包下 2019-07-15 10:40:42 +08:00
yadong.zhang fa2b9114d1 📝 编写文档 2019-07-15 10:25:41 +08:00
yadong.zhang 93bee4f5d9 🔀 合并Braavos96提交的github PR #16 2019-07-12 09:47:03 +08:00
yadong.zhang 5c0b8eb94a Merge pull request #16 from Diffblue-benchmarks/add-diffblue-tests
Add unit tests for me.zhyd.oauth.utils.GlobalAuthUtil
2019-07-12 09:29:25 +08:00
Eric Hettiaratchi e03088a9b4 Add unit tests for me.zhyd.oauth.utils.GlobalAuthUtil
These tests were written using Diffblue Cover.
2019-07-10 14:29:21 +01:00
yadong.zhang 41a52767b3 📝 编写文档 2019-06-29 15:52:50 +08:00
yadong.zhang 88048b7637 📝 编写文档 2019-06-29 15:12:49 +08:00
yadong.zhang 186ee58b72 修改测试代码 2019-06-29 07:36:08 +08:00
yadong.zhang fa51358072 📌 升级hutool版本 2019-06-28 23:08:09 +08:00
yadong.zhang 80329c2496 全面开启state校验 2019-06-28 22:58:34 +08:00
yadong.zhang 78988555b0 🍻 完善百度登录,增加gitee登录的state校验 2019-06-28 21:33:49 +08:00
yadong.zhang ac4ede74bf 👽 修改login方法的参数为AuthCallback,封装回调返回的参数、支持state参数、增加code和state参数校验 2019-06-27 19:39:21 +08:00
yadong.zhang 9941ce7e2a Merge branch 'master' of https://gitee.com/yadong.zhang/JustAuth 2019-06-26 21:53:28 +08:00
yadong.zhang 4f594a4178 📝 更新githubapi文档 2019-06-26 21:52:16 +08:00
yadong.zhang b9268f296b Merge remote-tracking branch 'origin/master' 2019-06-25 19:33:27 +08:00
yadong.zhang 1c30f6ab2f 🎨 适配qq授权登录时开发者账号没有申请unionId权限而导致报错的问题 2019-06-25 19:32:18 +08:00
yadong.zhang af7baa924c 📝 Writing docs. 2019-06-22 08:10:09 +08:00
yadong.zhang 9902e7eb0d !4 优化微博登录
Merge pull request !4 from skqing/master
2019-06-22 07:43:45 +08:00
yadong.zhang 739fa786ce Merge pull request #15 from xkcoding/patch-4
调整部分代码
2019-06-22 07:34:23 +08:00
Yangkai.Shen 6f1cead802 ♻️ 枚举类使用==替换equals,提高性能
参考:https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/comparing-java-enum-members-or-equals.md
2019-06-21 15:37:42 +08:00
Yangkai.Shen 374b71e5fe ♻️ 去除工具类方法,使用原生方法 2019-06-21 15:36:41 +08:00
Yangkai.Shen 9d1ab36e21 ♻️ 使用 StandardCharsets.UTF_8 替换 字符串UTF-8
1. 字符串形式的 utf-8 会抛异常
2. Charset 的性能要好
2019-06-21 15:34:38 +08:00
Yangkai.Shen b8d9f2ebc9 🐛 修复小米 scope 错误 2019-06-21 15:32:30 +08:00
skqing c201a9ac90 优化微博登录:
1.remind_in:该参数即将废弃,开发者请使用expires_in
2.uid作为openid,否则openid为空,理论上uid也是可以作为openid的
2019-06-21 11:29:20 +08:00
yadong.zhang 12ec6226bc 📝 编写文档 2019-06-20 10:28:29 +08:00
yadong.zhang 9a62332f30 📝 编写文档 2019-06-20 10:25:22 +08:00
yadong.zhang a962c96a2c Merge pull request #14 from xkcoding/refactor-v1.6.0-beta
🏗️ 架构调整,简化代码
2019-06-20 10:24:07 +08:00
yadong.zhang d442018c02 Merge branch 'master' into refactor-v1.6.0-beta 2019-06-20 10:23:41 +08:00
yadong.zhang 8d6d4533e4 🔀 合并Gitee上的pr 2019-06-20 10:19:20 +08:00
yadong.zhang 2f88c23405 🔀 合并github中的pr 2019-06-20 10:10:18 +08:00
yadong.zhang cf74f811fa 📝 编写文档 2019-06-19 20:01:03 +08:00
Yangkai.Shen bb613010dc 🏗️ 架构调整,简化代码 2019-06-19 18:16:13 +08:00
skqing 7ace410351 解决抖音登录问题:
1.抖音的client_id换成client_key了
2.抖音的地址后面必须加上/否则报错
2019-06-19 17:14:14 +08:00
yadong.zhang 65daa0592a 🚑 增加alipay授权参数的验证,修改部分命名 2019-06-19 16:48:09 +08:00
yadong.zhang 25424023c4 🔀 合并代码 2019-06-19 10:10:20 +08:00
yadong.zhang 67579bfb07 🍻 qq登录时根据openid和unionid选择合适的值作为uuid 2019-06-19 10:06:15 +08:00
yadong.zhang 458de3840d Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java
#	src/main/java/me/zhyd/oauth/request/AuthQqRequest.java
2019-06-19 10:04:47 +08:00
yadong.zhang 1c1d2dc9db !2 修复最近提出一些问题和BUG
Merge pull request !2 from skqing/master
2019-06-19 10:01:59 +08:00
yadong.zhang f5de7f93b5 🍻 解决Issue #IY1QR 增加对Config属性的校验功能,主要校验redirect uri的合法性 2019-06-19 09:56:28 +08:00
skqing dcf5f30e61 修复以下问题:
QQ登录未获取真正的uuid
部分Request封装AuthUser时缺少gender属性
location属性设置方式不一致
钉钉登录获取用户信息忽略了openid
钉钉登录回调报错 bug
2019-06-19 09:51:43 +08:00
yadong.zhang e534a4b62e 🍻 解决Issue #IY2FV 2019-06-18 19:56:00 +08:00
yadong.zhang 42ede32fc5 🍻 醉酒写代码 2019-06-18 19:51:53 +08:00
yadong.zhang c0dd700b0a 🎨 解决Issue #IY2OH 2019-06-18 19:27:11 +08:00
yadong.zhang f32c341b63 🎨 解决Issue #IY2HW 2019-06-18 19:21:05 +08:00
yadong.zhang 82358cbddb !1 修复钉钉回调JSON解析用户信息异常问题
Merge pull request !1 from skqing/master
2019-06-18 19:04:18 +08:00
skqing 56df9bc1b0 针对钉钉登录增加AuthToken属性 2019-06-18 14:55:28 +08:00
skqing 438660621e 解决钉钉回调异常问题 https://gitee.com/yadong.zhang/JustAuth/issues/IY1Z3 2019-06-18 14:20:05 +08:00
yadong.zhang 937fba37f5 📝 编写文档 2019-06-06 19:35:58 +08:00
yadong.zhang fde490a205 🔖 增加今日头条的授权登陆。注:该版为beta版,因头条开发者申请的问题,并未经过测试。待测试通过后正式发布release版 2019-06-06 19:23:01 +08:00
yadong.zhang da23fcd54c 🔖 增加今日头条的授权登陆。注:该版为beta版,因头条开发者申请的问题,并未经过测试。待测试通过后正式发布release版 2019-06-06 19:22:20 +08:00
yadong.zhang 36b5f333a4 🔖 发布1.5.1版本。注意:1.5.0版本因特殊原因已不可用! 2019-05-29 18:41:35 +08:00
yadong.zhang d5e0ff3f33 💡 添加源码注释 2019-05-29 18:11:59 +08:00
yadong.zhang b3aee449a2 📝 编写文档 2019-05-28 23:02:19 +08:00
yadong.zhang f3619f48fe 🔖 增加小米账号和微软的授权登陆。发布1.5.0版本 2019-05-28 22:57:20 +08:00
yadong.zhang e92f8961f5 Merge pull request #13 from xkcoding/feature-mi
 增加小米登录支持
2019-05-28 22:40:56 +08:00
Yangkai.Shen a6ed65ce3d 增加小米登录支持 2019-05-28 19:26:10 +08:00
yadong.zhang 132c682673 Merge pull request #12 from xkcoding/feature-microsoft
 增加微软登录支持
2019-05-28 18:21:28 +08:00
Yangkai.Shen 4d92794b1a 增加微软登录支持 2019-05-28 15:58:41 +08:00
yadong.zhang ae9edffc70 更新 README.md 2019-05-27 15:22:39 +08:00
yadong.zhang 7a6ff6c8e5 🔖 发布一个版本 2019-05-27 15:21:07 +08:00
yadong.zhang b9ca048d1c ✏️ 修复部分问题,详情见update.md 2019-05-27 15:15:51 +08:00
yadong.zhang a07dcbd26f ✏️ 修改钉钉图片的名字 2019-05-27 14:32:50 +08:00
yadong.zhang 3f67757a3a 支持抖音和领英,其他更新内容参考update.md 2019-05-27 11:43:56 +08:00
yadong.zhang c951ffad59 📝 编写文档 2019-05-24 14:22:21 +08:00
yadong.zhang 0c672dee2e 增加UUID属性用于确定唯一用户。其他更新内容请参考update.md 2019-05-24 13:32:46 +08:00
yadong.zhang c8617b4d19 Merge pull request #11 from xkcoding/feature-userid
 添加用户在Google中的唯一主键
2019-05-24 09:44:51 +08:00
Yangkai.Shen a5386666bb 添加用户在Google中的唯一主键 2019-05-24 09:41:39 +08:00
yadong.zhang 90f8d01c56 🍻 增加用户的UUID 2019-05-23 19:29:33 +08:00
yadong.zhang 0237cb383b Merge branch 'master' of https://github.com/zhangyd-c/JustAuth 2019-05-23 19:12:36 +08:00
yadong.zhang e70dae0b85 Merge pull request #10 from xkcoding/fix-response-code
♻️ 添加返回状态码
2019-05-23 19:12:17 +08:00
yadong.zhang 41cede22ad 🍻 醉酒写代码 2019-05-23 19:08:00 +08:00
yadong.zhang a9b3d61d4d 👽 增加uuid 2019-05-23 19:01:32 +08:00
Yangkai.Shen 18d650e59a ♻️ 添加返回状态码 2019-05-23 18:59:42 +08:00
yadong.zhang 96ada34347 Merge branch 'master' of https://github.com/zhangyd-c/JustAuth 2019-05-23 18:55:18 +08:00
yadong.zhang 356a1bf826 👽 增加uuid 2019-05-23 18:55:14 +08:00
yadong.zhang 9e27ef2f8d Merge pull request #9 from xkcoding/feature-userid
 添加用户在第三方系统中的唯一主键
2019-05-23 18:54:19 +08:00
Yangkai.Shen 334574dfe8 添加用户在第三方系统中的唯一主键 2019-05-23 18:51:22 +08:00
yadong.zhang d978ca9b19 📝 发布1.3.2版本 2019-05-23 17:56:52 +08:00
yadong.zhang 56bca92c59 📝 新版jar还未发布成功,先还原回上一版 2019-05-23 11:49:01 +08:00
yadong.zhang fd9cb530cd Merge pull request #8 from xkcoding/fix-qq-1
🐛 修复QQ登录,新增location
2019-05-23 11:46:44 +08:00
yadong.zhang dfd5c3b07d 修改文档中maven依赖的版本 2019-05-23 11:44:35 +08:00
Yangkai.Shen a2cee9879b 🐛 修复QQ登录,新增location 2019-05-23 11:25:21 +08:00
yadong.zhang 663efb35fe 修复qq登录的bug 2019-05-23 11:20:12 +08:00
yadong.zhang 94045ea81d Merge pull request #7 from xkcoding/fix-qq
🐛 修复QQ登录,获取用户信息需要携带 clientId
2019-05-23 11:18:18 +08:00
Yangkai.Shen 5ec49c6c77 🐛 修复QQ登录,获取用户信息需要携带 clientId 2019-05-23 11:13:32 +08:00
yadong.zhang 5e95cf1384 更新 README.md 2019-05-23 10:50:40 +08:00
yadong.zhang fd90a63f40 📝 修改文档中的jar版本 2019-05-23 09:10:25 +08:00
yadong.zhang 4a271c583e 💡 添加注释 2019-05-23 09:06:13 +08:00
yadong.zhang 14fab8cd77 🐛 修复QQ登录的问题 2019-05-23 08:55:05 +08:00
yadong.zhang 3c0c809dfc 🐛 修复QQ登录的问题 2019-05-23 08:53:55 +08:00
yadong.zhang 6f40460cce Merge pull request #6 from xkcoding/fix-qq
🐛 修复QQ登录,返回AccessToken以及OpenId数据格式解析错误问题
2019-05-22 23:06:52 +08:00
Yangkai.Shen e81e92130b 🐛 修复QQ登录,返回AccessToken以及OpenId数据格式解析错误问题 2019-05-22 19:05:13 +08:00
yadong.zhang 85afd754a9 📝 更新文档 2019-05-22 09:18:09 +08:00
yadong.zhang a8709a30d3 📝 编写文档 2019-05-21 23:40:37 +08:00
yadong.zhang ffa48970e5 更新 README.md 2019-05-21 23:38:35 +08:00
yadong.zhang 5d753de282 更新 README.md 2019-05-21 23:37:26 +08:00
yadong.zhang a739ca374e 增加facebook授权登录 2019-05-21 23:33:17 +08:00
yadong.zhang 880a7d4157 更新 README.md 2019-05-21 19:07:28 +08:00
yadong.zhang 6fe1aa62c6 📝 更新文档 2019-05-21 19:00:02 +08:00
yadong.zhang 7857db7824 增加Google授权 2019-05-21 18:45:29 +08:00
yadong.zhang 7ed248689f Merge pull request #5 from xkcoding/feature-google
 去除重复代码、新增支持Google登录
2019-05-21 18:13:31 +08:00
Yangkai.Shen d24733e016 新增支持Google登录 2019-05-21 15:09:16 +08:00
Yangkai.Shen 2dba2baef7 🎨 去除重复代码 2019-05-21 15:08:45 +08:00
yadong.zhang 2abca4f10f Merge pull request #4 from xkcoding/patch-3
gitignore文件更新,AuthConfig 添加 set 方法支持作为外部配置类
2019-05-20 14:36:43 +08:00
Yangkai.Shen 7db4dcce2a 🎨 添加 Setter 方法,方便作为配置类参数 2019-05-20 09:46:16 +08:00
Yangkai.Shen 14c2b4cacf 🙈 更新 .gitignore 文件 排除 target 文件夹 2019-05-20 09:45:09 +08:00
yadong.zhang 54e97b0ec4 ♻️ 优化获取authorize的代码 2019-05-18 18:26:38 +08:00
yadong.zhang aa187dd109 更新 README.md 2019-05-18 15:46:55 +08:00
yadong.zhang 3cf8beab83 📝 编写文档 2019-05-18 15:39:00 +08:00
yadong.zhang 3824bc0887 📝 编写文档 2019-05-18 15:32:58 +08:00
yadong.zhang b8c3caea73 支持淘宝登录、修改AuthUser.java类中的accessToken属性,由原本的accessToken (String)改为token (AuthToken) 2019-05-18 15:31:17 +08:00
yadong.zhang 4fb81436e2 📝 修改文档,更新依赖版本 2019-05-18 13:17:35 +08:00
yadong.zhang a52d34ed75 💡 修改注释,发布jar包 2019-05-18 12:58:54 +08:00
yadong.zhang 83856c8350 🔖 因新增微信和qq登录,版本迭代到1.1.0 2019-05-18 12:17:54 +08:00
yadong.zhang 79574a66e5 👽 注释、文档 2019-05-18 11:26:59 +08:00
yadong.zhang 6186236a13 👽 合并wechat登录代码 2019-05-17 19:13:11 +08:00
yadong.zhang 8abe74db9d Merge branch 'master' of https://github.com/zhangyd-c/JustAuth 2019-05-17 18:48:20 +08:00
yadong.zhang 188c52ca6b 👌 修改获取accessToken时的返回值为实体类,方便扩展 2019-05-17 18:37:57 +08:00
yadong.zhang e04d2abe3a Merge pull request #3 from xkcoding/feature-wechat
 增加微信登录支持
2019-05-17 18:17:53 +08:00
Yangkai.Shen 144decd932 🐛 微信公众平台更换为微信开放平台 2019-05-17 18:11:47 +08:00
Yangkai.Shen 87c2a1b2cd 增加微信登录支持 2019-05-17 15:26:31 +08:00
yadong.zhang 2e0262ed7e Merge branch 'master' of https://github.com/zhangyd-c/JustAuth 2019-05-17 10:20:52 +08:00
yadong.zhang 8520d0b24c 💡 修改注释,增加QQ登录支持 2019-05-17 10:19:52 +08:00
yadong.zhang 40b48a9f2a Merge pull request #2 from xkcoding/patch-2
🙈 更新 .gitignore 文件
2019-05-17 09:54:36 +08:00
yadong.zhang d176b6089c Merge pull request #1 from xkcoding/patch-1
📝 更新 README.md 的 LICENSE 标记
2019-05-17 09:54:01 +08:00
Yangkai.Shen 9b1bf7bb47 🙈 更新 .gitignore 文件 2019-05-17 09:41:24 +08:00
Yangkai.Shen 0ed1814deb 📝 更新 README.md 的 LICENSE 标记 2019-05-17 09:32:49 +08:00
yadong.zhang 755b72de99 更新 pom.xml 发布到中央仓库 2019-03-29 15:34:17 +08:00
yadong.zhang 0e24f06022 💡 修改注释,提到中央仓库中 2019-03-29 15:27:27 +08:00
yadong.zhang eb200c7fe3 更新 README.md 2019-03-27 18:37:24 +08:00
yadong.zhang a7933ab016 Merge remote-tracking branch 'origin/master' 2019-03-27 18:35:31 +08:00
yadong.zhang 75176571b7 集成支付宝授权登录 2019-03-27 18:34:53 +08:00
yadong.zhang bf78776c7b 开源协议修改为MIT 2019-03-26 15:50:02 +08:00
yadong.zhang f916454821 更新 LICENSE 2019-03-26 15:49:10 +08:00
63 changed files with 4855 additions and 2402 deletions
+19
View File
@@ -0,0 +1,19 @@
# JustAuth 开发组IDE 编辑器标准
root = true
# 空格替代Tab缩进在各种编辑工具下效果一致
[*]
indent_style = space
indent_size = 2
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.java]
indent_size = 4
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
+7
View File
@@ -21,3 +21,10 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# exclude idea files
.idea
*.iml
*.sh
target
+21 -674
View File
@@ -1,674 +1,21 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
MIT License
Copyright (c) 2019 https://gitee.com/yadong.zhang/JustAuth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+105 -93
View File
@@ -5,49 +5,52 @@
<strong>Login, so easy.</strong>
</p>
<p align="center">
<a target="_blank" href="https://search.maven.org/search?q=g:%22me.zhyd%22%20AND%20a:%22JustAuth%22">
<img src="https://img.shields.io/badge/Maven Central-1.0.0-blue.svg" ></img>
<a target="_blank" href="https://search.maven.org/search?q=JustAuth">
<img src="https://img.shields.io/badge/Maven Central-1.9.2-blue.svg" ></img>
</a>
<a target="_blank" href="https://gitee.com/yadong.zhang/JustAuth/blob/master/LICENSE">
<img src="https://img.shields.io/badge/License-GPL%20v3-yellow.svg" ></img>
<img src="https://img.shields.io/apm/l/vim-mode.svg?color=yellow" ></img>
</a>
<a target="_blank" href="https://www.oracle.com/technetwork/java/javase/downloads/index.html">
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" ></img>
</a>
<a target="_blank" href="https://apidoc.gitee.com/yadong.zhang/JustAuth/">
<img src="https://img.shields.io/badge/Docs-1.9.2-orange.svg" ></img>
</a>
</p>
<center>
<table>
<thead>
<tr>
<td align="center" width="200"><a href="https://gitee.com/"><img src="https://gitee.com/logo_icon.png" width="30"></a></td>
<td align="center" width="200"><a href="https://github.com"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/github.png" width="30"></a></td>
<td align="center" width="200"><a href="https://weibo.com"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/weibo.png" width="30"></a></td>
<td align="center" width="200"><a href="https://www.dingtalk.com"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/dingding.png" width="30"></a></td>
<td align="center" width="200"><a href="https://developer.baidu.com/"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/baidu.png" width="30"></a></td>
<td align="center" width="200"><a href="https://www.csdn.net/"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/csdn.png" width="30"></a></td>
<td align="center" width="200"><a href="https://coding.net"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/coding.png" width="30"></a></td>
<td align="center" width="200"><a href="https://dev.tencent.com/"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/tencent_cloud.png" width="30"></a></td>
<td align="center" width="200"><a href="https://www.oschina.net"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/oschinas.png" width="30"></a></td>
<td align="center" width="200"><a href="https://connect.qq.com/devuser.html#/"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/qq.png" width="30"></a></td>
<td align="center" width="200"><a href="https://mp.weixin.qq.com/cgi-bin/loginpage?t=wxm2-login&lang=zh_CN"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/wechats.png" width="30"></a></td>
</tr>
</thead>
<tbody>
<tr>
<td align="center" width="200"><a href="#授权gitee">Gitee</a></td>
<td align="center" width="200"><a href="#授权github">Github</a></td>
<td align="center" width="200"><a href="#授权weibo">Weibo</a></td>
<td align="center" width="200"><a href="#授权钉钉">钉钉</a></td>
<td align="center" width="200"><a href="#授权百度">百度</a></td>
<td align="center" width="200"><a href="#授权csdn">CSDN</a></td>
<td align="center" width="200"><a href="#授权coding">Coding</a></td>
<td align="center" width="200"><a href="#授权腾讯云开发者平台" title="coding升级后就变成腾讯云开发者平台了">腾讯云开发者平台</a></td>
<td align="center" width="200"><a href="#授权oschina">OSChina</a></td>
<td align="center" width="200"><a href="#授权qq">QQ</a></td>
<td align="center" width="200"><a href="#授权微信">微信</a></td>
</tr>
</tbody>
<tr>
<td align="center" width="200"><a href="#授权gitee"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/gitee.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权github"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/github.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权weibo"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/weibo.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权钉钉"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/dingtalk.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权百度"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/baidu.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权coding"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/coding.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权腾讯云开发者平台"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/tencentCloud.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权oschina"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/oschina.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权支付宝"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/alipay.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权qq"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/qq.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权微信"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/wechat.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权淘宝"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/taobao.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权google"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/google.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权facebook"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/facebook.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权抖音"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/douyin.png" width="20"></a></td>
</tr>
</table>
<table>
<tr>
<td align="center" width="200"><a href="#授权领英"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/linkedin.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权微软"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/microsoft.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权小米"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/mi.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权今日头条"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/toutiao.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权Teambition"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/teambition.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权人人"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/renren.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权Pinterest"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/pinterest.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权Stack Overflow"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/stackoverflow.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权csdn"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/csdn.png" width="20"></a></td>
</tr>
</table>
</center>
@@ -57,13 +60,23 @@
JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具类库**,它可以让我们脱离繁琐的第三方登录SDK,让登录变得**So easy!**
项目开源地址:[gitee](https://gitee.com/yadong.zhang/JustAuth) | [github](https://github.com/zhangyd-c/JustAuth)
## 特点
废话不多说,就俩字:
1. **全**:已集成十多家第三方平台(国内外常用的基本都已包含),后续依然还有扩展计划!
2. **简**API就是奔着最简单去设计的(见后面`快速开始`),尽量让您用起来没有障碍感!
## 快速开始
- 引入依赖
```xml
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.0.0</version>
<version>1.9.2</version>
</dependency>
```
- 调用api
@@ -73,29 +86,55 @@ AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 生成授权页面
authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的参数
authRequest.login(callback);
```
具体的例子可以参考:[实现Gitee授权登录](http://t.cn/ExDKxQs)
注:`1.8.0`版本后,增加了`state`参数校验,用于防止[CSRF](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)。强烈建议,保证单次流程内`state`的唯一性,且每个`state`只可用一次。
**配套Demo**
- [Springboot版](https://gitee.com/yadong.zhang/JustAuth-demo)
- [jFinal版](https://github.com/zhangyd-c/jfinal-justauth-demo)
具体的例子可以参考:
- [实现Gitee授权登录](http://t.cn/ExDKxQs)
- [实现Github授权登录](http://t.cn/EJ0Fxqo)
- [Spring Boot 快速集成第三方登录功能](http://t.cn/AiWWx5kH)
#### API列表
| :computer: 平台 | :coffee: API类 | :page_facing_up: SDK |
|:------:|:-------:|:-------:|
| <img src="https://gitee.com/logo_icon.png" width="20"> | [AuthGiteeRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java) | <a href="https://gitee.com/api/v5/oauth_doc#list_1" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/github.png" width="20"> | [AuthGithubRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java) | <a href="https://github.com/settings/developers" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/weibo.png" width="20"> | [AuthWeiboRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java) | <a href="https://open.weibo.com/apps?_blank" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/dingding.png" width="20"> | [AuthDingTalkRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java) | <a href="https://open-doc.dingtalk.com/microapp/serverapi2/kymkv6" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/baidu.png" width="20"> | [AuthBaiduRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java) | <a href="https://developer.baidu.com/" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/csdn.png" width="20"> | [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 待续 |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/gitee.png" width="20"> | [AuthGiteeRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java) | <a href="https://gitee.com/api/v5/oauth_doc#list_1" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/github.png" width="20"> | [AuthGithubRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java) | <a href="https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/weibo.png" width="20"> | [AuthWeiboRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java) | <a href="https://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/dingtalk.png" width="20"> | [AuthDingTalkRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java) | <a href="https://open-doc.dingtalk.com/microapp/serverapi2/kymkv6" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/baidu.png" width="20"> | [AuthBaiduRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java) | <a href="http://developer.baidu.com/wiki/index.php?title=docs/oauth" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/coding.png" width="25"> | [AuthCodingRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java) | <a href="https://open.coding.net/references/oauth/" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/tencent_cloud.png" width="25"> | [AuthTencentCloudRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java) | <a href="https://dev.tencent.com/help/doc/faq/b4e5b7aee786/oauth" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/oschinas.png" width="20"> | [AuthOschinaRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java) | <a href="https://www.oschina.net/openapi/docs/openapi_user" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/qq.png" width="20"> | AuthQqRequest | <a href="https://connect.qq.com/" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/wechats.png" width="20"> | AuthWechatRequest | 待续 |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/tencentCloud.png" width="25"> | [AuthTencentCloudRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java) | <a href="https://dev.tencent.com/help/doc/faq/b4e5b7aee786/oauth" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/oschina.png" width="20"> | [AuthOschinaRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java) | <a href="https://www.oschina.net/openapi/docs/oauth2_authorize" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/alipay.png" width="20"> | [AuthAlipayRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java) | <a href="https://alipay.open.taobao.com/docs/doc.htm?spm=a219a.7629140.0.0.336d4b70GUKXOl&treeId=193&articleId=105809&docType=1" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/qq.png" width="20"> | [AuthQqRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java) | <a href="https://wiki.connect.qq.com/%E4%BD%BF%E7%94%A8authorization_code%E8%8E%B7%E5%8F%96access_token" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/wechat.png" width="20"> | [AuthWeChatRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java) | <a href="https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/taobao.png" width="20"> | [AuthTaobaoRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java) | <a href="https://open.taobao.com/doc.htm?spm=a219a.7386797.0.0.4e00669acnkQy6&source=search&docId=105590&docType=1" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/google.png" width="20"> | [AuthGoogleRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java) | <a href="https://developers.google.com/identity/protocols/OpenIDConnect" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/facebook.png" width="20"> | [AuthFacebookRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java) | <a href="https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/douyin.png" width="20"> | [AuthDouyinRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java) | <a href="https://www.douyin.com/platform/doc/m-2-1-1" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/linkedin.png" width="20"> | [AuthLinkedinRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java) | <a href="https://docs.microsoft.com/zh-cn/linkedin/shared/authentication/authorization-code-flow?context=linkedin/context" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/microsoft.png" width="20"> | [AuthMicrosoftRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java) | <a href="https://docs.microsoft.com/zh-cn/graph/auth/" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/mi.png" width="20"> | [AuthMiRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java) | <a href="https://dev.mi.com/console/doc/detail?pId=711" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/toutiao.png" width="20"> | [AuthToutiaoRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java) | <a href="https://open.mp.toutiao.com/#/resource?_k=y7mfgk" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/teambition.png" width="20"> | [AuthTeambitionRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java) | <a href="https://docs.teambition.com/" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/renren.png" width="20"> | [AuthRenrenRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java) | <a href="http://open.renren.com/wiki/OAuth2.0" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/pinterest.png" width="20"> | [AuthPinterestRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java) | <a href="https://developers.pinterest.com/docs/api/overview/?" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/stackoverflow.png" width="20"> | [AuthStackOverflowRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java) | <a href="https://api.stackexchange.com/docs/authentication" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/csdn.png" width="20"> | [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 无 |
_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_
## 后续开发计划
@@ -103,12 +142,14 @@ authRequest.login("code");
另外,期待您和我一起完善这个项目!
## 贡献代码
## 本项目同步开源到Gitee和Github
[gitee](https://gitee.com/yadong.zhang/JustAuth)
[github](https://github.com/zhangyd-c/JustAuth)
1. fork本项目到自己的repo
2. 把fork过去的项目也就是你仓库中的项目clone到你的本地
3. 修改代码
4. commit后push到自己的库
5. 发起PRpull request 请求,提交到`dev`分支
6. 等待作者合并
## 致谢
@@ -116,56 +157,27 @@ authRequest.login("code");
[YurunOAuthLogin](https://gitee.com/yurunsoft/YurunOAuthLogin): PHP 第三方登录授权 SDK
[阿里妈妈MUX倾力打造的矢量图标库-iconfont](https://www.iconfont.cn/search/index): 本文档中的图标大部分取自该平台
## 参考图例
## 关于OAuth
#### 授权gitee
[The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749)
![Gitee授权登录](https://images.gitee.com/uploads/images/2019/0221/140015_4c09610e_784199.png "Gitee授权登录")
## 关注&交流
#### 授权github
| 公众号 | 微信(备注:加群) |
| :------------: | :------------: |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/wx/wechat_account.jpg" width="200" /> | <img src="https://gitee.com/yadong.zhang/static/raw/master/wx/wx.png" width="170"/> |
![Github授权登录](https://images.gitee.com/uploads/images/2019/0221/140032_58f7dfb5_784199.png "Github授权登录")
**QQ群**
#### 授权weibo
- JustAuth交流群 230017570):专业交流该项目
![微博授权登录](https://images.gitee.com/uploads/images/2019/0222/191210_67d5597c_784199.png "微博授权登录")
- 开源总群 (190886500):各个开源项目的都有,也有博客建设等方面的朋友。(注意,该群需付费进入,防止发垃圾广告、垃圾推广等人士)
#### 授权钉钉
![钉钉授权登录](https://images.gitee.com/uploads/images/2019/0221/140540_8da8d959_784199.jpeg "钉钉授权登录")
#### 授权百度
![百度授权登录](https://images.gitee.com/uploads/images/2019/0221/140607_ebf1dcb6_784199.png "百度授权登录")
#### 授权csdn
待续
#### 授权coding
![Coding授权登录](https://images.gitee.com/uploads/images/2019/0224/192106_fd53b3d7_784199.png "Coding授权登录")
#### 授权腾讯云开发者平台
![腾讯云开发者平台授权登录](https://images.gitee.com/uploads/images/2019/0224/192128_db9e203b_784199.png "腾讯云开发者平台授权登录")
#### 授权oschina
![授权oschina登录](https://images.gitee.com/uploads/images/2019/0322/230652_05b4fd8a_784199.png "授权oschina")
#### 授权qq
待续
#### 授权微信
待续
## 请喝咖啡
| 支付宝 | 微信 |
| :------------: | :------------: |
| 支付宝 | 微信 |
| :------------: | :------------: |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/qrcode/zfb_code.png" width="200"/> | <img src="https://gitee.com/yadong.zhang/static/raw/master/qrcode/wx_code.png" width="200" /> |
+7
View File
@@ -0,0 +1,7 @@
# 项目贡献者名单
- <img src="https://avatar.gitee.com/uploads/99/784199_yadong.zhang.png!avatar100?1462325358" width="20"> · yadong.zhang : <a href="https://github.com/zhangyd-c" target="_blank">[Github]</a> | <a href="https://gitee.com/yadong.zhang" target="_blank">[Gitee]</a> | <a href="https://www.zhyd.me" target="_blank">[个人网站]</a>
- <img src="https://avatars0.githubusercontent.com/u/10429917?s=460&v=4" width="20"> · yangkai.shen : <a href="https://github.com/xkcoding" target="_blank">[Github]</a> | <a href="https://xkcoding.com" target="_blank">[个人网站]</a>
- <img src="https://avatar.gitee.com/uploads/51/1651_dolphinboy.png!avatar100?1479346570" width="20"> · skqing : <a href="https://gitee.com/skqing" target="_blank">[Gitee]</a> | <a href="https://my.oschina.net/dolphinboy" target="_blank">[个人网站]</a>
- <img src="https://avatars2.githubusercontent.com/u/2988765?s=115&v=4" width="20"> · pengisgood : <a href="https://github.com/pengisgood" target="_blank">[Github]</a> | <a href="https://pengisgood.github.io" target="_blank">[个人网站]</a>
- 千年等一回,我只为等你...
+107
View File
@@ -0,0 +1,107 @@
## 各平台授权页面示例
_注:非全部平台,部分平台可能不存在图例_
#### 授权gitee
![Gitee授权登录](https://images.gitee.com/uploads/images/2019/0221/140015_4c09610e_784199.png "Gitee授权登录")
#### 授权github
![Github授权登录](https://images.gitee.com/uploads/images/2019/0221/140032_58f7dfb5_784199.png "Github授权登录")
#### 授权weibo
![微博授权登录](https://images.gitee.com/uploads/images/2019/0222/191210_67d5597c_784199.png "微博授权登录")
#### 授权钉钉
![钉钉授权登录](https://images.gitee.com/uploads/images/2019/0221/140540_8da8d959_784199.jpeg "钉钉授权登录")
#### 授权百度
![百度授权登录](https://images.gitee.com/uploads/images/2019/0221/140607_ebf1dcb6_784199.png "百度授权登录")
#### 授权coding
![Coding授权登录](https://images.gitee.com/uploads/images/2019/0224/192106_fd53b3d7_784199.png "Coding授权登录")
#### 授权腾讯云开发者平台
![腾讯云开发者平台授权登录](https://images.gitee.com/uploads/images/2019/0224/192128_db9e203b_784199.png "腾讯云开发者平台授权登录")
#### 授权oschina
![授权oschina登录](https://images.gitee.com/uploads/images/2019/0322/230652_05b4fd8a_784199.png "授权oschina")
#### 授权支付宝
![授权支付宝登录](https://images.gitee.com/uploads/images/2019/0327/183654_3d4b94eb_784199.png "授权支付宝登录")
#### 授权qq
暂无
#### 授权微信
![授权微信登录](https://images.gitee.com/uploads/images/2019/0523/104955_d4cea750_784199.png "授权微信登录")
#### 授权淘宝
![授权淘宝登录](https://images.gitee.com/uploads/images/2019/0518/154604_68b38305_784199.png "授权淘宝登录")
#### 授权Google
![授权google登录](https://images.gitee.com/uploads/images/2019/0521/190650_85c5f1c7_784199.png "授权google登录")
#### 授权Facebook
![授权facebook登录](https://images.gitee.com/uploads/images/2019/0521/233647_6a89fb45_784199.png "授权facebook登录")
#### 授权抖音
暂无
#### 授权领英
![授权领英登录](https://images.gitee.com/uploads/images/2019/0527/152207_a6342979_784199.png "授权领英登录")
#### 授权微软
![授权微软](https://images.gitee.com/uploads/images/2019/0718/224146_681aa535_784199.png "授权微软")
#### 授权小米
暂无
#### 授权今日头条
暂无
#### 授权Teambition
![授权Teambition](https://images.gitee.com/uploads/images/2019/0718/224119_3da514ab_784199.png "授权Teambition")
#### 授权Pinterest
![授权Pinterest](https://images.gitee.com/uploads/images/2019/0718/155012_6290f500_784199.jpeg "授权Pinterest")
#### 授权Renren
![授权Renre](https://images.gitee.com/uploads/images/2019/0718/155035_8e26c10a_784199.jpeg "授权Renren")
#### 授权Stack Overflow
![授权Stack Overflow](https://images.gitee.com/uploads/images/2019/0718/192639_cc301ba7_784199.png "授权Stack Overflow")
#### 授权Twitter
暂无
#### 授权csdn
暂无
_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_
+36 -20
View File
@@ -1,20 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.0.0</version>
<version>1.9.2</version>
<name>JustAuth</name>
<url>https://gitee.com/yadong.zhang/JustAuth</url>
<description>史上最全的整合第三方登录的工具, Just Auth</description>
<description>
史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。
Login, so easy!
</description>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<name>MIT</name>
<url>https://gitee.com/yadong.zhang/JustAuth/blob/master/LICENSE</url>
</license>
</licenses>
@@ -27,8 +30,19 @@
<developers>
<developer>
<name>yadong.zhang</name>
<name>Yadong.Zhang</name>
<email>yadong.zhang0415@gmail.com</email>
<url>https://www.zhyd.me</url>
</developer>
<developer>
<name>Yangkai.Shen</name>
<email>shenyangkai1994@gmail.com</email>
<url>https://xkcoding.com</url>
</developer>
<developer>
<name>Hongwei.Peng</name>
<email>pengisgood@gmail.com</email>
<url>https://github.com/pengisgood</url>
</developer>
</developers>
@@ -40,11 +54,12 @@
<maven-source.version>2.2.1</maven-source.version>
<maven-compiler.version>3.7.0</maven-compiler.version>
<maven.test.skip>true</maven.test.skip>
<hutool-version>4.1.21</hutool-version>
<hutool-version>4.5.15</hutool-version>
<lombok-version>1.18.4</lombok-version>
<junit-version>4.11</junit-version>
<fastjson-version>1.2.44</fastjson-version>
<google-api-version>1.28.0</google-api-version>
<fastjson-version>1.2.58</fastjson-version>
<alipay-sdk-version>3.7.4.ALL</alipay-sdk-version>
<slf4j-version>1.7.25</slf4j-version>
</properties>
<dependencies>
@@ -69,11 +84,17 @@
<artifactId>fastjson</artifactId>
<version>${fastjson-version}</version>
</dependency>
<!--<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>${google-api-version}</version>
</dependency>-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay-sdk-version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j-version}</version>
</dependency>
</dependencies>
<build>
@@ -102,12 +123,10 @@
</execution>
</executions>
</plugin>
<!-- Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<!-- GPG -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
@@ -119,7 +138,6 @@
<id>release</id>
<build>
<plugins>
<!-- Source -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
@@ -134,7 +152,6 @@
</execution>
</executions>
</plugin>
<!-- Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
@@ -147,7 +164,6 @@
</execution>
</executions>
</plugin>
<!-- GPG -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
@@ -164,11 +180,11 @@
</build>
<distributionManagement>
<snapshotRepository>
<id>sonatype-oss</id>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>sonatype-oss</id>
<id>sonatype-nexus-staging</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
@@ -1,22 +1,61 @@
package me.zhyd.oauth.config;
import lombok.Builder;
import lombok.Getter;
import lombok.*;
/**
* JustAuth配置类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 14:03
* @since 1.8
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthConfig {
/**
* 客户端id:对应个平台的appKey
*/
private String clientId;
/**
* 客户端Secret:对应个平台的appSecret
*/
private String clientSecret;
/**
* 登录成功后的回调地址
*/
private String redirectUri;
/**
* 支付宝公钥:当选择支付宝登录时,该值可用
*/
private String alipayPublicKey;
/**
* 是否需要申请unionid,目前只针对qq登录
* 注:qq授权登录时,获取unionid需要单独发送邮件申请权限。如果个人开发者账号中申请了该权限,可以将该值置为true,在获取openId时就会同步获取unionId
* 参考链接:http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D
* <p>
* 1.7.1版本新增参数
*/
private boolean unionId;
/**
* 一个神奇的参数,最好使用随机的不可测的内容,可以用来防止CSRF攻击
* <p>
* 1.8.0版本新增参数
*/
private String state;
/**
* Stack Overflow Key
* <p>
* 1.9.0版本新增参数
*/
private String stackOverflowKey;
}
@@ -0,0 +1,563 @@
package me.zhyd.oauth.config;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthResponseStatus;
/**
* 各api需要的url, 用枚举类分平台类型管理
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.0
*/
public enum AuthSource {
/**
* Github
*/
GITHUB {
@Override
public String authorize() {
return "https://github.com/login/oauth/authorize";
}
@Override
public String accessToken() {
return "https://github.com/login/oauth/access_token";
}
@Override
public String userInfo() {
return "https://api.github.com/user";
}
},
/**
* 新浪微博
*/
WEIBO {
@Override
public String authorize() {
return "https://api.weibo.com/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://api.weibo.com/oauth2/access_token";
}
@Override
public String userInfo() {
return "https://api.weibo.com/2/users/show.json";
}
},
/**
* gitee
*/
GITEE {
@Override
public String authorize() {
return "https://gitee.com/oauth/authorize";
}
@Override
public String accessToken() {
return "https://gitee.com/oauth/token";
}
@Override
public String userInfo() {
return "https://gitee.com/api/v5/user";
}
},
/**
* 钉钉
*/
DINGTALK {
@Override
public String authorize() {
return "https://oapi.dingtalk.com/connect/qrconnect";
}
@Override
public String accessToken() {
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
@Override
public String userInfo() {
return "https://oapi.dingtalk.com/sns/getuserinfo_bycode";
}
},
/**
* 百度
*/
BAIDU {
@Override
public String authorize() {
return "https://openapi.baidu.com/oauth/2.0/authorize";
}
@Override
public String accessToken() {
return "https://openapi.baidu.com/oauth/2.0/token";
}
@Override
public String userInfo() {
return "https://openapi.baidu.com/rest/2.0/passport/users/getInfo";
}
@Override
public String revoke() {
return "https://openapi.baidu.com/rest/2.0/passport/auth/revokeAuthorization";
}
@Override
public String refresh() {
return "https://openapi.baidu.com/oauth/2.0/token";
}
},
/**
* csdn
*/
CSDN {
@Override
public String authorize() {
return "https://api.csdn.net/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://api.csdn.net/oauth2/access_token";
}
@Override
public String userInfo() {
return "https://api.csdn.net/user/getinfo";
}
},
/**
* Coding
*/
CODING {
@Override
public String authorize() {
return "https://coding.net/oauth_authorize.html";
}
@Override
public String accessToken() {
return "https://coding.net/api/oauth/access_token";
}
@Override
public String userInfo() {
return "https://coding.net/api/account/current_user";
}
},
/**
* 腾讯云开发者平台(coding升级后就变成腾讯云开发者平台了)
*/
TENCENT_CLOUD {
@Override
public String authorize() {
return "https://dev.tencent.com/oauth_authorize.html";
}
@Override
public String accessToken() {
return "https://dev.tencent.com/api/oauth/access_token";
}
@Override
public String userInfo() {
return "https://dev.tencent.com/api/account/current_user";
}
},
/**
* oschina 开源中国
*/
OSCHINA {
@Override
public String authorize() {
return "https://www.oschina.net/action/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://www.oschina.net/action/openapi/token";
}
@Override
public String userInfo() {
return "https://www.oschina.net/action/openapi/user";
}
},
/**
* 支付宝
*/
ALIPAY {
@Override
public String authorize() {
return "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm";
}
@Override
public String accessToken() {
return "https://openapi.alipay.com/gateway.do";
}
@Override
public String userInfo() {
return "https://openapi.alipay.com/gateway.do";
}
},
/**
* QQ
*/
QQ {
@Override
public String authorize() {
return "https://graph.qq.com/oauth2.0/authorize";
}
@Override
public String accessToken() {
return "https://graph.qq.com/oauth2.0/token";
}
@Override
public String userInfo() {
return "https://graph.qq.com/user/get_user_info";
}
@Override
public String refresh() {
return "https://graph.qq.com/oauth2.0/token";
}
},
/**
* 微信
*/
WECHAT {
@Override
public String authorize() {
return "https://open.weixin.qq.com/connect/qrconnect";
}
@Override
public String accessToken() {
return "https://api.weixin.qq.com/sns/oauth2/access_token";
}
@Override
public String userInfo() {
return "https://api.weixin.qq.com/sns/userinfo";
}
@Override
public String refresh() {
return "https://api.weixin.qq.com/sns/oauth2/refresh_token";
}
},
/**
* 淘宝
*/
TAOBAO {
@Override
public String authorize() {
return "https://oauth.taobao.com/authorize";
}
@Override
public String accessToken() {
return "https://oauth.taobao.com/token";
}
@Override
public String userInfo() {
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
},
/**
* Google
*/
GOOGLE {
@Override
public String authorize() {
return "https://accounts.google.com/o/oauth2/v2/auth";
}
@Override
public String accessToken() {
return "https://www.googleapis.com/oauth2/v4/token";
}
@Override
public String userInfo() {
return "https://www.googleapis.com/oauth2/v3/userinfo";
}
},
/**
* Facebook
*/
FACEBOOK {
@Override
public String authorize() {
return "https://www.facebook.com/v3.3/dialog/oauth";
}
@Override
public String accessToken() {
return "https://graph.facebook.com/v3.3/oauth/access_token";
}
@Override
public String userInfo() {
return "https://graph.facebook.com/v3.3/me";
}
},
/**
* 抖音
*/
DOUYIN {
@Override
public String authorize() {
return "https://open.douyin.com/platform/oauth/connect";
}
@Override
public String accessToken() {
return "https://open.douyin.com/oauth/access_token/";
}
@Override
public String userInfo() {
return "https://open.douyin.com/oauth/userinfo/";
}
@Override
public String refresh() {
return "https://open.douyin.com/oauth/refresh_token/";
}
},
/**
* 领英
*/
LINKEDIN {
@Override
public String authorize() {
return "https://www.linkedin.com/oauth/v2/authorization";
}
@Override
public String accessToken() {
return "https://www.linkedin.com/oauth/v2/accessToken";
}
@Override
public String userInfo() {
return "https://api.linkedin.com/v2/me";
}
@Override
public String refresh() {
return "https://www.linkedin.com/oauth/v2/accessToken";
}
},
/**
* 微软
*/
MICROSOFT {
@Override
public String authorize() {
return "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
}
@Override
public String accessToken() {
return "https://login.microsoftonline.com/common/oauth2/v2.0/token";
}
@Override
public String userInfo() {
return "https://graph.microsoft.com/v1.0/me";
}
@Override
public String refresh() {
return "https://login.microsoftonline.com/common/oauth2/v2.0/token";
}
},
/**
* 小米
*/
MI {
@Override
public String authorize() {
return "https://account.xiaomi.com/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://account.xiaomi.com/oauth2/token";
}
@Override
public String userInfo() {
return "https://open.account.xiaomi.com/user/profile";
}
@Override
public String refresh() {
return "https://account.xiaomi.com/oauth2/token";
}
},
/**
* 今日头条
*/
TOUTIAO {
@Override
public String authorize() {
return "https://open.snssdk.com/auth/authorize";
}
@Override
public String accessToken() {
return "https://open.snssdk.com/auth/token";
}
@Override
public String userInfo() {
return "https://open.snssdk.com/data/user_profile";
}
},
/**
* Teambition
*/
TEAMBITION {
@Override
public String authorize() {
return "https://account.teambition.com/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://account.teambition.com/oauth2/access_token";
}
@Override
public String refresh() {
return "https://account.teambition.com/oauth2/refresh_token";
}
@Override
public String userInfo() {
return "https://api.teambition.com/users/me";
}
},
/**
* 人人网
*/
RENREN {
@Override
public String authorize() {
return "https://graph.renren.com/oauth/authorize";
}
@Override
public String accessToken() {
return "https://graph.renren.com/oauth/token";
}
@Override
public String refresh() {
return "https://graph.renren.com/oauth/token";
}
@Override
public String userInfo() {
return "https://api.renren.com/v2/user/get";
}
},
/**
* Pinterest
*/
PINTEREST {
@Override
public String authorize() {
return "https://api.pinterest.com/oauth";
}
@Override
public String accessToken() {
return "https://api.pinterest.com/v1/oauth/token";
}
@Override
public String userInfo() {
return "https://api.pinterest.com/v1/me";
}
},
/**
* Stack Overflow
*/
STACK_OVERFLOW {
@Override
public String authorize() {
return "https://stackoverflow.com/oauth";
}
@Override
public String accessToken() {
return "https://stackoverflow.com/oauth/access_token/json";
}
@Override
public String userInfo() {
return "https://api.stackexchange.com/2.2/me";
}
};
/**
* 授权的api
*
* @return url
*/
public abstract String authorize();
/**
* 获取accessToken的api
*
* @return url
*/
public abstract String accessToken();
/**
* 获取用户信息的api
*
* @return url
*/
public abstract String userInfo();
/**
* 取消授权的api
*
* @return url
*/
public String revoke() {
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
/**
* 刷新授权的api
*
* @return url
*/
public String refresh() {
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
}
@@ -1,288 +0,0 @@
package me.zhyd.oauth.consts;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.request.ResponseStatus;
/**
* 各api需要的url, 用枚举类分平台类型管理
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/24 21:20
* @since 1.0
*/
public enum ApiUrl {
/**
* Github
*/
GITHUB {
@Override
public String authorize() {
return "https://github.com/login/oauth/authorize";
}
@Override
public String accessToken() {
return "https://github.com/login/oauth/access_token";
}
@Override
public String userInfo() {
return "https://api.github.com/user";
}
@Override
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
},
/**
* 新浪微博
*/
WEIBO {
@Override
public String authorize() {
return "https://api.weibo.com/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://api.weibo.com/oauth2/access_token";
}
@Override
public String userInfo() {
return "https://api.weibo.com/2/users/show.json";
}
@Override
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
},
/**
* gitee
*/
GITEE {
@Override
public String authorize() {
return "https://gitee.com/oauth/authorize";
}
@Override
public String accessToken() {
return "https://gitee.com/oauth/token";
}
@Override
public String userInfo() {
return "https://gitee.com/api/v5/user";
}
@Override
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
},
/**
* 钉钉
*/
DINGTALK {
@Override
public String authorize() {
return "https://oapi.dingtalk.com/connect/qrconnect";
}
@Override
public String accessToken() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String userInfo() {
return "https://oapi.dingtalk.com/sns/getuserinfo_bycode";
}
@Override
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
},
/**
* 百度
*/
BAIDU {
@Override
public String authorize() {
return "https://openapi.baidu.com/oauth/2.0/authorize";
}
@Override
public String accessToken() {
return "https://openapi.baidu.com/oauth/2.0/token";
}
@Override
public String userInfo() {
return "https://openapi.baidu.com/rest/2.0/passport/users/getInfo";
}
@Override
public String revoke() {
return "https://openapi.baidu.com/rest/2.0/passport/auth/revokeAuthorization";
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
},
/**
* csdn
*/
CSDN {
@Override
public String authorize() {
return "https://api.csdn.net/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://api.csdn.net/oauth2/access_token";
}
@Override
public String userInfo() {
return "https://api.csdn.net/user/getinfo";
}
@Override
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
},
/**
* Coding
*/
CODING {
@Override
public String authorize() {
return "https://coding.net/oauth_authorize.html";
}
@Override
public String accessToken() {
return "https://coding.net/api/oauth/access_token";
}
@Override
public String userInfo() {
return "https://coding.net/api/account/current_user";
}
@Override
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
},
/**
* 腾讯云开发者平台(coding升级后就变成腾讯云开发者平台了)
*/
TENCENTCLOUD {
@Override
public String authorize() {
return "https://dev.tencent.com/oauth_authorize.html";
}
@Override
public String accessToken() {
return "https://dev.tencent.com/api/oauth/access_token";
}
@Override
public String userInfo() {
return "https://dev.tencent.com/api/account/current_user";
}
@Override
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
},
/**
* oschina 开源中国
*/
OSCHINA {
@Override
public String authorize() {
return "https://www.oschina.net/action/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://www.oschina.net/action/openapi/token";
}
@Override
public String userInfo() {
return "https://www.oschina.net/action/openapi/user";
}
@Override
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
@Override
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
}
};
public abstract String authorize();
public abstract String accessToken();
public abstract String userInfo();
public abstract String revoke();
public abstract String refresh();
}
@@ -0,0 +1,46 @@
package me.zhyd.oauth.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 今日头条授权登录时的异常状态码
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
@Getter
@AllArgsConstructor
public enum AuthToutiaoErrorCode {
EC0(0, "接口调用成功"),
EC1(1, "API配置错误,未传入Client Key"),
EC2(2, "API配置错误,Client Key错误,请检查是否和开放平台的ClientKey一致"),
EC3(3, "没有授权信息"),
EC4(4, "响应类型错误"),
EC5(5, "授权类型错误"),
EC6(6, "client_secret错误"),
EC7(7, "authorize_code过期"),
EC8(8, "指定url的scheme不是https"),
EC9(9, "接口内部错误,请联系头条技术"),
EC10(10, "access_token过期"),
EC11(11, "缺少access_token"),
EC12(12, "参数缺失"),
EC13(13, "url错误"),
EC21(21, "域名与登记域名不匹配"),
EC999(999, "未知错误,请联系头条技术"),
;
private int code;
private String desc;
public static AuthToutiaoErrorCode getErrorCode(int errorCode) {
AuthToutiaoErrorCode[] errorCodes = AuthToutiaoErrorCode.values();
for (AuthToutiaoErrorCode code : errorCodes) {
if (code.getCode() == errorCode) {
return code;
}
}
return EC999;
}
}
@@ -0,0 +1,36 @@
package me.zhyd.oauth.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户性别
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
@Getter
@AllArgsConstructor
public enum AuthUserGender {
MALE(1, ""), FEMALE(0, ""), UNKNOWN(-1, "未知");
private int code;
private String desc;
public static AuthUserGender getRealGender(String code) {
if (code == null) {
return UNKNOWN;
}
String[] males = {"m", "", "1", "male"};
if (Arrays.asList(males).contains(code.toLowerCase())) {
return MALE;
}
String[] females = {"f", "", "0", "female"};
if (Arrays.asList(females).contains(code.toLowerCase())) {
return FEMALE;
}
return UNKNOWN;
}
}
@@ -1,12 +1,10 @@
package me.zhyd.oauth.exception;
import me.zhyd.oauth.request.ResponseStatus;
import me.zhyd.oauth.model.AuthResponseStatus;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 16:05
* @since 1.8
*/
public class AuthException extends RuntimeException {
@@ -15,16 +13,16 @@ public class AuthException extends RuntimeException {
private String errorMsg;
public AuthException(String errorMsg) {
this(ResponseStatus.FAILURE.getCode(), errorMsg);
this(AuthResponseStatus.FAILURE.getCode(), errorMsg);
}
public AuthException(int errorCode, String errorMsg) {
super(errorCode + ":" + errorMsg);
super(errorMsg);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public AuthException(ResponseStatus status) {
public AuthException(AuthResponseStatus status) {
super(status.getMsg());
}
@@ -32,6 +30,10 @@ public class AuthException extends RuntimeException {
super(message, cause);
}
public AuthException(Throwable cause) {
super(cause);
}
public int getErrorCode() {
return errorCode;
}
@@ -1,62 +0,0 @@
package me.zhyd.oauth.model;
import me.zhyd.oauth.utils.StringUtils;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/19 19:13
* @since 1.8
*/
public enum AuthBaiduErrorCode {
OK("ok", "ok", "ok"),
INVALID_REQUEST("invalid_request", "invalid refresh token", "请求缺少某个必需参数,包含一个不支持的参数或参数值,或者格式不正确。"),
INVALID_CLIENT("invalid_client", "unknown client id", "client_id”、“client_secret”参数无效。"),
INVALID_GRANT("invalid_grant", "The provided authorization grant is revoked", "提供的Access Grant是无效的、过期的或已撤销的,例如,Authorization Code无效(一个授权码只能使用一次)、Refresh Token无效、redirect_uri与获取Authorization Code时提供的不一致、Devie Code无效(一个设备授权码只能使用一次)等。"),
UNAUTHORIZED_CLIENT("unauthorized_client", "The client is not authorized to use this authorization grant type", "应用没有被授权,无法使用所指定的grant_type。"),
UNSUPPORTED_GRANT_TYPE("unsupported_grant_type", "The authorization grant type is not supported", "“grant_type”百度OAuth2.0服务不支持该参数。"),
INVALID_SCOPE("invalid_scope", "The requested scope is exceeds the scope granted by the resource owner", "请求的“scope”参数是无效的、未知的、格式不正确的、或所请求的权限范围超过了数据拥有者所授予的权限范围。"),
EXPIRED_TOKEN("expired_token", "refresh token has been used", "提供的Refresh Token已过期"),
REDIRECT_URI_MISMATCH("redirect_uri_mismatch", "Invalid redirect uri", "“redirect_uri”所在的根域与开发者注册应用时所填写的根域名不匹配。"),
UNSUPPORTED_RESPONSE_TYPE("unsupported_response_type", "The response type is not supported", "“response_type”参数值不为百度OAuth2.0服务所支持,或者应用已经主动禁用了对应的授权模式"),
SLOW_DOWN("slow_down", "The device is polling too frequently", "Device Flow中,设备通过Device Code换取Access Token的接口过于频繁,两次尝试的间隔应大于5秒。"),
AUTHORIZATION_PENDING("authorization_pending", "User has not yet completed the authorization", "Device Flow中,用户还没有对Device Code完成授权操作。"),
AUTHORIZATION_DECLINED("authorization_declined", "User has declined the authorization", "Device Flow中,用户拒绝了对Device Code的授权操作。"),
INVALID_REFERER("invalid_referer", "Invalid Referer", "Implicit Grant模式中,浏览器请求的Referer与根域名绑定不匹配");
private String code;
private String msg;
private String desc;
AuthBaiduErrorCode(String code, String msg, String desc) {
this.code = code;
this.msg = msg;
this.desc = desc;
}
public static AuthBaiduErrorCode getErrorCode(String code) {
if (StringUtils.isEmpty(code)) {
return OK;
}
AuthBaiduErrorCode[] errorCodes = AuthBaiduErrorCode.values();
for (AuthBaiduErrorCode errorCode : errorCodes) {
if (code.equalsIgnoreCase(errorCode.getCode())) {
return errorCode;
}
}
return OK;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
public String getDesc() {
return desc;
}
}
@@ -0,0 +1,31 @@
package me.zhyd.oauth.model;
import lombok.Getter;
import lombok.Setter;
/**
* 授权回调时的参数类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
@Getter
@Setter
public class AuthCallback {
/**
* 访问AuthorizeUrl后回调时带的参数code
*/
private String code;
/**
* 访问AuthorizeUrl后回调时带的参数auth_code,该参数目前只使用于支付宝登录
*/
private String auth_code;
/**
* 访问AuthorizeUrl后回调时带的参数state,用于和请求AuthorizeUrl前的state比较,防止CSRF攻击
*/
private String state;
}
@@ -1,407 +0,0 @@
package me.zhyd.oauth.model;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/19 9:42
* @since 1.8
*/
public enum AuthDingTalkErrorCode {
/**
* 异常状态码
*/
EC1_MINUS(-1, "系统繁忙", "服务器暂不可用,建议稍候再重试1次,最多重试3次"),
EC0(0, "请求成功", "接口调用成功"),
EC404(404, "请求的URI地址不存在", "地址不存在,检查下url是否和文档里写的一致"),
EC33001(33001, "无效的企业ID", "请确认下access_token是否正确"),
EC33002(33002, "无效的微应用的名称", "校验下微应用的名称字段,不能为空且长度不能超过10个字符"),
EC33003(33003, "无效的微应用的描述", "校验下微应用的描述字段,不能为空且长度不能超过20个字符"),
EC33004(33004, "无效的微应用的ICON", "校验下微应用的icon字段,不能为空且确保图标存在"),
EC33005(33005, "无效的微应用的移动端主页", "校验下微应用的移动端主页,不能为空且必须以http开头或https开头"),
EC33006(33006, "无效的微应用的PC端主页", "校验下微应用的PC端主页,必须以http开头或https开头"),
EC33007(33007, "微应用的移动端的主页与PC端主页不同", "校验下微应用的PC端主页,确保它和移动端主页的域名保持一致"),
EC33008(33008, "无效的微应用OA后台的主页", "校验下微应用的后台管理的主页失败,必须以http开头或https开头"),
EC34001(34001, "无效的会话id", "检查下所传的chatId字段是否为空"),
EC34002(34002, "无效的会话消息的发送者", "检查下sender字段是否为空"),
EC34003(34003, "无效的会话消息的发送者的企业Id", "检查下发送者的企业Id"),
EC34004(34004, "无效的会话消息的类型", "检查下msgtype字段,是否为空,是否是定义的那几种类型"),
EC34005(34005, "无效的会话音频消息的播放时间", "该错误码已废弃"),
EC34006(34006, "发送者不在企业中", "检查下发送者是否在企业中"),
EC34007(34007, "发送者不在会话中", "检查下发送者是否在会话id中"),
EC34008(34008, "图片不能为空", "如果发的是图片消息,检查下图片是否为空"),
EC34009(34009, "链接内容不能为空", "检查下messageUrl字段是否为空"),
EC34010(34010, "文件不能为空", "检查下media_id字段是否为空"),
EC34011(34011, "音频文件不能为空", "检查下media_id字段是否为空"),
EC34012(34012, "找不到发送者的企业", "检查下发送者是否是真实的"),
EC34013(34013, "找不到群会话对象", "检查下chatid是否真实存在"),
EC34014(34014, "会话消息的json结构无效或不完整", "检查下消息的json格式是否正确,json的key对应msgtype的value值"),
EC34015(34015, "发送群会话消息失败", "消息发送失败,建议稍后再重试下"),
EC34016(34016, "消息内容长度超过限制", "检查下消息的content字段长度是否超过5000title字段长度是否超过64markdown字段长度是否超过5000single_title字段长度是否超过20single_url字段长度是否超过500btn_json_list字段长度是否超过1000"),
EC40001(40001, "获取access_token时Secret错误,或者access_token无效", "检查下access_token是否正确"),
EC40002(40002, "不合法的凭证类型", ""),
EC40003(40003, "不合法的UserID", "确保该id在通讯录中存在,且是在你所传access_token对应的企业里"),
EC40004(40004, "不合法的媒体文件类型", "检查下type字段,只支持imagevoicefile"),
EC40005(40005, "不合法的文件类型", "如果是文件类型,检查下是否是支持的那几种,目前只支持doc,docxxlsxlsxpptpptxzippdfrar"),
EC40006(40006, "不合法的文件大小", "检查下文件大小,image类型最大1MBfile类型最大10MBvoice类型最大2MB"),
EC40007(40007, "不合法的媒体文件id", "检查下mediaId是否为空,是否真实存在"),
EC40008(40008, "不合法的消息类型", "检查下msgtype是否为空,确保它在开放平台定义的几种类型里,具体见消息类型及格式"),
EC40009(40009, "不合法的部门id", "检查下部门id是否为空,是否为数字且大于0"),
EC40010(40010, "不合法的父部门id", "检查下父部门id是否为一个数字"),
EC40011(40011, "不合法的排序order", "检查下order字段是否为空,是否为数字且大于0"),
EC40012(40012, "不合法的发送者", "检查下sender字段是否为空,是否真实存在"),
EC40013(40013, "不合法的corpid", "检查下corpid是否有效"),
EC40014(40014, "不合法的access_token", "检查下access_token是否正确,注意access_token这个参数应该是带在url后面的"),
EC40015(40015, "发送者不在会话中", "检查下sender字段和cid字段是否能对应上"),
EC40016(40016, "不合法的会话ID", "检查下cid字段是否为空,是否有效"),
EC40017(40017, "在会话中没有找到与发送者在同一企业的人", "cid对应的消息接收者为空,检查下cid字段"),
EC40018(40018, "不允许以递归方式查询部门用户列表", "检查下fetchChild字段,目前不支持递归查询"),
EC40019(40019, "该手机号码对应的用户最多可以加入5个非认证企业", ""),
EC40020(40020, "当前团队人数已经达到上限,用电脑登录钉钉企业管理后台,升级成为认证企业", ""),
EC40021(40021, "更换的号码已注册过钉钉,无法使用该号码", ""),
EC40022(40022, "企业中的手机号码和登录钉钉的手机号码不一致,暂时不支持修改用户信息,可以删除后重新添加", ""),
EC40023(40023, "部门人数达到上限", "部门人数不能超过1000"),
EC40024(40024, "(安全校验不通过)保存失败,团队人数超限。请在手机钉钉绑定支付宝完成实名认证,或者申请企业认证,人数上限自动扩充", ""),
EC40025(40025, "无效的部门JSONArray对象,合法格式需要用中括号括起来,且如果属于多部门,部门id需要用逗号分隔", ""),
EC40029(40029, "不合法的oauth_code", ""),
EC40031(40031, "不合法的UserID列表", "指定的UserID列表,至少存在一个UserID不在通讯录中"),
EC40032(40032, "不合法的UserID列表长度", "检查下列表是否为空,且长度合适。创建部门接口的userPerimits最多接收10000个"),
EC40033(40033, "不合法的请求字符,不能包含\\uxxxx格式的字符", ""),
EC40035(40035, "不合法的参数", "检查下有没有传请求参数,一般发生在http post形式的接口里,没有传参数"),
EC40038(40038, "不合法的请求格式", "检查下参数中是不是少了某个字段,具体参考各个文档的参数介绍"),
EC40039(40039, "不合法的URL长度", ""),
EC40048(40048, "url中包含不合法domain", "发消息接口中消息url链接不安全"),
EC40055(40055, "不合法的agent结构", "已废弃"),
EC40056(40056, "不合法的agentid", "检查下agentid字段是否为空,是否真实存在"),
EC40057(40057, "不合法的callbackurl", ""),
EC40061(40061, "设置应用头像失败", ""),
EC40062(40062, "不合法的应用模式", ""),
EC40063(40063, "不合法的分机号", "tel字段长度超长,长度不能超过50"),
EC40064(40064, "不合法的工作地址", "workPlace长度超长,长度不能超过50个字符"),
EC40065(40065, "不合法的备注", "remark长度超长,长度不能超过1024个字符"),
EC40066(40066, "不合法的部门列表", "部门id列表长度太长,不能超过10000,并且每个id必须是数字"),
EC40067(40067, "标题长度不合法", "检查下标题长度"),
EC40068(40068, "不合法的偏移量", "偏移量必须大于0"),
EC40069(40069, "不合法的分页大小", "分页大小不合法,具体参考每个接口的参数定义"),
EC40070(40070, "不合法的排序参数", "具体参考获取部门成员接口里面对order字段的定义"),
EC40073(40073, "不存在的openid", "openid不能为空"),
EC40077(40077, "不存在的预授权码", ""),
EC40078(40078, "不存在的临时授权码", "临时授权码不能为空,且只能被使用一次"),
EC40079(40079, "不存在的授权信息", "检查下企业是否授权"),
EC40080(40080, "不合法的suitesecret", ""),
EC40082(40082, "不合法的suitetoken", "检查下token"),
EC40083(40083, "不合法的suiteid", "suiteKey字段不合法"),
EC40084(40084, "不合法的永久授权码", "检查下永久授权码是否正确"),
EC40085(40085, "不存在的suiteticket", "检查下suiteticket是否正确,确保是由回调接口正确来接收suiteticket"),
EC40086(40086, "不合法的第三方应用appid", "appid字段不能为空"),
EC40087(40087, "创建永久授权码失败", "稍后再重试下,确保参数都传对"),
EC40088(40088, "不合法的套件key或secret", "稍后再重试下,确保suiteKey和suiteSecret都传对且一一对应"),
EC40089(40089, "不合法的corpid或corpsecret", "稍后再重试下,确保corpid和corpsecret字段传对且一一对应"),
EC40090(40090, "套件已经不存在", "检查下suiteKey字段是否正确"),
EC40091(40091, "用户授权码创建失败,需要用户重新授权", "创建永久授权码失败,需要用户重新授权产生临时授权码"),
EC40103(40103, "用户开启了账号保护,无法被加入到您的团队", "用户在钉钉“我的-设置-隐私”出开启了账号保护"),
EC40104(40104, "无效手机号", "检查手机号格式是否正确"),
EC41001(41001, "缺少access_token参数", "检查下access_token是否传了,注意该参数必须跟在请求url中"),
EC41002(41002, "缺少corpid参数", "检查下corpid是否为空"),
EC41003(41003, "缺少refresh_token参数", "检查下refresh_token是否为空"),
EC41004(41004, "缺少secret参数", "检查下secret参数是否为空"),
EC41005(41005, "缺少多媒体文件数据", ""),
EC41006(41006, "缺少media_id参数", "检查下media_id参数是否为空"),
EC41007(41007, "无效的ssocode", "sso的永久授权code无效,检查下是否为空"),
EC41008(41008, "缺少oauth", ""),
EC41009(41009, "缺少UserID", "检查下UserID是否为空"),
EC41010(41010, "缺少url", "检查下url是否为空"),
EC41011(41011, "缺少agentid", "检查下agentid是否为空"),
EC41012(41012, "缺少应用头像mediaid", "检查下mediaid是否为空"),
EC41013(41013, "缺少应用名字", "检查应用名字是否为空"),
EC41014(41014, "缺少应用描述", "检查应用描述是否为空"),
EC41015(41015, "缺少JSON参数", "检查JSON参数是否为空"),
EC41021(41021, "缺少suitekey", "检查suitekey参数是否为空"),
EC41022(41022, "缺少suitetoken", "检查suitetoken参数是否为空"),
EC41023(41023, "缺少suiteticket", "检查suiteticket参数是否为空"),
EC41024(41024, "缺少suitesecret", "检查suitesecret参数是否为空"),
EC41025(41025, "缺少permanent_code", "检查permanent_code永久授权码参数是否为空"),
EC41026(41026, "缺少tmp_auth_code", "检查tmp_auth_code临时授权码参数是否为空"),
EC41027(41027, "需要授权企业的corpid参数", "检查corpid参数是否为空"),
EC41028(41028, "禁止给全员发送消息", "检查是否有全员发送消息的权限,ISV没有该权限"),
EC41029(41029, "超过消息接收者人数上限", "发送OA消息人数超上限(企业消息人数上限:5000,ISV消息人数上限:1000"),
EC41030(41030, "企业未对该套件授权", "检查该企业是否已经对该套件进行授权"),
EC41031(41031, "auth_corpid和permanent_code不匹配", "激活套件时使用的auth_corpid和permanent_code不匹配"),
EC41041(41041, "查询间隔时间太长", "考勤打卡数据查询间隔时间超过7天"),
EC41044(41044, "禁止发送消息", "检查是否有权限发送消息"),
EC41045(41045, "单应用全员消息/每天总量超限", ""),
EC41046(41046, "超过发送全员消息的每分钟次数上限", "企业OA消息全员发送每天不能超过3次,ISV不能发送全员消息"),
EC41047(41047, "超过给该企业发消息的每分钟次数上限", "企业OA消息每分钟不能超过1500次,ISV OA消息每分钟不能超过200次"),
EC41048(41048, "超过给企业发消息的每分钟次数总上限", ""),
EC41049(41049, "包含违禁内容", "检查消息文本中是否有黄色、反动等词语"),
EC41050(41050, "无效的活动编码", ""),
EC41051(41051, "活动权益的校验失败", ""),
EC41100(41100, "时间参数不合法", "时间参数不能为空,且为“yyyy-MM-dd hh:mm:ss”格式"),
EC41101(41101, "数据内容过长", "请求体字符长度不能大于4096"),
EC41102(41102, "参数值过大", "上传文件或者idlist等参数过大"),
EC42001(42001, "access_token超时", "请检查网络状态"),
EC42002(42002, "refresh_token超时", "请检查网络状态"),
EC42003(42003, "oauth_code超时", "请检查网络状态"),
EC42007(42007, "预授权码失效", "请检查该预授权码是否已经使用过"),
EC42008(42008, "临时授权码失效", "请检查该临时授权码是否已经使用过或者是否不正确"),
EC42009(42009, "suitetoken失效", "请检查该suitetoken是否已经过期"),
EC43001(43001, "需要GET请求", "请检查http请求方式是否正确"),
EC43002(43002, "需要POST请求", "请检查http请求方式是否正确"),
EC43003(43003, "需要HTTPS", "请检查调用接口协议是否是https"),
EC43004(43004, "无效的HTTP HEADER Content-Type", "请检查请求头中的content-type是否正确"),
EC43005(43005, "需要Content-Type为application/json;charset=UTF-8", "请检查请求头中的content-type是否是“application/json;charset=UTF-8”"),
EC43007(43007, "需要授权", "该接口需要access_token才能调用"),
EC43008(43008, "参数需要multipart类型", "检查提交参数中的ENCTYPE是否是multipart类型"),
EC43009(43009, "post参数需要json类型", "请检查post参数数据是否是json类型"),
EC44001(44001, "多媒体文件为空", "请检查多媒体文件数据是否为空"),
EC44002(44002, "POST的数据包为空", "请检查POST的数据包是否为空"),
EC44003(44003, "图文消息内容为空", "请检查图文消息参数是否为空"),
EC44004(44004, "文本消息内容为空", "请检查文本消息参数是否为空"),
EC45001(45001, "多媒体文件大小超过限制", ""),
EC45002(45002, "消息内容超过限制", ""),
EC45003(45003, "标题字段超过限制", ""),
EC45004(45004, "描述字段超过限制", ""),
EC45005(45005, "链接字段超过限制", ""),
EC45006(45006, "图片链接字段超过限制", ""),
EC45007(45007, "语音播放时间超过限制", ""),
EC45008(45008, "图文消息超过限制", ""),
EC45009(45009, "接口调用超过限制", ""),
EC45016(45016, "系统分组,不允许修改", ""),
EC45017(45017, "分组名字过长", ""),
EC45018(45018, "分组数量超过上限", ""),
EC45024(45024, "账号数量超过上限", ""),
EC46001(46001, "不存在媒体数据", ""),
EC46004(46004, "不存在的员工", ""),
EC47001(47001, "解析JSON/XML内容错误", ""),
EC48002(48002, "Api禁用", ""),
EC48003(48003, "suitetoken无效", ""),
EC48004(48004, "授权关系无效", ""),
EC49000(49000, "缺少chatid", "请检查参数中是否有chatid"),
EC49001(49001, "绑定的微应用超过个数限制", "绑定群会话和微应用超过5个"),
EC49002(49002, "一个群只能被一个ISV套件绑定一次", ""),
EC49003(49003, "操作者必须为群主", ""),
EC49004(49004, "添加成员列表和删除成员列表不能有交集", ""),
EC49005(49005, "群人数超过人数限制", ""),
EC49006(49006, "群成员列表必须包含群主", ""),
EC49007(49007, "超过创建群的个数上限", ""),
EC49008(49008, "不合法的群类型,只能传入0或2", ""),
EC49009(49009, "企业群不能添加外部联系人,群主只能为企业员工", ""),
EC49010(49010, "群成员不能为空", ""),
EC49011(49011, "群员工列表超长", ""),
EC49012(49012, "群外部联系人列表超长", ""),
EC49013(49013, "群主不能为空", ""),
EC49014(49014, "非法的群主类型,只能为emp或者ext", ""),
EC49015(49015, "不合法的群名称", ""),
EC49016(49016, "查询企业员工不存在", ""),
EC49017(49017, "查询企业外部联系人不存在", ""),
EC49018(49018, "群主非企业员工", ""),
EC49019(49019, "群主非企业外部通讯录人员", ""),
EC49020(49020, "某人处于勿扰模式,拒绝加入群聊;请先与TA建立好友关系", ""),
EC49021(49021, "非好友建立群聊,认证用户一天只能拉50个人,非认证用户一天只能拉10个人", ""),
EC49022(49022, "某人拒绝加入群聊", ""),
EC49023(49023, "某人处于勿扰模式,拒绝加入群聊;请先与TA建立好友关系", ""),
EC50001(50001, "redirect_uri未授权", ""),
EC50002(50002, "员工不在权限范围", ""),
EC50003(50003, "应用已停用", ""),
EC50004(50004, "企业部门不在授权范围", "检查企业部门是否设置可见范围,具体排查方法请参考通讯录FAQ"),
EC50005(50005, "企业已禁用", ""),
EC52010(52010, "无效的corpid", "请检查corpid参数是否正确"),
EC52011(52011, "jsapi ticket 读取失败", "请检查ticket参数是否正确"),
EC52012(52012, "jsapi 签名生成失败", "请检查“url, nonceStr, timestamp, ticket”等参数是否正确"),
EC52013(52013, "签名校验失败", "请检查“url, nonceStr, timestamp, ticket”等参数是否正确"),
EC52014(52014, "无效的url参数", "请检查url参数是否正确"),
EC52015(52015, "无效的随机字符串参数", "请检查nonceStr参数是否正确"),
EC52016(52016, "无效的签名参数", "请检查“url, nonceStr, timestamp, ticket”等参数是否正确"),
EC52017(52017, "无效的jsapi列表参数", "请检查dd.config中的jsApiList参数是否正确"),
EC52018(52018, "无效的时间戳", "请检查timestamp参数是否正确"),
EC52019(52019, "无效的agentid", "请检查agentid参数是否正确"),
EC60001(60001, "不合法的部门名称", "请检查部门名称是否正确,长度不能超过64个字符"),
EC60002(60002, "部门层级深度超过限制", ""),
EC60003(60003, "部门不存在", ""),
EC60004(60004, "父亲部门不存在", ""),
EC60005(60005, "不允许删除有成员的部门", ""),
EC60006(60006, "不允许删除有子部门的部门", ""),
EC60007(60007, "不允许删除根部门", ""),
EC60008(60008, "父部门下该部门名称已存在", ""),
EC60009(60009, "部门名称含有非法字符", ""),
EC60010(60010, "部门存在循环关系", ""),
EC60011(60011, "没有调用该接口的权限", "请检查当前请求使用的access_token是否有对该部门/人的操作权限,查看获取appSecret授权范围"),
EC60012(60012, "不允许删除默认应用", ""),
EC60013(60013, "不允许关闭应用", ""),
EC60014(60014, "不允许开启应用", ""),
EC60015(60015, "不允许修改默认应用可见范围", ""),
EC60016(60016, "部门id已经存在", ""),
EC60017(60017, "不允许设置企业", ""),
EC60018(60018, "不允许更新根部门", ""),
EC60019(60019, "从部门查询人员失败", "请检查该成员是否在该部门中"),
EC60020(60020, "访问ip不在白名单之中", "如果是企业应用,检查配置的服务器出口ip地址是否和请求ip地址一致。如果是isv应用,请检查套件ip白名单和请求ip是否一致"),
EC60067(60067, "部门的企业群群主不存在", ""),
EC60068(60068, "部门的管理员不存在", ""),
EC60102(60102, "UserID在公司中已存在", ""),
EC60103(60103, "手机号码不合法", ""),
EC60104(60104, "手机号码在公司中已存在", ""),
EC60105(60105, "邮箱不合法", ""),
EC60106(60106, "邮箱已存在", ""),
EC60107(60107, "使用该手机登录钉钉的用户已经在企业中", ""),
EC60110(60110, "部门个数超出限制", ""),
EC60111(60111, "UserID不存在", ""),
EC60112(60112, "用户name不合法", ""),
EC60113(60113, "身份认证信息(手机/邮箱)不能同时为空", ""),
EC60114(60114, "性别不合法", ""),
EC60118(60118, "用户无有效邀请字段(邮箱,手机号)", ""),
EC60119(60119, "不合法的position", ""),
EC60120(60120, "用户已禁用", ""),
EC60121(60121, "找不到该用户", "检查该企业下该员工是否存在"),
EC60122(60122, "不合法的extattr", ""),
EC60123(60123, "不合法的jobnumber", ""),
EC60124(60124, "用户不在此群中", ""),
EC60125(60125, "CRM配置信息创建失败", ""),
EC60126(60126, "CRM配置信息更新失败", ""),
EC60127(60127, "CRM人员配置信息删除失败", ""),
EC70001(70001, "企业不存在或者已经被解散", ""),
EC70002(70002, "获取套件下的微应用失败", ""),
EC70003(70003, "agentid对应微应用不存在", ""),
EC70004(70004, "企业下没有对应该agentid的微应用", "注意:代表应用和企业映射关系的ID (appId的实例化ID),同一个ISV应用在不同企业的agentId不一致"),
EC70005(70005, "ISV激活套件失败", "请检查激活套件使用的参数是否正确"),
EC71006(71006, "回调地址已经存在", ""),
EC71007(71007, "回调地址已不存在", ""),
EC71008(71008, "回调call_back_tag必须在指定的call_back_tag列表中", ""),
EC71009(71009, "返回文本非success", "回调地址返回的内容必须是“success”文本经过加密后的结果"),
EC71010(71010, "POST的JSON数据不包含所需要的参数字段或包含的参数格式非法", ""),
EC71011(71011, "传入的url参数不是合法的url格式", "合法的URL地址是协议+域名+端口+路径path+参数组成"),
EC71012(71012, "url地址访问异常", ""),
EC71013(71013, "此域名或IP不能注册或者接收回调事件", "注意回调地址的域名或者IP必须在套件的ip白名单中,并且该ip必须为外网ip"),
EC72001(72001, "获取钉盘空间失败", "检查domain、agent_id、access_token参数是否正确有效"),
EC72002(72002, "授权钉盘空间访问权限失败", ""),
EC80001(80001, "可信域名没有IPC备案,后续将不能在该域名下正常使用jssdk", ""),
EC81001(81001, "两个用户没有任何关系,请先相互成为好友", ""),
EC81002(81002, "用户拒收消息", ""),
EC88005(88005, "管理日历个人日历操作失败", ""),
EC89001(89001, "管理日历启动导出任务失败", ""),
EC89011(89011, "管理日历写入数据失败", ""),
EC89012(89012, "管理日历更新数据失败", ""),
EC90001(90001, "您的服务器调用钉钉开放平台所有接口的请求都被暂时禁用了", ""),
EC90002(90002, "您的服务器调用钉钉开放平台当前接口的所有请求都被暂时禁用了", ""),
EC90003(90003, "您的企业调用钉钉开放平台所有接口的请求都被暂时禁用了,仅对企业自己的Accesstoken有效", ""),
EC90004(90004, "您当前使用的CorpId及CorpSecret被暂时禁用了,仅对企业自己的Accesstoken有效", ""),
EC90005(90005, "您的企业调用当前接口次数过多,请求被暂时禁用了,仅对企业自己的Accesstoken有效", ""),
EC90006(90006, "您当前使用的CorpId及CorpSecret调用当前接口次数过多,请求被暂时禁用了,仅对企业自己的Accesstoken有效", ""),
EC90007(90007, "您当前要调用的企业的接口次数过多,对该企业的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", ""),
EC90008(90008, "您当前要调用的企业的当前接口次数过多,对此企业下该接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", ""),
EC90009(90009, "您调用企业接口超过了限制,对所有企业的所有接口的请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", ""),
EC90010(90010, "您调用企业当前接口超过了限制,对所有企业的该接口的请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", ""),
EC90011(90011, "您的套件调用企业接口超过了限制,该套件的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", ""),
EC90012(90012, "您的套件调用企业当前接口超过了限制,该套件对此接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", ""),
EC90013(90013, "您的套件调用当前企业的接口超过了限制,该套件对此企业的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", ""),
EC90014(90014, "您的套件调用企业当前接口超过了限制,该套件对此企业该接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", ""),
EC900001(900001, "加密明文文本非法", "加密明文不能为空"),
EC900002(900002, "加密时间戳参数非法", "加密时间戳不能为空"),
EC900003(900003, "加密随机字符串参数非法", "加密随机字符串不能为空"),
EC900004(900004, "不合法的aeskey", "检查aeskey是否符合规格,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,是AESKey的Base64编码。解码后即为32字节长的AESKey"),
EC900005(900005, "签名不匹配", "检查签名计算的参数是否正确。请参考文档获取签名参数"),
EC900006(900006, "计算签名错误", "检查签名计算的参数是否正确。请参考文档获取签名参数"),
EC900007(900007, "计算加密文字错误", "检查是否安装JRE补丁或者对应的JRE版本是否正常。 请参考文档ISV应用开发准备工作"),
EC900008(900008, "计算解密文字错误", "检查是否安装JRE补丁或者对应的JRE版本是否正常。 请参考文档ISV应用开发准备工作"),
EC900009(900009, "计算解密文字长度不匹配", "检查aeskey是否符合规格。长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,是AESKey的Base64编码"),
EC900010(900010, "计算解密文字corpid不匹配", "检查corpid是否正确或者是否为当前企业的corpid"),
EC400010(400010, "激活的设备不存在(未绑定)", ""),
EC400011(400011, "设备已经激活", ""),
EC400020(400020, "无访问权限", ""),
EC400021(400021, "密钥错误", ""),
EC400022(400022, "设备不存在", ""),
EC400023(400023, "用户不存在", ""),
EC400040(400040, "回调不存在", "检查是否注册回调事件"),
EC400041(400041, "回调已经存在", "检查该回调事件是否已注册过"),
EC400042(400042, "企业不存在", ""),
EC400043(400043, "企业不合法", ""),
EC400050(400050, "回调地址无效", "检查回调地址是否正确或者符合地址格式"),
EC400051(400051, "回调地址访问异常", "注意回调地址必须部署到外网以便开发平台通过回调地址推送回调信息"),
EC400052(400052, "回调地址访返回数据错误", ""),
EC400053(400053, "回调地址在黑名单中无法注册", "回调地址已添加黑名单,无法注册"),
EC400054(400054, "回调URL访问超时", ""),
EC400055(400055, "回调设备不在线", ""),
EC400056(400056, "回调访问设备失败", ""),
EC400057(400057, "回调访问设备不存在", ""),
EC420001(420001, "客户不存在", ""),
EC420002(420002, "客户查询失败", ""),
EC420003(420003, "联系人不存在", ""),
EC420004(420004, "联系人查询失败", ""),
EC420005(420005, "客户删除失败", ""),
EC420006(420006, "联系人删除失败", ""),
EC420007(420007, "跟进人绑定失败", ""),
EC420008(420008, "客户id非法", ""),
EC420009(420009, "跟进人id非法", ""),
EC4200010(4200010, "客户联系人id非法", ""),
EC4200011(4200011, "客户描述表单不存在", ""),
EC4200012(4200012, "客户描述表单查询失败", ""),
EC4200013(4200013, "联系人描述表单不存在", ""),
EC4200014(4200014, "联系人描述表单查询失败", ""),
EC4200015(4200015, "客户描述表单格式校验错误", ""),
EC4200016(4200016, "客户描述表单格缺少固定字段", ""),
EC4200017(4200017, "客户联系人描述表单格式校验错误", ""),
EC4200018(4200018, "客户联系人描述表单格缺少固定字段", ""),
EC4200019(4200019, "客户描述表单数据格式校验错误", ""),
EC4200020(4200020, "客户描述表单数据缺少固定字段", ""),
EC4200021(4200021, "客户联系人描述表单数据格式校验错误", ""),
EC4200022(4200022, "客户联系人描述表单数据缺少固定字段", ""),
EC800001(800001, "仅限ISV调用", "只有ISV微应用才能调用"),
EC41042(41042, "加密失败", ""),
EC41043(41043, "解密失败", ""),
EC40100(40100, "分机号已经存在", ""),
EC40101(40101, "邮箱已经存在", ""),
EC33013(33013, "企业自建微应用的个数过多,通过接口创建微应用受限", "此限制只针对企业自建微应用,对ISV应用没有限制"),
EC90017(90017, "此IP使用CorpId及CorpSecret调用接口的CorpId个数超过限制", "从该ip发起超过XX个corpid的请求被限制"),
EC40102(40102, "过期的临时授权码", "注意临时授权只能使用一次后就不能在使用。 需要重新执行授权操作有开放平台推送新的临时授权码"),
EC52020(52020, "未找到服务窗授权", ""),
EC52021(52021, "未找到微应用授权", ""),
EC52022(52022, "无效的jsapi类型", ""),
EC52023(52023, "无效的服务窗agentid", "检查服务窗微应用是否停用或者删除"),
EC52024(52024, "无效的jsapi tag", ""),
EC52025(52025, "无效的安全微应用", ""),
EC52026(52026, "无效的安全微应用URL", ""),
EC71014(71014, "获取套件下的服务窗应用失败", ""),
EC72003(72003, "钉盘空间添加文件失败", ""),
EC60128(60128, "无效的主管id", ""),
EC200001(200001, "表单不能为空", ""),
EC200004(200004, "APP_ID 不允许为空", "app_id为创建套件成功后,创建的ISV微应用的微应用ID。 可以登录开发者后台查看"),
EC200005(200005, "表单名称不允许为空", ""),
EC200006(200006, "表单内容不允许为空", ""),
EC200007(200007, "表单值不允许为空", ""),
EC200008(200008, "表单uuid不存在", ""),
EC400001(400001, "系统错误", ""),
EC400002(400002, "参数错误", "检查参数是否符合规格。具体请参考当前接口的文档的参数说明和参数示例"),
EC400003(400003, "时间戳无效", "检查随机时间戳是否符合规格。具体请参考当前接口的文档的参数说明和参数示例"),
EC400004(400004, "随机数无效", "检查随机随机数是否符合规格。具体请参考当前接口的文档的参数说明和参数示例");
private int code;
private String desc;
private String solution;
AuthDingTalkErrorCode(int code, String desc, String solution) {
this.code = code;
this.desc = desc;
this.solution = solution;
}
public static AuthDingTalkErrorCode getErrorCode(int errorCode) {
AuthDingTalkErrorCode[] errorCodes = AuthDingTalkErrorCode.values();
for (AuthDingTalkErrorCode code : errorCodes) {
if (code.getCode() == errorCode) {
return code;
}
}
return EC1_MINUS;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
public String getSolution() {
return solution;
}
}
@@ -1,20 +1,40 @@
package me.zhyd.oauth.model;
import lombok.Builder;
import lombok.Data;
import me.zhyd.oauth.request.ResponseStatus;
import lombok.Getter;
import lombok.Setter;
/**
* JustAuth统一授权响应类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 15:43
* @since 1.8
*/
@Getter
@Builder
@Data
public class AuthResponse<T> {
private int code = ResponseStatus.SUCCESS.getCode();
private String msg = ResponseStatus.SUCCESS.getMsg();
/**
* 授权响应状态码
*/
private int code;
/**
* 授权响应信息
*/
private String msg;
/**
* 授权响应数据,当且仅当 code = 2000 时返回
*/
private T data;
/**
* 是否请求成功
*
* @return true or false
*/
public boolean ok() {
return this.code == AuthResponseStatus.SUCCESS.getCode();
}
}
@@ -0,0 +1,29 @@
package me.zhyd.oauth.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
@Getter
@AllArgsConstructor
public enum AuthResponseStatus {
SUCCESS(2000, "Success"),
FAILURE(5000, "Failure"),
NOT_IMPLEMENTED(5001, "Not Implemented"),
PARAMETER_INCOMPLETE(5002, "Parameter incomplete"),
UNSUPPORTED(5003, "Unsupported operation"),
NO_AUTH_SOURCE(5004, "AuthSource cannot be null"),
UNIDENTIFIED_PLATFORM(5005, "Unidentified platform"),
ILLEGAL_REDIRECT_URI(5006, "Illegal redirect uri"),
ILLEGAL_REQUEST(5007, "Illegal request"),
ILLEGAL_CODE(5008, "Illegal code"),
;
private int code;
private String msg;
}
@@ -1,24 +0,0 @@
package me.zhyd.oauth.model;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 13:52
* @since 1.8
*/
public enum AuthSource {
GITHUB,
GITEE,
WEIBO,
DINGTALK,
BAIDU,
CSDN,
CODING,
OSCHINA,
TENCEN_CLOUD,
QQ,
WECHAT,
GOOGLE,
}
@@ -2,19 +2,39 @@ package me.zhyd.oauth.model;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
/**
* 授权所需的token
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 15:43
* @since 1.8
*/
@Data
@Getter
@Setter
@Builder
public class AuthToken {
private String accessToken;
private String expireIn;
private int expireIn;
private String refreshToken;
private String uid;
private String openId;
private String accessCode;
private String unionId;
/**
* Google附带属性
*/
private String scope;
private String tokenType;
private String idToken;
/**
* 小米附带属性
*/
private String macAlgorithm;
private String macKey;
}
@@ -1,27 +1,68 @@
package me.zhyd.oauth.model;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
/**
* 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 13:47
* @since 1.8
*/
@Getter
@Setter
@Builder
@Data
public class AuthUser {
/**
* 用户第三方系统的唯一id。在调用方集成改组件时,可以用uuid + source唯一确定一个用户
*/
private String uuid;
/**
* 用户名
*/
private String username;
private String avatar;
private String blog;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户头像
*/
private String avatar;
/**
* 用户网址
*/
private String blog;
/**
* 所在公司
*/
private String company;
/**
* 位置
*/
private String location;
/**
* 用户邮箱
*/
private String email;
/**
* 用户备注(各平台中的用户个人介绍)
*/
private String remark;
/**
* 性别
*/
private AuthUserGender gender;
/**
* 用户来源
*/
private AuthSource source;
private String accessToken;
/**
* 用户授权的token信息
*/
private AuthToken token;
}
@@ -1,40 +0,0 @@
package me.zhyd.oauth.model;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 17:24
* @since 1.8
*/
public enum AuthUserGender {
MALE(1, ""), FEMALE(0, ""), UNKNOW(-1, "");
private int code;
private String desc;
AuthUserGender(int code, String desc) {
this.code = code;
this.desc = desc;
}
public static AuthUserGender getRealGender(String code) {
if (code == null) {
return UNKNOW;
}
if ("m".equals(code) || "".equals(code) || "1".equals(code) || "male".equalsIgnoreCase(code)) {
return MALE;
}
if ("f".equals(code) || "".equals(code) || "0".equals(code) || "female".equalsIgnoreCase(code)) {
return FEMALE;
}
return UNKNOW;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
@@ -0,0 +1,102 @@
package me.zhyd.oauth.request;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.alipay.api.request.AlipayUserInfoShareRequest;
import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.alipay.api.response.AlipayUserInfoShareResponse;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 支付宝登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthAlipayRequest extends AuthDefaultRequest {
private AlipayClient alipayClient;
public AuthAlipayRequest(AuthConfig config) {
super(config, AuthSource.ALIPAY);
this.alipayClient = new DefaultAlipayClient(AuthSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(), "json", "UTF-8", config
.getAlipayPublicKey(), "RSA2");
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setGrantType("authorization_code");
request.setCode(authCallback.getAuth_code());
AlipaySystemOauthTokenResponse response = null;
try {
response = this.alipayClient.execute(request);
} catch (Exception e) {
throw new AuthException(e);
}
if (!response.isSuccess()) {
throw new AuthException(response.getSubMsg());
}
return AuthToken.builder()
.accessToken(response.getAccessToken())
.uid(response.getUserId())
.expireIn(Integer.parseInt(response.getExpiresIn()))
.refreshToken(response.getRefreshToken())
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
AlipayUserInfoShareRequest request = new AlipayUserInfoShareRequest();
AlipayUserInfoShareResponse response = null;
try {
response = this.alipayClient.execute(request, accessToken);
} catch (AlipayApiException e) {
throw new AuthException(e.getErrMsg(), e);
}
if (!response.isSuccess()) {
throw new AuthException(response.getSubMsg());
}
String province = response.getProvince(), city = response.getCity();
String location = String.format("%s %s", StringUtils.isEmpty(province) ? "" : province, StringUtils.isEmpty(city) ? "" : city);
return AuthUser.builder()
.uuid(response.getUserId())
.username(StringUtils.isEmpty(response.getUserName()) ? response.getNickName() : response.getUserName())
.nickname(response.getNickName())
.avatar(response.getAvatar())
.location(location)
.gender(AuthUserGender.getRealGender(response.getGender()))
.token(authToken)
.source(source)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("app_id", config.getClientId())
.queryParam("scope", "auth_user")
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.build();
}
}
@@ -4,60 +4,116 @@ import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 百度账号登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/19 18:43
* @since 1.8
*/
public class AuthBaiduRequest extends BaseAuthRequest {
public class AuthBaiduRequest extends AuthDefaultRequest {
public AuthBaiduRequest(AuthConfig config) {
super(config, AuthSource.BAIDU);
}
@Override
protected String getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getBaiduAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
AuthBaiduErrorCode errorCode = AuthBaiduErrorCode.getErrorCode(accessTokenObject.getString("error"));
if (!AuthBaiduErrorCode.OK.equals(errorCode)) {
throw new AuthException(errorCode.getDesc());
}
return accessTokenObject.getString("access_token");
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
return getAuthToken(response);
}
@Override
protected AuthUser getUserInfo(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getBaiduUserInfoUrl(accessToken)).execute();
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
AuthBaiduErrorCode errorCode = AuthBaiduErrorCode.getErrorCode(object.getString("error"));
if (!AuthBaiduErrorCode.OK.equals(errorCode)) {
throw new AuthException(errorCode.getDesc());
}
this.checkResponse(object);
return AuthUser.builder()
.username(object.getString("username"))
.nickname(object.getString("username"))
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.accessToken(accessToken)
.source(AuthSource.BAIDU)
.build();
.uuid(object.getString("userid"))
.username(object.getString("username"))
.nickname(object.getString("username"))
.avatar(getAvatar(object))
.remark(object.getString("userdetail"))
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.token(authToken)
.source(source)
.build();
}
private String getAvatar(JSONObject object) {
String protrait = object.getString("portrait");
return StringUtils.isEmpty(protrait) ? null : String.format("http://himg.bdimg.com/sys/portrait/item/%s.jpg", protrait);
}
@Override
public AuthResponse revoke(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getBaiduRevokeUrl(accessToken)).execute();
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
ResponseStatus status = object.getIntValue("result") == 1 ? ResponseStatus.SUCCESS : ResponseStatus.FAILURE;
public AuthResponse revoke(AuthToken authToken) {
HttpResponse response = doGetRevoke(authToken);
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
// 返回1表示取消授权成功,否则失败
AuthResponseStatus status = object.getIntValue("result") == 1 ? AuthResponseStatus.SUCCESS : AuthResponseStatus.FAILURE;
return AuthResponse.builder().code(status.getCode()).msg(status.getMsg()).build();
}
@Override
public AuthResponse refresh(AuthToken authToken) {
String refreshUrl = UrlBuilder.fromBaseUrl(this.source.refresh())
.queryParam("grant_type", "refresh_token")
.queryParam("refresh_token", authToken.getRefreshToken())
.queryParam("client_id", this.config.getClientId())
.queryParam("client_secret", this.config.getClientSecret())
.build();
HttpResponse response = HttpRequest.get(refreshUrl).execute();
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getAuthToken(response))
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("display", "popup")
.queryParam("state", getRealState(config.getState()))
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error") || object.containsKey("error_code")) {
String msg = object.containsKey("error_description") ? object.getString("error_description") : object.getString("error_msg");
throw new AuthException(msg);
}
}
private AuthToken getAuthToken(HttpResponse response) {
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.scope(accessTokenObject.getString("scope"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.build();
}
}
@@ -1,59 +1,88 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* Cooding登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/23 15:48
* @since 1.8
*/
public class AuthCodingRequest extends BaseAuthRequest {
public class AuthCodingRequest extends AuthDefaultRequest {
public AuthCodingRequest(AuthConfig config) {
super(config, AuthSource.CODING);
}
@Override
protected String getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getCodingAccessTokenUrl(config.getClientId(), config.getClientSecret(), code);
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.getIntValue("code") != 0) {
throw new AuthException("Unable to get token from coding using code [" + code + "]");
}
return accessTokenObject.getString("access_token");
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.build();
}
@Override
protected AuthUser getUserInfo(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getCodingUserInfoUrl(accessToken)).execute();
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
object = object.getJSONObject("data");
return AuthUser.builder()
.uuid(object.getString("id"))
.username(object.getString("name"))
.avatar("https://coding.net/" + object.getString("avatar"))
.blog("https://coding.net/" + object.getString("path"))
.nickname(object.getString("name"))
.company(object.getString("company"))
.location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.email(object.getString("email"))
.remark(object.getString("slogan"))
.token(authToken)
.source(source)
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.getIntValue("code") != 0) {
throw new AuthException(object.getString("msg"));
}
object = object.getJSONObject("data");
return AuthUser.builder()
.username(object.getString("name"))
.avatar("https://coding.net/" + object.getString("avatar"))
.blog("https://coding.net/" + object.getString("path"))
.nickname(object.getString("name"))
.company(object.getString("company"))
.location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.email(object.getString("email"))
.remark(object.getString("slogan"))
.accessToken(accessToken)
.source(AuthSource.CODING)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("scope", "user")
.queryParam("state", getRealState(config.getState()))
.build();
}
}
@@ -1,49 +1,61 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* CSDN登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/23 15:00
* @since 1.8
*/
public class AuthCsdnRequest extends BaseAuthRequest {
@Deprecated
public class AuthCsdnRequest extends AuthDefaultRequest {
public AuthCsdnRequest(AuthConfig config) {
super(config, AuthSource.CSDN);
}
@Override
protected String getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getCsdnAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error_code")) {
throw new AuthException("Unable to get token from csdn using code [" + code + "]");
}
return accessTokenObject.getString("access_token");
this.checkResponse(accessTokenObject);
return AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build();
}
@Override
protected AuthUser getUserInfo(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getCsdnUserInfoUrl(accessToken)).execute();
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
return AuthUser.builder()
.uuid(object.getString("username"))
.username(object.getString("username"))
.remark(object.getString("description"))
.blog(object.getString("website"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error_code")) {
throw new AuthException(object.getString("error"));
}
return AuthUser.builder()
.username(object.getString("username"))
.accessToken(accessToken)
.source(AuthSource.CSDN)
.build();
}
}
@@ -0,0 +1,201 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.AuthChecker;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 默认的request处理类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @author yangkai.shen (https://xkcoding.com)
* @version 1.0
* @since 1.8
*/
@Slf4j
public abstract class AuthDefaultRequest implements AuthRequest {
protected AuthConfig config;
protected AuthSource source;
public AuthDefaultRequest(AuthConfig config, AuthSource source) {
this.config = config;
this.source = source;
if (!AuthChecker.isSupportedAuth(config, source)) {
throw new AuthException(AuthResponseStatus.PARAMETER_INCOMPLETE);
}
// 校验配置合法性
AuthChecker.checkConfig(config, source);
}
protected abstract AuthToken getAccessToken(AuthCallback authCallback);
protected abstract AuthUser getUserInfo(AuthToken authToken);
@Override
public AuthResponse login(AuthCallback authCallback) {
try {
AuthChecker.checkCode(source == AuthSource.ALIPAY ? authCallback.getAuth_code() : authCallback.getCode());
AuthChecker.checkState(authCallback.getState(), config.getState());
AuthToken authToken = this.getAccessToken(authCallback);
AuthUser user = this.getUserInfo(authToken);
return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build();
} catch (Exception e) {
log.error("Failed to login with oauth authorization.", e);
return this.responseError(e);
}
}
private AuthResponse responseError(Exception e) {
int errorCode = AuthResponseStatus.FAILURE.getCode();
if (e instanceof AuthException) {
errorCode = ((AuthException) e).getErrorCode();
}
return AuthResponse.builder().code(errorCode).msg(e.getMessage()).build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.build();
}
/**
* 返回获取accessToken的url
*
* @param code 授权码
* @return 返回获取accessToken的url
*/
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("code", code)
.queryParam("client_id", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("grant_type", "authorization_code")
.queryParam("redirect_uri", config.getRedirectUri())
.build();
}
/**
* 返回获取accessToken的url
*
* @param refreshToken refreshToken
* @return 返回获取accessToken的url
*/
protected String refreshTokenUrl(String refreshToken) {
return UrlBuilder.fromBaseUrl(source.refresh())
.queryParam("client_id", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("refresh_token", refreshToken)
.queryParam("grant_type", "refresh_token")
.queryParam("redirect_uri", config.getRedirectUri())
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken token
* @return 返回获取userInfo的url
*/
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo()).queryParam("access_token", authToken.getAccessToken()).build();
}
/**
* 返回获取revoke authorization的url
*
* @param authToken token
* @return 返回获取revoke authorization的url
*/
protected String revokeUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.revoke()).queryParam("access_token", authToken.getAccessToken()).build();
}
/**
* 获取state,如果为空, 则默认去当前日期的时间戳
*
* @param state 原始的state
* @return 返回不为null的state
*/
protected String getRealState(String state) {
return StringUtils.isEmpty(state) ? String.valueOf(System.currentTimeMillis()) : state;
}
/**
* 通用的 authorizationCode 协议
*
* @param code code码
* @return HttpResponse
*/
protected HttpResponse doPostAuthorizationCode(String code) {
return HttpRequest.post(accessTokenUrl(code)).execute();
}
/**
* 通用的 authorizationCode 协议
*
* @param code code码
* @return HttpResponse
*/
protected HttpResponse doGetAuthorizationCode(String code) {
return HttpRequest.get(accessTokenUrl(code)).execute();
}
/**
* 通用的 用户信息
*
* @param authToken token封装
* @return HttpResponse
*/
protected HttpResponse doPostUserInfo(AuthToken authToken) {
return HttpRequest.post(userInfoUrl(authToken)).execute();
}
/**
* 通用的 用户信息
*
* @param authToken token封装
* @return HttpResponse
*/
protected HttpResponse doGetUserInfo(AuthToken authToken) {
return HttpRequest.get(userInfoUrl(authToken)).execute();
}
/**
* 通用的post形式的取消授权方法
*
* @param authToken token封装
* @return HttpResponse
*/
protected HttpResponse doPostRevoke(AuthToken authToken) {
return HttpRequest.post(revokeUrl(authToken)).execute();
}
/**
* 通用的post形式的取消授权方法
*
* @param authToken token封装
* @return HttpResponse
*/
protected HttpResponse doGetRevoke(AuthToken authToken) {
return HttpRequest.get(revokeUrl(authToken)).execute();
}
}
@@ -2,61 +2,93 @@ package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthDingTalkErrorCode;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Objects;
/**
* 钉钉登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/18 18:43
* @since 1.8
*/
public class AuthDingTalkRequest extends BaseAuthRequest {
public class AuthDingTalkRequest extends AuthDefaultRequest {
public AuthDingTalkRequest(AuthConfig config) {
super(config, AuthSource.DINGTALK);
}
@Override
protected String getAccessToken(String code) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED);
protected AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder().accessCode(authCallback.getCode()).build();
}
@Override
protected AuthUser getUserInfo(String code) {
// 根据timestamp, appSecret计算签名值
String stringToSign = System.currentTimeMillis() + "";
String urlEncodeSignature = GlobalAuthUtil.generateDingTalkSignature(config.getClientSecret(), stringToSign);
HttpResponse response = HttpRequest.post(UrlBuilder.getDingTalkUserInfoUrl(urlEncodeSignature, stringToSign, config.getClientId()))
.body(Objects.requireNonNull(new JSONObject().put("tmp_auth_code", code)))
.execute();
String userInfo = response.body();
JSONObject object = new JSONObject(userInfo);
AuthDingTalkErrorCode errorCode = AuthDingTalkErrorCode.getErrorCode(object.getInt("errcode"));
if (!AuthDingTalkErrorCode.EC0.equals(errorCode)) {
throw new AuthException(errorCode.getDesc());
protected AuthUser getUserInfo(AuthToken authToken) {
String code = authToken.getAccessCode();
JSONObject param = new JSONObject();
param.put("tmp_auth_code", code);
HttpResponse response = HttpRequest.post(userInfoUrl(authToken)).body(param.toJSONString()).execute();
JSONObject object = JSON.parseObject(response.body());
if (object.getIntValue("errcode") != 0) {
throw new AuthException(object.getString("errmsg"));
}
object = object.getJSONObject("user_info");
AuthToken token = AuthToken.builder()
.openId(object.getString("openid"))
.unionId(object.getString("unionid"))
.build();
return AuthUser.builder()
.nickname(object.getStr("nick"))
.source(AuthSource.DINGTALK)
.build();
.uuid(object.getString("unionid"))
.nickname(object.getString("nick"))
.username(object.getString("nick"))
.gender(AuthUserGender.UNKNOWN)
.source(source)
.token(token)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public AuthResponse login(String code) {
return AuthResponse.builder()
.data(this.getUserInfo(code))
.build();
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("appid", config.getClientId())
.queryParam("scope", "snsapi_login")
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
// 根据timestamp, appSecret计算签名值
String timestamp = System.currentTimeMillis() + "";
String urlEncodeSignature = GlobalAuthUtil.generateDingTalkSignature(config.getClientSecret(), timestamp);
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("signature", urlEncodeSignature)
.queryParam("timestamp", timestamp)
.queryParam("accessKey", config.getClientId())
.build();
}
}
@@ -0,0 +1,151 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 抖音登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthDouyinRequest extends AuthDefaultRequest {
public AuthDouyinRequest(AuthConfig config) {
super(config, AuthSource.DOUYIN);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject userInfoObject = JSONObject.parseObject(response.body());
this.checkResponse(userInfoObject);
return AuthUser.builder()
.uuid(userInfoObject.getString("union_id"))
.username(userInfoObject.getString("nickname"))
.nickname(userInfoObject.getString("nickname"))
.avatar(userInfoObject.getString("avatar"))
.remark(userInfoObject.getString("description"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
String message = object.getString("message");
JSONObject data = object.getJSONObject("data");
int errorCode = data.getIntValue("error_code");
if ("error".equals(message) || errorCode != 0) {
throw new AuthException(errorCode, data.getString("description"));
}
}
/**
* 获取token,适用于获取access_token和刷新token
*
* @param accessTokenUrl 实际请求token的地址
* @return token对象
*/
private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
String accessTokenStr = response.body();
JSONObject object = JSONObject.parseObject(accessTokenStr);
this.checkResponse(object);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.openId(object.getString("open_id"))
.expireIn(object.getIntValue("expires_in"))
.refreshToken(object.getString("refresh_token"))
.scope(object.getString("scope"))
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_key", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.queryParam("scope", "user_info")
.build();
}
/**
* 返回获取accessToken的url
*
* @param code oauth的授权码
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("code", code)
.queryParam("client_key", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("grant_type", "authorization_code")
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken oauth返回的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("open_id", authToken.getOpenId())
.build();
}
/**
* 返回获取accessToken的url
*
* @param refreshToken oauth返回的refreshtoken
* @return 返回获取accessToken的url
*/
@Override
protected String refreshTokenUrl(String refreshToken) {
return UrlBuilder.fromBaseUrl(source.refresh())
.queryParam("client_key", config.getClientId())
.queryParam("refresh_token", refreshToken)
.queryParam("grant_type", "refresh_token")
.build();
}
}
@@ -0,0 +1,94 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* Facebook登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthFacebookRequest extends AuthDefaultRequest {
public AuthFacebookRequest(AuthConfig config) {
super(config, AuthSource.FACEBOOK);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.tokenType(accessTokenObject.getString("token_type"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
return AuthUser.builder()
.uuid(object.getString("id"))
.username(object.getString("name"))
.nickname(object.getString("name"))
.avatar(getUserPicture(object))
.location(object.getString("locale"))
.email(object.getString("email"))
.gender(AuthUserGender.getRealGender(object.getString("gender")))
.token(authToken)
.source(source)
.build();
}
private String getUserPicture(JSONObject object) {
String picture = null;
if (object.containsKey("picture")) {
JSONObject pictureObj = object.getJSONObject("picture");
pictureObj = pictureObj.getJSONObject("data");
if (null != pictureObj) {
picture = pictureObj.getString("url");
}
}
return picture;
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("fields", "id,name,birthday,gender,hometown,email,devices,picture.width(400)")
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getJSONObject("error").getString("message"));
}
}
}
@@ -1,54 +1,72 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* Gitee登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 16:31
* @since 1.8
*/
public class AuthGiteeRequest extends BaseAuthRequest {
public class AuthGiteeRequest extends AuthDefaultRequest {
public AuthGiteeRequest(AuthConfig config) {
super(config, AuthSource.GITEE);
}
@Override
protected String getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getGiteeAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error")) {
throw new AuthException("Unable to get token from gitee using code [" + code + "]");
}
return accessTokenObject.getString("access_token");
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.scope(accessTokenObject.getString("scope"))
.tokenType(accessTokenObject.getString("token_type"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.build();
}
@Override
protected AuthUser getUserInfo(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getGiteeUserInfoUrl(accessToken)).execute();
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
return AuthUser.builder()
.username(object.getString("login"))
.avatar(object.getString("avatar_url"))
.blog(object.getString("blog"))
.nickname(object.getString("name"))
.company(object.getString("company"))
.location(object.getString("address"))
.email(object.getString("email"))
.remark(object.getString("bio"))
.accessToken(accessToken)
.source(AuthSource.GITEE)
.build();
.uuid(object.getString("id"))
.username(object.getString("login"))
.avatar(object.getString("avatar_url"))
.blog(object.getString("blog"))
.nickname(object.getString("name"))
.company(object.getString("company"))
.location(object.getString("address"))
.email(object.getString("email"))
.remark(object.getString("bio"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
}
}
@@ -1,57 +1,74 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Map;
/**
* Github登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 16:31
* @since 1.8
*/
public class AuthGithubRequest extends BaseAuthRequest {
public class AuthGithubRequest extends AuthDefaultRequest {
public AuthGithubRequest(AuthConfig config) {
super(config, AuthSource.GITHUB);
}
@Override
protected String getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getGithubAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
Map<String, String> res = GlobalAuthUtil.parseStringToMap(response.body());
if (res.containsKey("error")) {
throw new AuthException(res.get("error") + ":" + res.get("error_description"));
}
return res.get("access_token");
return AuthToken.builder()
.accessToken(res.get("access_token"))
.scope(res.get("scope"))
.tokenType(res.get("token_type"))
.build();
}
@Override
protected AuthUser getUserInfo(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getGithubUserInfoUrl(accessToken)).execute();
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
return AuthUser.builder()
.username(object.getString("login"))
.avatar(object.getString("avatar_url"))
.blog(object.getString("blog"))
.nickname(object.getString("name"))
.company(object.getString("company"))
.location(object.getString("location"))
.email(object.getString("email"))
.remark(object.getString("bio"))
.accessToken(accessToken)
.source(AuthSource.GITHUB)
.build();
.uuid(object.getString("id"))
.username(object.getString("login"))
.avatar(object.getString("avatar_url"))
.blog(object.getString("blog"))
.nickname(object.getString("name"))
.company(object.getString("company"))
.location(object.getString("location"))
.email(object.getString("email"))
.remark(object.getString("bio"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
}
}
@@ -0,0 +1,101 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* Google登录
*
* @author yangkai.shen (https://xkcoding.com)
* @version 1.3
* @since 1.3
*/
public class AuthGoogleRequest extends AuthDefaultRequest {
public AuthGoogleRequest(AuthConfig config) {
super(config, AuthSource.GOOGLE);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.scope(accessTokenObject.getString("scope"))
.tokenType(accessTokenObject.getString("token_type"))
.idToken(accessTokenObject.getString("id_token"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = HttpRequest.post(userInfoUrl(authToken))
.header("Authorization", "Bearer " + authToken.getAccessToken())
.execute();
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
return AuthUser.builder()
.uuid(object.getString("sub"))
.username(object.getString("email"))
.avatar(object.getString("picture"))
.nickname(object.getString("name"))
.location(object.getString("locale"))
.email(object.getString("email"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 返回认证url,可自行跳转页面
* https://openidconnect.googleapis.com/v1/userinfo
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("scope", "openid%20email%20profile")
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo()).queryParam("access_token", authToken.getAccessToken()).build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error") || object.containsKey("error_description")) {
throw new AuthException(object.containsKey("error") + ":" + object.getString("error_description"));
}
}
}
@@ -0,0 +1,212 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 领英登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthLinkedinRequest extends AuthDefaultRequest {
public AuthLinkedinRequest(AuthConfig config) {
super(config, AuthSource.LINKEDIN);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(userInfoUrl(authToken))
.header("Host", "api.linkedin.com")
.header("Connection", "Keep-Alive")
.header("Authorization", "Bearer " + accessToken)
.execute();
JSONObject userInfoObject = JSONObject.parseObject(response.body());
this.checkResponse(userInfoObject);
String userName = getUserName(userInfoObject);
// 获取用户头像
String avatar = this.getAvatar(userInfoObject);
// 获取用户邮箱地址
String email = this.getUserEmail(accessToken);
return AuthUser.builder()
.uuid(userInfoObject.getString("id"))
.username(userName)
.nickname(userName)
.avatar(avatar)
.email(email)
.token(authToken)
.gender(AuthUserGender.UNKNOWN)
.source(AuthSource.LINKEDIN)
.build();
}
/**
* 获取用户的真实名
*
* @param userInfoObject 用户json对象
* @return 用户名
*/
private String getUserName(JSONObject userInfoObject) {
String firstName, lastName;
// 获取firstName
if (userInfoObject.containsKey("localizedFirstName")) {
firstName = userInfoObject.getString("localizedFirstName");
} else {
firstName = getUserName(userInfoObject, "firstName");
}
// 获取lastName
if (userInfoObject.containsKey("localizedLastName")) {
lastName = userInfoObject.getString("localizedLastName");
} else {
lastName = getUserName(userInfoObject, "lastName");
}
return firstName + " " + lastName;
}
/**
* 获取用户的头像
*
* @param userInfoObject 用户json对象
* @return 用户的头像地址
*/
private String getAvatar(JSONObject userInfoObject) {
String avatar = null;
JSONObject profilePictureObject = userInfoObject.getJSONObject("profilePicture");
if (profilePictureObject.containsKey("displayImage~")) {
JSONArray displayImageElements = profilePictureObject.getJSONObject("displayImage~")
.getJSONArray("elements");
if (null != displayImageElements && displayImageElements.size() > 0) {
JSONObject largestImageObj = displayImageElements.getJSONObject(displayImageElements.size() - 1);
avatar = largestImageObj.getJSONArray("identifiers").getJSONObject(0).getString("identifier");
}
}
return avatar;
}
/**
* 获取用户的email
*
* @param accessToken 用户授权后返回的token
* @return 用户的邮箱地址
*/
private String getUserEmail(String accessToken) {
String email = null;
HttpResponse emailResponse = HttpRequest.get("https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))")
.header("Host", "api.linkedin.com")
.header("Connection", "Keep-Alive")
.header("Authorization", "Bearer " + accessToken)
.execute();
JSONObject emailObj = JSONObject.parseObject(emailResponse.body());
this.checkResponse(emailObj);
Object obj = JSONPath.eval(emailObj, "$['elements'][0]['handle~']['emailAddress']");
return null == obj ? null : (String) obj;
}
private String getUserName(JSONObject userInfoObject, String nameKey) {
String firstName;
JSONObject firstNameObj = userInfoObject.getJSONObject(nameKey);
JSONObject localizedObj = firstNameObj.getJSONObject("localized");
JSONObject preferredLocaleObj = firstNameObj.getJSONObject("preferredLocale");
firstName = localizedObj.getString(preferredLocaleObj.getString("language") + "_" + preferredLocaleObj.getString("country"));
return firstName;
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
String refreshToken = oldToken.getRefreshToken();
if (StringUtils.isEmpty(refreshToken)) {
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
String refreshTokenUrl = refreshTokenUrl(refreshToken);
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl))
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
}
/**
* 获取token,适用于获取access_token和刷新token
*
* @param accessTokenUrl 实际请求token的地址
* @return token对象
*/
private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.post(accessTokenUrl)
.header("Host", "www.linkedin.com")
.contentType("application/x-www-form-urlencoded")
.execute();
String accessTokenStr = response.body();
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.queryParam("scope", "r_liteprofile%20r_emailaddress%20w_member_social")
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("projection", "(id,firstName,lastName,profilePicture(displayImage~:playableStreams))")
.build();
}
}
@@ -0,0 +1,141 @@
package me.zhyd.oauth.request;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder;
import java.text.MessageFormat;
/**
* 小米登录
*
* @author yangkai.shen (https://xkcoding.com)
* @version 1.5
* @since 1.5
*/
@Slf4j
public class AuthMiRequest extends AuthDefaultRequest {
private static final String PREFIX = "&&&START&&&";
public AuthMiRequest(AuthConfig config) {
super(config, AuthSource.MI);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return getToken(accessTokenUrl(authCallback.getCode()));
}
private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
String jsonStr = StrUtil.replace(response.body(), PREFIX, StrUtil.EMPTY);
JSONObject accessTokenObject = JSONObject.parseObject(jsonStr);
if (accessTokenObject.containsKey("error")) {
throw new AuthException(accessTokenObject.getString("error_description"));
}
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.scope(accessTokenObject.getString("scope"))
.tokenType(accessTokenObject.getString("token_type"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.openId(accessTokenObject.getString("openId"))
.macAlgorithm(accessTokenObject.getString("mac_algorithm"))
.macKey(accessTokenObject.getString("mac_key"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
// 获取用户信息
HttpResponse userResponse = doGetUserInfo(authToken);
JSONObject userProfile = JSONObject.parseObject(userResponse.body());
if ("error".equalsIgnoreCase(userProfile.getString("result"))) {
throw new AuthException(userProfile.getString("description"));
}
JSONObject user = userProfile.getJSONObject("data");
AuthUser authUser = AuthUser.builder()
.uuid(authToken.getOpenId())
.username(user.getString("miliaoNick"))
.nickname(user.getString("miliaoNick"))
.avatar(user.getString("miliaoIcon"))
.email(user.getString("mail"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
// 获取用户邮箱手机号等信息
String emailPhoneUrl = MessageFormat.format("{0}?clientId={1}&token={2}", "https://open.account.xiaomi.com/user/phoneAndEmail", config
.getClientId(), authToken.getAccessToken());
HttpResponse emailResponse = HttpRequest.get(emailPhoneUrl).execute();
JSONObject userEmailPhone = JSONObject.parseObject(emailResponse.body());
if (!"error".equalsIgnoreCase(userEmailPhone.getString("result"))) {
JSONObject emailPhone = userEmailPhone.getJSONObject("data");
authUser.setEmail(emailPhone.getString("email"));
} else {
log.warn("小米开发平台暂时不对外开放用户手机及邮箱信息的获取");
}
return authUser;
}
/**
* 刷新access token (续期)
*
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(authToken.getRefreshToken())))
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.queryParam("scope", "user/profile%20user/openIdV2%20user/phoneAndEmail")
.queryParam("skip_confirm", "false")
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("clientId", config.getClientId())
.queryParam("token", authToken.getAccessToken())
.build();
}
}
@@ -0,0 +1,167 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder;
import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap;
/**
* 微软登录
*
* @author yangkai.shen (https://xkcoding.com)
* @version 1.5
* @since 1.5
*/
public class AuthMicrosoftRequest extends AuthDefaultRequest {
public AuthMicrosoftRequest(AuthConfig config) {
super(config, AuthSource.MICROSOFT);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return getToken(accessTokenUrl(authCallback.getCode()));
}
/**
* 获取token,适用于获取access_token和刷新token
*
* @param accessTokenUrl 实际请求token的地址
* @return token对象
*/
private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.post(accessTokenUrl)
.header("Host", "https://login.microsoftonline.com")
.contentType("application/x-www-form-urlencoded")
.form(parseQueryToMap(accessTokenUrl))
.execute();
String accessTokenStr = response.body();
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.scope(accessTokenObject.getString("scope"))
.tokenType(accessTokenObject.getString("token_type"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String token = authToken.getAccessToken();
String tokenType = authToken.getTokenType();
String jwt = tokenType + " " + token;
HttpResponse response = HttpRequest.get(userInfoUrl(authToken)).header("Authorization", jwt).execute();
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
return AuthUser.builder()
.uuid(object.getString("id"))
.username(object.getString("userPrincipalName"))
.nickname(object.getString("displayName"))
.location(object.getString("officeLocation"))
.email(object.getString("mail"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 刷新access token (续期)
*
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(authToken.getRefreshToken())))
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("response_mode", "query")
.queryParam("scope", "offline_access%20user.read%20mail.read")
.queryParam("state", getRealState(config.getState()))
.build();
}
/**
* 返回获取accessToken的url
*
* @param code
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("code", code)
.queryParam("client_id", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("grant_type", "authorization_code")
.queryParam("scope", "user.read%20mail.read")
.queryParam("redirect_uri", config.getRedirectUri())
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo()).build();
}
/**
* 返回获取accessToken的url
*
* @param refreshToken 用户授权后的token
* @return 返回获取accessToken的url
*/
@Override
protected String refreshTokenUrl(String refreshToken) {
return UrlBuilder.fromBaseUrl(source.refresh())
.queryParam("client_id", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("refresh_token", refreshToken)
.queryParam("grant_type", "refresh_token")
.queryParam("scope", "user.read%20mail.read")
.queryParam("redirect_uri", config.getRedirectUri())
.build();
}
}
@@ -1,56 +1,101 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* oschina登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/23 15:48
* @since 1.8
*/
public class AuthOschinaRequest extends BaseAuthRequest {
public class AuthOschinaRequest extends AuthDefaultRequest {
public AuthOschinaRequest(AuthConfig config) {
super(config, AuthSource.OSCHINA);
}
@Override
protected String getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getOschinaAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error")) {
throw new AuthException("Unable to get token from oschina using code [" + code + "]");
}
return accessTokenObject.getString("access_token");
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.uid(accessTokenObject.getString("uid"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.build();
}
@Override
protected AuthUser getUserInfo(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getOschinaUserInfoUrl(accessToken)).execute();
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
return AuthUser.builder()
.uuid(object.getString("id"))
.username(object.getString("name"))
.nickname(object.getString("name"))
.avatar(object.getString("avatar"))
.blog(object.getString("url"))
.location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("gender")))
.email(object.getString("email"))
.token(authToken)
.source(source)
.build();
}
/**
* 返回获取accessToken的url
*
* @param code
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("code", code)
.queryParam("client_id", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("grant_type", "authorization_code")
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("dataType", "json")
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("dataType", "json")
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
return AuthUser.builder()
.username(object.getString("name"))
.nickname(object.getString("name"))
.avatar(object.getString("avatar"))
.blog(object.getString("url"))
.location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("gender")))
.email(object.getString("email"))
.accessToken(accessToken)
.source(AuthSource.OSCHINA)
.build();
}
}
@@ -0,0 +1,107 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Objects;
import static me.zhyd.oauth.config.AuthSource.PINTEREST;
/**
* Pinterest登录
*
* @author hongwei.peng (pengisgood(at)gmail(dot)com)
* @version 1.9.0
* @since 1.8
*/
public class AuthPinterestRequest extends AuthDefaultRequest {
private static final String FAILURE = "failure";
public AuthPinterestRequest(AuthConfig config) {
super(config, PINTEREST);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.tokenType(accessTokenObject.getString("token_type"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String userinfoUrl = userInfoUrl(authToken);
HttpResponse response = HttpRequest.get(userinfoUrl).setFollowRedirects(true).execute();
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
JSONObject userObj = object.getJSONObject("data");
return AuthUser.builder()
.uuid(userObj.getString("id"))
.avatar(getAvatarUrl(userObj))
.username(userObj.getString("username"))
.nickname(userObj.getString("first_name") + " " + userObj.getString("last_name"))
.gender(AuthUserGender.UNKNOWN)
.remark(userObj.getString("bio"))
.token(authToken)
.source(source)
.build();
}
private String getAvatarUrl(JSONObject userObj) {
// image is a map data structure
JSONObject jsonObject = userObj.getJSONObject("image");
if (Objects.isNull(jsonObject)) {
return null;
}
return jsonObject.getJSONObject("60x60").getString("url");
}
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.queryParam("scope", "read_public")
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken token
* @return 返回获取userInfo的url
*/
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("fields", "id,username,first_name,last_name,bio,image")
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (!object.containsKey("status") && FAILURE.equals(object.getString("status"))) {
throw new AuthException(object.getString("message"));
}
}
}
@@ -0,0 +1,122 @@
package me.zhyd.oauth.request;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Map;
/**
* qq登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @author yangkai.shen (https://xkcoding.com)
* @version 1.0
* @since 1.8
*/
public class AuthQqRequest extends AuthDefaultRequest {
public AuthQqRequest(AuthConfig config) {
super(config, AuthSource.QQ);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
return getAuthToken(response);
}
@Override
public AuthResponse refresh(AuthToken authToken) {
HttpResponse response = HttpRequest.get(refreshTokenUrl(authToken.getRefreshToken())).execute();
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getAuthToken(response))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String openId = this.getOpenId(authToken);
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
if (object.getIntValue("ret") != 0) {
throw new AuthException(object.getString("msg"));
}
String avatar = object.getString("figureurl_qq_2");
if (StringUtils.isEmpty(avatar)) {
avatar = object.getString("figureurl_qq_1");
}
String location = String.format("%s-%s", object.getString("province"), object.getString("city"));
return AuthUser.builder()
.username(object.getString("nickname"))
.nickname(object.getString("nickname"))
.avatar(avatar)
.location(location)
.uuid(openId)
.gender(AuthUserGender.getRealGender(object.getString("gender")))
.token(authToken)
.source(source)
.build();
}
private String getOpenId(AuthToken authToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.fromBaseUrl("https://graph.qq.com/oauth2.0/me")
.queryParam("access_token", authToken.getAccessToken())
.queryParam("unionid", config.isUnionId() ? 1 : 0)
.build()).execute();
if (response.isOk()) {
String body = response.body();
String removePrefix = StrUtil.replace(body, "callback(", "");
String removeSuffix = StrUtil.replace(removePrefix, ");", "");
String openId = StrUtil.trim(removeSuffix);
JSONObject object = JSONObject.parseObject(openId);
if (object.containsKey("error")) {
throw new AuthException(object.get("error") + ":" + object.get("error_description"));
}
authToken.setOpenId(object.getString("openid"));
if (object.containsKey("unionid")) {
authToken.setUnionId(object.getString("unionid"));
}
return StringUtils.isEmpty(authToken.getUnionId()) ? authToken.getOpenId() : authToken.getUnionId();
}
throw new AuthException("request error");
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("oauth_consumer_key", config.getClientId())
.queryParam("openid", authToken.getOpenId())
.build();
}
private AuthToken getAuthToken(HttpResponse response) {
Map<String, String> accessTokenObject = GlobalAuthUtil.parseStringToMap(response.body());
if (!accessTokenObject.containsKey("access_token") || accessTokenObject.containsKey("code")) {
throw new AuthException(accessTokenObject.get("msg"));
}
return AuthToken.builder()
.accessToken(accessTokenObject.get("access_token"))
.expireIn(Integer.valueOf(accessTokenObject.get("expires_in")))
.refreshToken(accessTokenObject.get("refresh_token"))
.build();
}
}
@@ -0,0 +1,113 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Objects;
import static me.zhyd.oauth.config.AuthSource.RENREN;
import static me.zhyd.oauth.model.AuthResponseStatus.SUCCESS;
/**
* 人人登录
*
* @author hongwei.peng (pengisgood(at)gmail(dot)com)
* @version 1.9.0
* @since 1.8
*/
public class AuthRenrenRequest extends AuthDefaultRequest {
public AuthRenrenRequest(AuthConfig config) {
super(config, RENREN);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject userObj = JSONObject.parseObject(response.body()).getJSONObject("response");
return AuthUser.builder()
.uuid(userObj.getString("id"))
.avatar(getAvatarUrl(userObj))
.nickname(userObj.getString("name"))
.company(getCompany(userObj))
.gender(getGender(userObj))
.token(authToken)
.source(source)
.build();
}
@Override
public AuthResponse refresh(AuthToken authToken) {
return AuthResponse.builder()
.code(SUCCESS.getCode())
.data(getToken(this.refreshTokenUrl(authToken.getRefreshToken())))
.build();
}
private AuthToken getToken(String url) {
HttpResponse response = HttpRequest.post(url).execute();
JSONObject jsonObject = JSONObject.parseObject(response.body());
if (jsonObject.containsKey("error")) {
throw new AuthException("Failed to get token from Renren: " + jsonObject);
}
return AuthToken.builder()
.tokenType(jsonObject.getString("token_type"))
.expireIn(jsonObject.getIntValue("expires_in"))
.accessToken(jsonObject.getString("access_token"))
.refreshToken(jsonObject.getString("refresh_token"))
.openId(jsonObject.getJSONObject("user").getString("id"))
.build();
}
private String getAvatarUrl(JSONObject userObj) {
JSONArray jsonArray = userObj.getJSONArray("avatar");
if (Objects.isNull(jsonArray) || jsonArray.isEmpty()) {
return null;
}
return jsonArray.getJSONObject(0).getString("url");
}
private AuthUserGender getGender(JSONObject userObj) {
JSONObject basicInformation = userObj.getJSONObject("basicInformation");
if (Objects.isNull(basicInformation)) {
return AuthUserGender.UNKNOWN;
}
return AuthUserGender.getRealGender(basicInformation.getString("sex"));
}
private String getCompany(JSONObject userObj) {
JSONArray jsonArray = userObj.getJSONArray("work");
if (Objects.isNull(jsonArray) || jsonArray.isEmpty()) {
return null;
}
return jsonArray.getJSONObject(0).getString("name");
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("userId", authToken.getOpenId())
.build();
}
}
@@ -1,49 +1,54 @@
package me.zhyd.oauth.request;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthResponseStatus;
import me.zhyd.oauth.model.AuthToken;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 15:45
* @since 1.8
*/
public interface AuthRequest {
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
default String authorize() {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED);
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
/**
* 第三方登录
*
* @param code 通过authorize换回的code
* @param authCallback 用于接收回调参数的实体
* @return 返回登录成功后的用户信息
*/
default AuthResponse login(String code) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED);
default AuthResponse login(AuthCallback authCallback) {
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
/**
* 撤销授权
*
* @param accessToken 登录成功后返回的accessToken
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
default AuthResponse revoke(String accessToken) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED);
default AuthResponse revoke(AuthToken authToken) {
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
/**
* 刷新access token (续期)
*
* @param accessToken 登录成功后返回的accessToken
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
default AuthResponse refresh(String accessToken) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED);
default AuthResponse refresh(AuthToken authToken) {
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
}
@@ -0,0 +1,91 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
import static me.zhyd.oauth.config.AuthSource.STACK_OVERFLOW;
import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap;
/**
* Stack Overflow登录
*
* @author hongwei.peng (pengisgood(at)gmail(dot)com)
* @version 1.9.0
* @since 1.8
*/
public class AuthStackOverflowRequest extends AuthDefaultRequest {
public AuthStackOverflowRequest(AuthConfig config) {
super(config, STACK_OVERFLOW);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = accessTokenUrl(authCallback.getCode());
HttpResponse response = HttpRequest.post(accessTokenUrl)
.contentType("application/x-www-form-urlencoded")
.form(parseQueryToMap(accessTokenUrl))
.execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String userInfoUrl = UrlBuilder.fromBaseUrl(this.source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("site", "stackoverflow")
.queryParam("key", this.config.getStackOverflowKey())
.build();
HttpResponse response = HttpRequest.get(userInfoUrl).execute();
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
JSONObject userObj = object.getJSONArray("items").getJSONObject(0);
return AuthUser.builder()
.uuid(userObj.getString("user_id"))
.avatar(userObj.getString("profile_image"))
.location(userObj.getString("location"))
.nickname(userObj.getString("display_name"))
.blog(userObj.getString("website_url"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.queryParam("scope", "read_inbox")
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
}
}
@@ -0,0 +1,72 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 淘宝登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthTaobaoRequest extends AuthDefaultRequest {
public AuthTaobaoRequest(AuthConfig config) {
super(config, AuthSource.TAOBAO);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder().accessCode(authCallback.getCode()).build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doPostAuthorizationCode(authToken.getAccessCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error")) {
throw new AuthException(accessTokenObject.getString("error_description"));
}
authToken.setAccessToken(accessTokenObject.getString("access_token"));
authToken.setRefreshToken(accessTokenObject.getString("refresh_token"));
authToken.setExpireIn(accessTokenObject.getIntValue("expires_in"));
authToken.setUid(accessTokenObject.getString("taobao_user_id"));
authToken.setOpenId(accessTokenObject.getString("taobao_open_uid"));
String nick = GlobalAuthUtil.urlDecode(accessTokenObject.getString("taobao_user_nick"));
return AuthUser.builder()
.uuid(accessTokenObject.getString("taobao_user_id"))
.username(nick)
.nickname(nick)
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.queryParam("view", "web")
.build();
}
}
@@ -0,0 +1,105 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
/**
* Teambition授权登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthTeambitionRequest extends AuthDefaultRequest {
public AuthTeambitionRequest(AuthConfig config) {
super(config, AuthSource.TEAMBITION);
}
/**
* @param authCallback 回调返回的参数
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = HttpRequest.post(source.accessToken())
.form("client_id", config.getClientId())
.form("client_secret", config.getClientSecret())
.form("code", authCallback.getCode())
.form("grant_type", "code")
.execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(source.userInfo())
.header("Authorization", "OAuth2 " + accessToken)
.execute();
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
authToken.setUid(object.getString("_id"));
return AuthUser.builder()
.uuid(object.getString("_id"))
.username(object.getString("name"))
.nickname(object.getString("name"))
.avatar(object.getString("avatarUrl"))
.blog(object.getString("website"))
.location(object.getString("location"))
.email(object.getString("email"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
String uid = oldToken.getUid();
String refreshToken = oldToken.getRefreshToken();
HttpResponse response = HttpRequest.post(source.refresh())
.form("_userId", uid)
.form("refresh_token", refreshToken)
.execute();
JSONObject refreshTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(refreshTokenObject);
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(refreshTokenObject.getString("access_token"))
.refreshToken(refreshTokenObject.getString("refresh_token"))
.build())
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if ((object.containsKey("message") && object.containsKey("name"))) {
throw new AuthException(object.getString("name") + ", " + object.getString("message"));
}
}
}
@@ -1,59 +1,88 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 腾讯云登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/23 15:48
* @since 1.8
*/
public class AuthTencentCloudRequest extends BaseAuthRequest {
public class AuthTencentCloudRequest extends AuthDefaultRequest {
public AuthTencentCloudRequest(AuthConfig config) {
super(config, AuthSource.TENCEN_CLOUD);
super(config, AuthSource.TENCENT_CLOUD);
}
@Override
protected String getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getTencentCloudAccessTokenUrl(config.getClientId(), config.getClientSecret(), code);
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
JSONObject object = JSONObject.parseObject(response.body());
if (object.getIntValue("code") != 0) {
throw new AuthException("Unable to get token from tencent cloud using code [" + code + "]: " + object.get("msg"));
}
return object.getString("access_token");
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.build();
}
@Override
protected AuthUser getUserInfo(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getTencentCloudUserInfoUrl(accessToken)).execute();
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
object = object.getJSONObject("data");
return AuthUser.builder()
.uuid(object.getString("id"))
.username(object.getString("name"))
.avatar("https://dev.tencent.com/" + object.getString("avatar"))
.blog("https://dev.tencent.com/" + object.getString("path"))
.nickname(object.getString("name"))
.company(object.getString("company"))
.location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.email(object.getString("email"))
.remark(object.getString("slogan"))
.token(authToken)
.source(source)
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.getIntValue("code") != 0) {
throw new AuthException(object.getString("msg"));
}
object = object.getJSONObject("data");
return AuthUser.builder()
.username(object.getString("name"))
.avatar("https://dev.tencent.com/" + object.getString("avatar"))
.blog("https://dev.tencent.com/" + object.getString("path"))
.nickname(object.getString("name"))
.company(object.getString("company"))
.location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.email(object.getString("email"))
.remark(object.getString("slogan"))
.accessToken(accessToken)
.source(AuthSource.TENCEN_CLOUD)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("scope", "user")
.queryParam("state", getRealState(config.getState()))
.build();
}
}
@@ -0,0 +1,125 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthToutiaoErrorCode;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 今日头条登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.5
* @since 1.5
*/
public class AuthToutiaoRequest extends AuthDefaultRequest {
public AuthToutiaoRequest(AuthConfig config) {
super(config, AuthSource.TOUTIAO);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.openId(accessTokenObject.getString("open_id"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse userResponse = doGetUserInfo(authToken);
JSONObject userProfile = JSONObject.parseObject(userResponse.body());
this.checkResponse(userProfile);
JSONObject user = userProfile.getJSONObject("data");
boolean isAnonymousUser = user.getIntValue("uid_type") == 14;
String anonymousUserName = "匿名用户";
return AuthUser.builder()
.uuid(user.getString("uid"))
.username(isAnonymousUser ? anonymousUserName : user.getString("screen_name"))
.nickname(isAnonymousUser ? anonymousUserName : user.getString("screen_name"))
.avatar(user.getString("avatar_url"))
.remark(user.getString("description"))
.gender(AuthUserGender.getRealGender(user.getString("gender")))
.token(authToken)
.source(source)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_key", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(config.getState()))
.queryParam("auth_only", 1)
.queryParam("display", 0)
.build();
}
/**
* 返回获取accessToken的url
*
* @param code 授权码
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("code", code)
.queryParam("client_key", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("grant_type", "authorization_code")
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("client_key", config.getClientId())
.queryParam("access_token", authToken.getAccessToken())
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error_code")) {
throw new AuthException(AuthToutiaoErrorCode.getErrorCode(object.getIntValue("error_code"))
.getDesc());
}
}
}
@@ -0,0 +1,163 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 微信登录
*
* @author yangkai.shen (https://xkcoding.com)
* @version 1.0
* @since 1.8
*/
public class AuthWeChatRequest extends AuthDefaultRequest {
public AuthWeChatRequest(AuthConfig config) {
super(config, AuthSource.WECHAT);
}
/**
* 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
*
* @param authCallback 回调返回的参数
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String openId = authToken.getOpenId();
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city"));
if (object.containsKey("unionid")) {
authToken.setUnionId(object.getString("unionid"));
}
return AuthUser.builder()
.username(object.getString("nickname"))
.nickname(object.getString("nickname"))
.avatar(object.getString("headimgurl"))
.location(location)
.uuid(openId)
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.token(authToken)
.source(source)
.build();
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("errcode")) {
throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg"));
}
}
/**
* 获取token,适用于获取access_token和刷新token
*
* @param accessTokenUrl 实际请求token的地址
* @return token对象
*/
private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.openId(accessTokenObject.getString("openid"))
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("appid", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("scope", "snsapi_login")
.queryParam("state", getRealState(config.getState()).concat("#wechat_redirect"))
.build();
}
/**
* 返回获取accessToken的url
*
* @param code 授权码
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("code", code)
.queryParam("appid", config.getClientId())
.queryParam("secret", config.getClientSecret())
.queryParam("grant_type", "authorization_code")
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("openid", authToken.getOpenId())
.queryParam("lang", "zh_CN")
.build();
}
/**
* 返回获取userInfo的url
*
* @param refreshToken getAccessToken方法返回的refreshToken
* @return 返回获取userInfo的url
*/
@Override
protected String refreshTokenUrl(String refreshToken) {
return UrlBuilder.fromBaseUrl(source.refresh())
.queryParam("appid", config.getClientId())
.queryParam("refresh_token", refreshToken)
.queryParam("grant_type", "refresh_token")
.build();
}
}
@@ -4,61 +4,86 @@ import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.IpUtils;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 微博登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 16:31
* @since 1.8
*/
public class AuthWeiboRequest extends BaseAuthRequest {
public class AuthWeiboRequest extends AuthDefaultRequest {
public AuthWeiboRequest(AuthConfig config) {
super(config, AuthSource.WEIBO);
}
@Override
protected String getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getWeiboAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
String accessTokenStr = response.body();
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
if (accessTokenObject.containsKey("error")) {
throw new AuthException("Unable to get token from gitee using code [" + code + "]");
throw new AuthException(accessTokenObject.getString("error_description"));
}
String accessToken = accessTokenObject.getString("access_token");
String uid = accessTokenObject.getString("uid");
return String.format("uid=%s&access_token=%s", uid, accessToken);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.uid(accessTokenObject.getString("uid"))
.openId(accessTokenObject.getString("uid"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.build();
}
@Override
protected AuthUser getUserInfo(String accessToken) {
HttpResponse response = HttpRequest.get(UrlBuilder.getWeiboUserInfoUrl(accessToken))
.header("Authorization", "OAuth2 " + accessToken)
.header("API-RemoteIP", IpUtils.getIp())
.execute();
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
String uid = authToken.getUid();
String oauthParam = String.format("uid=%s&access_token=%s", uid, accessToken);
HttpResponse response = HttpRequest.get(userInfoUrl(authToken))
.header("Authorization", "OAuth2 " + oauthParam)
.header("API-RemoteIP", IpUtils.getIp())
.execute();
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
if (object.containsKey("error")) {
throw new AuthException(object.getString("error"));
}
return AuthUser.builder()
.username(object.getString("name"))
.avatar(object.getString("profile_image_url"))
.blog(StringUtils.isEmpty(object.getString("url")) ? "https://weibo.com/" + object.getString("profile_url") : object.getString("url"))
.nickname(object.getString("screen_name"))
.location(object.getString("location"))
.remark(object.getString("description"))
.gender(AuthUserGender.getRealGender(object.getString("gender")))
.accessToken(GlobalAuthUtil.parseStringToMap(accessToken).get("access_token"))
.source(AuthSource.WEIBO)
.build();
.uuid(object.getString("id"))
.username(object.getString("name"))
.avatar(object.getString("profile_image_url"))
.blog(StringUtils.isEmpty(object.getString("url")) ? "https://weibo.com/" + object.getString("profile_url") : object
.getString("url"))
.nickname(object.getString("screen_name"))
.location(object.getString("location"))
.remark(object.getString("description"))
.gender(AuthUserGender.getRealGender(object.getString("gender")))
.token(authToken)
.source(source)
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken authToken
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("uid", authToken.getUid())
.build();
}
}
@@ -1,85 +0,0 @@
package me.zhyd.oauth.request;
import lombok.Data;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthSource;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.AuthConfigChecker;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/18 13:11
* @since 1.8
*/
@Data
public abstract class BaseAuthRequest implements AuthRequest {
protected AuthConfig config;
protected AuthSource source;
public BaseAuthRequest(AuthConfig config, AuthSource source) {
this.config = config;
this.source = source;
if (!AuthConfigChecker.isSupportedAuth(config)) {
throw new AuthException(ResponseStatus.PARAMETER_INCOMPLETE);
}
}
protected abstract String getAccessToken(String code);
protected abstract AuthUser getUserInfo(String accessToken);
@Override
public AuthResponse login(String code) {
return AuthResponse.builder()
.data(this.getUserInfo(this.getAccessToken(code)))
.build();
}
@Override
public String authorize() {
String authorizeUrl = null;
switch (source) {
case WEIBO:
authorizeUrl = UrlBuilder.getWeiboAuthorizeUrl(config.getClientId(), config.getRedirectUri());
break;
case BAIDU:
authorizeUrl = UrlBuilder.getBaiduAuthorizeUrl(config.getClientId(), config.getRedirectUri());
break;
case DINGTALK:
authorizeUrl = UrlBuilder.getDingTalkQrConnectUrl(config.getClientId(), config.getRedirectUri());
break;
case GITEE:
authorizeUrl = UrlBuilder.getGiteeAuthorizeUrl(config.getClientId(), config.getRedirectUri());
break;
case GITHUB:
authorizeUrl = UrlBuilder.getGithubAuthorizeUrl(config.getClientId(), config.getRedirectUri());
break;
case CSDN:
authorizeUrl = UrlBuilder.getCsdnAuthorizeUrl(config.getClientId(), config.getRedirectUri());
break;
case CODING:
authorizeUrl = UrlBuilder.getCodingAuthorizeUrl(config.getClientId(), config.getRedirectUri());
break;
case TENCEN_CLOUD:
authorizeUrl = UrlBuilder.getTencentCloudAuthorizeUrl(config.getClientId(), config.getRedirectUri());
break;
case OSCHINA:
authorizeUrl = UrlBuilder.getOschinaAuthorizeUrl(config.getClientId(), config.getRedirectUri());
break;
case QQ:
break;
case WECHAT:
break;
case GOOGLE:
break;
default:
break;
}
return authorizeUrl;
}
}
@@ -1,34 +0,0 @@
package me.zhyd.oauth.request;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 16:37
* @since 1.8
*/
public enum ResponseStatus {
SUCCESS(2000, "Success"),
FAILURE(5000, "Failure"),
NOT_IMPLEMENTED(5001, "Not Implemented"),
PARAMETER_INCOMPLETE(5002, "Parameter incomplete"),
UNSUPPORTED(5003, "Unsupported operation"),
;
private int code;
private String msg;
ResponseStatus(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
@@ -0,0 +1,87 @@
package me.zhyd.oauth.utils;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthResponseStatus;
/**
* 授权配置类的校验器
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthChecker {
/**
* 是否支持第三方登录
*
* @param config config
* @param source source
* @return true or false
*/
public static boolean isSupportedAuth(AuthConfig config, AuthSource source) {
boolean isSupported = StringUtils.isNotEmpty(config.getClientId()) && StringUtils.isNotEmpty(config.getClientSecret()) && StringUtils.isNotEmpty(config.getRedirectUri());
if (isSupported && AuthSource.ALIPAY == source) {
isSupported = StringUtils.isNotEmpty(config.getAlipayPublicKey());
}
if (isSupported && AuthSource.STACK_OVERFLOW == source) {
isSupported = StringUtils.isNotEmpty(config.getStackOverflowKey());
}
return isSupported;
}
/**
* 检查配置合法性。针对部分平台, 对redirect uri有特定要求。一般来说redirect uri都是http://,而对于facebook平台, redirect uri 必须是https的链接
*
* @param config config
* @param source source
*/
public static void checkConfig(AuthConfig config, AuthSource source) {
String redirectUri = config.getRedirectUri();
if (!GlobalAuthUtil.isHttpProtocol(redirectUri) && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI);
}
// facebook的回调地址必须为https的链接
if (AuthSource.FACEBOOK == source && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI);
}
// 支付宝在创建回调地址时,不允许使用localhost或者127.0.0.1
if (AuthSource.ALIPAY == source && GlobalAuthUtil.isLocalHost(redirectUri)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI);
}
}
/**
* 校验回调传回的code
*
* @param code 回调时传回的code
*/
public static void checkCode(String code) {
if (StringUtils.isEmpty(code)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_CODE);
}
}
/**
* 校验state的合法性防止被CSRF
*
* @param newState 新的state,一般为回调时传回的state(可能被篡改)
* @param originalState 原始的state,发起授权时向第三方平台传递的state
*/
public static void checkState(String newState, String originalState) {
// 如果原始state为空,表示当前平台未使用state
if (StringUtils.isEmpty(originalState)) {
return;
}
// 如果授权之前使用了state,但是回调时未返回state,则表示当前请求为非法的请求,可能正在被CSRF攻击
if (StringUtils.isEmpty(newState)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST);
}
// 如果授权前后的state不一致,则表示当前请求为非法的请求,新的state可能为伪造
if (!newState.equals(originalState)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST);
}
}
}
@@ -1,22 +0,0 @@
package me.zhyd.oauth.utils;
import me.zhyd.oauth.config.AuthConfig;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 15:46
* @since 1.8
*/
public class AuthConfigChecker {
/**
* 是否支持第三方登录
*
* @return true or false
*/
public static boolean isSupportedAuth(AuthConfig config) {
return StringUtils.isNotEmpty(config.getClientId()) && StringUtils.isNotEmpty(config.getClientSecret()) && StringUtils.isNotEmpty(config.getRedirectUri());
}
}
@@ -0,0 +1,185 @@
package me.zhyd.oauth.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthResponseStatus;
import java.nio.charset.Charset;
import java.util.concurrent.ConcurrentHashMap;
/**
* state工具,负责创建、获取和删除state
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
@Slf4j
public class AuthState {
/**
* 空字符串
*/
private static final String EMPTY_STR = "";
/**
* state存储器
*/
private static ConcurrentHashMap<String, String> stateBucket = new ConcurrentHashMap<>();
/**
* 生成随机的state
*
* @param source oauth平台
* @return state
*/
public static String create(AuthSource source) {
return create(source.name());
}
/**
* 生成随机的state
*
* @param source oauth平台
* @return state
*/
public static String create(String source) {
return create(source, RandomUtil.randomString(4));
}
/**
* 创建state
*
* @param source oauth平台
* @param body 希望加密到state的消息体
* @return state
*/
public static String create(String source, Object body) {
return create(source, JSON.toJSONString(body));
}
/**
* 创建state
*
* @param source oauth平台
* @param body 希望加密到state的消息体
* @return state
*/
public static String create(String source, String body) {
String currentIp = getCurrentIp();
String simpleKey = ((source + currentIp));
String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
log.debug("Create the state: ip={}, platform={}, simpleKey={}, key={}, body={}", currentIp, source, simpleKey, key, body);
if (stateBucket.containsKey(key)) {
log.debug("Get from bucket: {}", stateBucket.get(key));
return stateBucket.get(key);
}
String simpleState = source + "_" + currentIp + "_" + body;
String state = Base64.encode(simpleState.getBytes(Charset.forName("UTF-8")));
log.debug("Create a new state: {}", state, simpleState);
stateBucket.put(key, state);
return state;
}
/**
* 获取state
*
* @param source oauth平台
* @return state
*/
public static String get(String source) {
String currentIp = getCurrentIp();
String simpleKey = ((source + currentIp));
String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
log.debug("Get state by the key[{}], current ip[{}]", key, currentIp);
return stateBucket.get(key);
}
/**
* 获取state中保存的body内容
*
* @param source oauth平台
* @param state 加密后的state
* @param clazz body的实际类型
* @param <T> 需要转换的具体的class类型
* @return state
*/
public static <T> T getBody(String source, String state, Class<T> clazz) {
if (StringUtils.isEmpty(state) || null == clazz) {
return null;
}
log.debug("Get body from the state[{}] of the {} and convert it to {}", state, source, clazz.toString());
String currentIp = getCurrentIp();
String decodedState = Base64.decodeStr(state);
log.debug("The decoded state is [{}]", decodedState);
if (!decodedState.startsWith(source)) {
return null;
}
String noneSourceState = decodedState.substring(source.length() + 1);
if (!noneSourceState.startsWith(currentIp)) {
// ip不相同,可能为非法的请求
throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST);
}
String body = noneSourceState.substring(currentIp.length() + 1);
log.debug("body is [{}]", body);
if (clazz == String.class) {
return (T) body;
}
if (clazz == Integer.class) {
return (T) Integer.valueOf(Integer.parseInt(body));
}
if (clazz == Long.class) {
return (T) Long.valueOf(Long.parseLong(body));
}
if (clazz == Short.class) {
return (T) Short.valueOf(Short.parseShort(body));
}
if (clazz == Double.class) {
return (T) Double.valueOf(Double.parseDouble(body));
}
if (clazz == Float.class) {
return (T) Float.valueOf(Float.parseFloat(body));
}
if (clazz == Boolean.class) {
return (T) Boolean.valueOf(Boolean.parseBoolean(body));
}
if (clazz == Byte.class) {
return (T) Byte.valueOf(Byte.parseByte(body));
}
return JSON.parseObject(body, clazz);
}
/**
* 登录成功后,清除state
*
* @param source oauth平台
*/
public static void delete(String source) {
String currentIp = getCurrentIp();
String simpleKey = ((source + currentIp));
String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
log.debug("Delete used state[{}] by the key[{}], current ip[{}]", stateBucket.get(key), key, currentIp);
stateBucket.remove(key);
}
/**
* 登录成功后,清除state
*
* @param source oauth平台
*/
public static void delete(AuthSource source) {
delete(source.name());
}
private static String getCurrentIp() {
String currentIp = IpUtils.getIp();
return StringUtils.isEmpty(currentIp) ? EMPTY_STR : currentIp;
}
}
@@ -1,6 +1,9 @@
package me.zhyd.oauth.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import me.zhyd.oauth.exception.AuthException;
import javax.crypto.Mac;
@@ -8,23 +11,26 @@ import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
/**
* 全局的工具类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class GlobalAuthUtil {
private static final String DEFAULT_ENCODING = "UTF-8";
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
private static final String ALGORITHM = "HmacSHA256";
public static String generateDingTalkSignature(String canonicalString, String secret) {
try {
byte[] signData = sign(canonicalString.getBytes(DEFAULT_ENCODING), secret.getBytes(DEFAULT_ENCODING));
return urlEncode(new String(Base64.encode(signData, false)));
} catch (UnsupportedEncodingException ex) {
throw new AuthException("Unsupported algorithm: " + DEFAULT_ENCODING, ex);
}
public static String generateDingTalkSignature(String secretKey, String timestamp) {
byte[] signData = sign(secretKey.getBytes(DEFAULT_ENCODING), timestamp.getBytes(DEFAULT_ENCODING));
return urlEncode(new String(Base64.encode(signData, false)));
}
private static byte[] sign(byte[] key, byte[] data) {
@@ -39,15 +45,14 @@ public class GlobalAuthUtil {
}
}
private static String urlEncode(String value) {
public static String urlEncode(String value) {
if (value == null) {
return "";
}
try {
String encoded = URLEncoder.encode(value, GlobalAuthUtil.DEFAULT_ENCODING);
return encoded.replace("+", "%20").replace("*", "%2A")
.replace("~", "%7E").replace("/", "%2F");
String encoded = URLEncoder.encode(value, GlobalAuthUtil.DEFAULT_ENCODING.displayName());
return encoded.replace("+", "%20").replace("*", "%2A").replace("~", "%7E").replace("/", "%2F");
} catch (UnsupportedEncodingException e) {
throw new AuthException("Failed To Encode Uri", e);
}
@@ -58,7 +63,7 @@ public class GlobalAuthUtil {
return "";
}
try {
return URLDecoder.decode(value, GlobalAuthUtil.DEFAULT_ENCODING);
return URLDecoder.decode(value, GlobalAuthUtil.DEFAULT_ENCODING.displayName());
} catch (UnsupportedEncodingException e) {
throw new AuthException("Failed To Decode Uri", e);
}
@@ -77,4 +82,43 @@ public class GlobalAuthUtil {
}
return res;
}
public static String parseMapToString(Map<String, Object> params, boolean encode) {
List<String> paramList = new ArrayList<>();
params.forEach((k, v) -> {
if (ObjectUtil.isNull(v)) {
paramList.add(k + "=");
} else {
String valueString = v.toString();
paramList.add(k + "=" + (encode ? urlEncode(valueString) : valueString));
}
});
return CollUtil.join(paramList, "&");
}
public static Map<String, Object> parseQueryToMap(String url) {
Map<String, Object> paramMap = new HashMap<>();
HttpUtil.decodeParamMap(url, "UTF-8").forEach(paramMap::put);
return paramMap;
}
public static boolean isHttpProtocol(String url) {
if (StringUtils.isEmpty(url)) {
return false;
}
return url.startsWith("http://");
}
public static boolean isHttpsProtocol(String url) {
if (StringUtils.isEmpty(url)) {
return false;
}
return url.startsWith("https://");
}
public static boolean isLocalHost(String url) {
return StringUtils.isEmpty(url) || url.contains("127.0.0.1") || url.contains("localhost");
}
}
@@ -8,14 +8,14 @@ import java.net.UnknownHostException;
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 16:31
* @since 1.0
*/
public class IpUtils {
/**
* 获取IP
*
* @return ip
*/
public static String getIp() {
try {
@@ -3,8 +3,6 @@ package me.zhyd.oauth.utils;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 15:48
* @since 1.8
*/
public class StringUtils {
+46 -323
View File
@@ -1,356 +1,79 @@
package me.zhyd.oauth.utils;
import me.zhyd.oauth.consts.ApiUrl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Setter;
import java.text.MessageFormat;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Url构建工具类
* <p>
* 构造URL
* </p>
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @author yangkai.shen (https://xkcoding.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/1/31 12:26
* @since 1.0
* @since 1.8
*/
@Setter
public class UrlBuilder {
private static final String GITHUB_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}";
private static final String GITHUB_USER_INFO_PATTERN = "{0}?access_token={1}";
private static final String GITHUB_AUTHORIZE_PATTERN = "{0}?client_id={1}&state=1&redirect_uri={2}";
private final Map<String, Object> params = new LinkedHashMap<>(7);
private String baseUrl;
private static final String WEIBO_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}";
private static final String WEIBO_USER_INFO_PATTERN = "{0}?{1}";
private static final String WEIBO_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}";
private UrlBuilder() {
private static final String GITEE_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}";
private static final String GITEE_USER_INFO_PATTERN = "{0}?access_token={1}";
private static final String GITEE_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}";
private static final String DING_TALK_QRCONNECT_PATTERN = "{0}?appid={1}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri={2}";
private static final String DING_TALK_USER_INFO_PATTERN = "{0}?signature={1}&timestamp={2}&accessKey={3}";
private static final String BAIDU_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}";
private static final String BAIDU_USER_INFO_PATTERN = "{0}?access_token={1}";
private static final String BAIDU_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&display=popup";
private static final String BAIDU_REVOKE_PATTERN = "{0}?access_token={1}";
private static final String CSDN_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}";
private static final String CSDN_USER_INFO_PATTERN = "{0}?access_token={1}";
private static final String CSDN_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}";
private static final String CODING_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}";
private static final String CODING_USER_INFO_PATTERN = "{0}?access_token={1}";
private static final String CODING_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&scope=user";
private static final String TENCENT_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}";
private static final String TENCENT_USER_INFO_PATTERN = "{0}?access_token={1}";
private static final String TENCENT_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&scope=user";
private static final String OSCHINA_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}&dataType=json";
private static final String OSCHINA_USER_INFO_PATTERN = "{0}?access_token={1}&dataType=json";
private static final String OSCHINA_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}";
/**
* 获取githubtoken的接口地址
*
* @param clientId github应用的Client ID
* @param clientSecret github应用的Client Secret
* @param code github授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getGithubAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(GITHUB_ACCESS_TOKEN_PATTERN, ApiUrl.GITHUB.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取github用户详情的接口地址
*
* @param token github 应用的token
* @return full url
* @param baseUrl 基础路径
* @return the new {@code UrlBuilder}
*/
public static String getGithubUserInfoUrl(String token) {
return MessageFormat.format(GITHUB_USER_INFO_PATTERN, ApiUrl.GITHUB.userInfo(), token);
public static UrlBuilder fromBaseUrl(String baseUrl) {
UrlBuilder builder = new UrlBuilder();
builder.setBaseUrl(baseUrl);
return builder;
}
/**
* 获取github授权地址
* 添加参数
*
* @param clientId github 应用的Client ID
* @param redirectUrl github 应用授权成功后的回调地址
* @return full url
* @param key 参数名称
* @param value 参数值
* @return this UrlBuilder
*/
public static String getGithubAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(GITHUB_AUTHORIZE_PATTERN, ApiUrl.GITHUB.authorize(), clientId, redirectUrl);
public UrlBuilder queryParam(String key, Object value) {
Assert.notBlank(key, "参数名不能为空");
String valueAsString = (value != null ? value.toString() : null);
this.params.put(key, valueAsString);
return this;
}
/**
* 获取weibo token的接口地址
* 构造url
*
* @param clientId weibo应用的App Key
* @param clientSecret weibo应用的App Secret
* @param code weibo授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
* @return url
*/
public static String getWeiboAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(WEIBO_ACCESS_TOKEN_PATTERN, ApiUrl.WEIBO.accessToken(), clientId, clientSecret, code, redirectUri);
public String build() {
return this.build(false);
}
/**
* 获取weibo用户详情的接口地址
* 构造url
*
* @param token weibo 应用的token
* @return full url
* @param encode 转码
* @return url
*/
public static String getWeiboUserInfoUrl(String token) {
return MessageFormat.format(WEIBO_USER_INFO_PATTERN, ApiUrl.WEIBO.userInfo(), token);
}
/**
* 获取weibo授权地址
*
* @param clientId weibo 应用的Client ID
* @param redirectUrl weibo 应用授权成功后的回调地址
* @return full url
*/
public static String getWeiboAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(WEIBO_AUTHORIZE_PATTERN, ApiUrl.WEIBO.authorize(), clientId, redirectUrl);
}
/**
* 获取gitee token的接口地址
*
* @param clientId gitee应用的Client ID
* @param clientSecret gitee应用的Client Secret
* @param code gitee授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getGiteeAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(GITEE_ACCESS_TOKEN_PATTERN, ApiUrl.GITEE.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取gitee用户详情的接口地址
*
* @param token gitee 应用的token
* @return full url
*/
public static String getGiteeUserInfoUrl(String token) {
return MessageFormat.format(GITEE_USER_INFO_PATTERN, ApiUrl.GITEE.userInfo(), token);
}
/**
* 获取gitee授权地址
*
* @param clientId gitee 应用的Client ID
* @param redirectUrl gitee 应用授权成功后的回调地址
* @return json
*/
public static String getGiteeAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(GITEE_AUTHORIZE_PATTERN, ApiUrl.GITEE.authorize(), clientId, redirectUrl);
}
/**
* 获取钉钉登录二维码的地址
*
* @param clientId 钉钉 应用的App Id
* @param redirectUrl 钉钉 应用授权成功后的回调地址
* @return full url
*/
public static String getDingTalkQrConnectUrl(String clientId, String redirectUrl) {
return MessageFormat.format(DING_TALK_QRCONNECT_PATTERN, ApiUrl.DINGTALK.authorize(), clientId, redirectUrl);
}
/**
* 获取钉钉用户信息的地址
*
* @param signature 通过appSecret计算出来的签名值,签名计算方法:https://open-doc.dingtalk.com/microapp/faquestions/hxs5v9
* @param timestamp 当前时间戳,单位是毫秒
* @param accessKey 钉钉 应用的App Id
* @return full url
*/
public static String getDingTalkUserInfoUrl(String signature, String timestamp, String accessKey) {
return MessageFormat.format(DING_TALK_USER_INFO_PATTERN, ApiUrl.DINGTALK.userInfo(), signature, timestamp, accessKey);
}
/**
* 获取baidu token的接口地址
*
* @param clientId baidu应用的API Key
* @param clientSecret baidu应用的Secret Key
* @param code baidu授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getBaiduAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(BAIDU_ACCESS_TOKEN_PATTERN, ApiUrl.BAIDU.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取baidu用户详情的接口地址
*
* @param token baidu 应用的token
* @return full url
*/
public static String getBaiduUserInfoUrl(String token) {
return MessageFormat.format(BAIDU_USER_INFO_PATTERN, ApiUrl.BAIDU.userInfo(), token);
}
/**
* 获取baidu授权地址
*
* @param clientId baidu 应用的API Key
* @param redirectUrl baidu 应用授权成功后的回调地址
* @return json
*/
public static String getBaiduAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(BAIDU_AUTHORIZE_PATTERN, ApiUrl.BAIDU.authorize(), clientId, redirectUrl);
}
/**
* 获取收回baidu授权的地址
*
* @param accessToken baidu授权登录后的token
* @return json
*/
public static String getBaiduRevokeUrl(String accessToken) {
return MessageFormat.format(BAIDU_REVOKE_PATTERN, ApiUrl.BAIDU.revoke(), accessToken);
}
/**
* 获取csdn token的接口地址
*
* @param clientId csdn应用的App Key
* @param clientSecret csdn应用的App Secret
* @param code csdn授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getCsdnAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(CSDN_ACCESS_TOKEN_PATTERN, ApiUrl.CSDN.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取csdn用户详情的接口地址
*
* @param token csdn 应用的token
* @return full url
*/
public static String getCsdnUserInfoUrl(String token) {
return MessageFormat.format(CSDN_USER_INFO_PATTERN, ApiUrl.CSDN.userInfo(), token);
}
/**
* 获取csdn授权地址
*
* @param clientId csdn 应用的Client ID
* @param redirectUrl csdn 应用授权成功后的回调地址
* @return full url
*/
public static String getCsdnAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(CSDN_AUTHORIZE_PATTERN, ApiUrl.CSDN.authorize(), clientId, redirectUrl);
}
/**
* 获取coding token的接口地址
*
* @param clientId coding应用的App Key
* @param clientSecret coding应用的App Secret
* @param code coding授权前的code,用来换token
* @return full url
*/
public static String getCodingAccessTokenUrl(String clientId, String clientSecret, String code) {
return MessageFormat.format(CODING_ACCESS_TOKEN_PATTERN, ApiUrl.CODING.accessToken(), clientId, clientSecret, code);
}
/**
* 获取coding用户详情的接口地址
*
* @param token coding 应用的token
* @return full url
*/
public static String getCodingUserInfoUrl(String token) {
return MessageFormat.format(CODING_USER_INFO_PATTERN, ApiUrl.CODING.userInfo(), token);
}
/**
* 获取coding授权地址
*
* @param clientId coding 应用的Client ID
* @param redirectUrl coding 应用授权成功后的回调地址
* @return full url
*/
public static String getCodingAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(CODING_AUTHORIZE_PATTERN, ApiUrl.CODING.authorize(), clientId, redirectUrl);
}
/**
* 获取腾讯云开发者平台 token的接口地址
*
* @param clientId coding应用的App Key
* @param clientSecret coding应用的App Secret
* @param code coding授权前的code,用来换token
* @return full url
*/
public static String getTencentCloudAccessTokenUrl(String clientId, String clientSecret, String code) {
return MessageFormat.format(TENCENT_ACCESS_TOKEN_PATTERN, ApiUrl.TENCENTCLOUD.accessToken(), clientId, clientSecret, code);
}
/**
* 获取腾讯云开发者平台用户详情的接口地址
*
* @param token coding 应用的token
* @return full url
*/
public static String getTencentCloudUserInfoUrl(String token) {
return MessageFormat.format(TENCENT_USER_INFO_PATTERN, ApiUrl.TENCENTCLOUD.userInfo(), token);
}
/**
* 获取腾讯云开发者平台授权地址
*
* @param clientId coding 应用的Client ID
* @param redirectUrl coding 应用授权成功后的回调地址
* @return full url
*/
public static String getTencentCloudAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(TENCENT_AUTHORIZE_PATTERN, ApiUrl.TENCENTCLOUD.authorize(), clientId, redirectUrl);
}
/**
* 获取oschina token的接口地址
*
* @param clientId oschina应用的App Key
* @param clientSecret oschina应用的App Secret
* @param code oschina授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getOschinaAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(OSCHINA_ACCESS_TOKEN_PATTERN, ApiUrl.OSCHINA.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取oschina用户详情的接口地址
*
* @param token oschina 应用的token
* @return full url
*/
public static String getOschinaUserInfoUrl(String token) {
return MessageFormat.format(OSCHINA_USER_INFO_PATTERN, ApiUrl.OSCHINA.userInfo(), token);
}
/**
* 获取oschina授权地址
*
* @param clientId oschina 应用的Client ID
* @param redirectUrl oschina 应用授权成功后的回调地址
* @return full url
*/
public static String getOschinaAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(OSCHINA_AUTHORIZE_PATTERN, ApiUrl.OSCHINA.authorize(), clientId, redirectUrl);
public String build(boolean encode) {
if (MapUtil.isEmpty(this.params)) {
return this.baseUrl;
}
String baseUrl = StrUtil.addSuffixIfNot(this.baseUrl, "?");
String paramString = GlobalAuthUtil.parseMapToString(this.params, encode);
return baseUrl + paramString;
}
}
+188 -26
View File
@@ -1,14 +1,14 @@
package me.zhyd.oauth;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.request.*;
import org.junit.Test;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/18 13:10
* @since 1.8
*/
public class AuthRequestTest {
@@ -19,11 +19,12 @@ public class AuthRequestTest {
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行调整
// 返回授权页面,可自行跳转
authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
authRequest.login(new AuthCallback());
}
@Test
@@ -32,11 +33,12 @@ public class AuthRequestTest {
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行调整
// 返回授权页面,可自行跳转
authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
authRequest.login(new AuthCallback());
}
@Test
@@ -46,10 +48,10 @@ public class AuthRequestTest {
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
// 返回授权页面,可自行跳转
authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
authRequest.login(new AuthCallback());
}
@Test
@@ -58,11 +60,12 @@ public class AuthRequestTest {
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行调整
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
authRequest.login(new AuthCallback());
}
@Test
@@ -71,11 +74,12 @@ public class AuthRequestTest {
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行调整
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
authRequest.login(new AuthCallback());
}
@Test
@@ -84,11 +88,12 @@ public class AuthRequestTest {
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行调整
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
authRequest.login(new AuthCallback());
}
@Test
@@ -97,11 +102,12 @@ public class AuthRequestTest {
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行调整
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
authRequest.login(new AuthCallback());
}
@Test
@@ -110,10 +116,166 @@ public class AuthRequestTest {
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行调整
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
authRequest.login(new AuthCallback());
}
@Test
public void alipayTest() {
AuthRequest authRequest = new AuthAlipayRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.alipayPublicKey("publicKey")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void qqTest() {
AuthRequest authRequest = new AuthQqRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void wechatTest() {
AuthRequest authRequest = new AuthWeChatRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void taobaoTest() {
AuthRequest authRequest = new AuthTaobaoRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void googleTest() {
AuthRequest authRequest = new AuthGoogleRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void facebookTest() {
AuthRequest authRequest = new AuthFacebookRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void douyinTest() {
AuthRequest authRequest = new AuthDouyinRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void linkedinTest() {
AuthRequest authRequest = new AuthLinkedinRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void microsoftTest() {
AuthRequest authRequest = new AuthMicrosoftRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void miTest() {
AuthRequest authRequest = new AuthMiRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void toutiaoTest() {
AuthRequest authRequest = new AuthToutiaoRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state")
.build());
// 返回授权页面,可自行跳转
String url = authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
AuthResponse login = authRequest.login(new AuthCallback());
}
}
@@ -0,0 +1,231 @@
package me.zhyd.oauth.utils;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import me.zhyd.oauth.config.AuthConfig;
import org.junit.Assert;
import org.junit.Test;
import java.util.*;
public class AuthStateTest {
/**
* step1 生成state: 预期创建一个新的state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
*
* step2 重复生成state: 预期从bucket中返回一个可用的state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
*
* step3 获取state: 预期获取上面生成的state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
*
* step4 删除state: 预期删除掉上面创建的state...
*
* step5 重新获取state: 预期返回null...
* null
*/
@Test
public void usage() {
String source = "github";
System.out.println("\nstep1 生成state: 预期创建一个新的state...");
String state = AuthState.create(source);
System.out.println(state);
System.out.println("\nstep2 重复生成state: 预期从bucket中返回一个可用的state...");
String recreateState = AuthState.create(source);
System.out.println(recreateState);
Assert.assertEquals(state, recreateState);
System.out.println("\nstep3 获取state: 预期获取上面生成的state...");
String stateByBucket = AuthState.get(source);
System.out.println(stateByBucket);
Assert.assertEquals(state, stateByBucket);
System.out.println("\nstep4 删除state: 预期删除掉上面创建的state...");
AuthState.delete(source);
System.out.println("\nstep5 重新获取state: 预期返回null...");
String deletedState = AuthState.get(source);
System.out.println(deletedState);
Assert.assertNull(deletedState);
}
/**
* 通过随机字符串生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9wdnAy
*
* 通过传入自定义的字符串生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV/ov5nmmK/kuIDkuKrlrZfnrKbkuLI=
*
* 通过传入数字生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV8xMTE=
*
* 通过传入日期生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV8xNTQ2MzE1OTMyMDAw
*
* 通过传入map生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV97InVzZXJUb2tlbiI6Inh4eHh4IiwidXNlcklkIjoxfQ==
*
* 通过传入List生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9bInh4eHgiLCJ4eHh4eHh4eCJd
*
* 通过传入实体类生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV97ImNsaWVudElkIjoieHh4eHgiLCJjbGllbnRTZWNyZXQiOiJ4eHh4eCIsInVuaW9uSWQiOmZhbHNlfQ==
*/
@Test
public void create() {
String source = "github";
System.out.println("\n通过随机字符串生成state...");
String state = AuthState.create(source);
System.out.println(state);
AuthState.delete(source);
System.out.println("\n通过传入自定义的字符串生成state...");
String stringBody = "这是一个字符串";
String stringState = AuthState.create(source, stringBody);
System.out.println(stringState);
AuthState.delete(source);
System.out.println("\n通过传入数字生成state...");
Integer numberBody = 111;
String numberState = AuthState.create(source, numberBody);
System.out.println(numberState);
AuthState.delete(source);
System.out.println("\n通过传入日期生成state...");
Date dateBody = DateUtil.parse("2019-01-01 12:12:12", DatePattern.NORM_DATETIME_PATTERN);
String dateState = AuthState.create(source, dateBody);
System.out.println(dateState);
AuthState.delete(source);
System.out.println("\n通过传入map生成state...");
Map<String, Object> mapBody = new HashMap<>();
mapBody.put("userId", 1);
mapBody.put("userToken", "xxxxx");
String mapState = AuthState.create(source, mapBody);
System.out.println(mapState);
AuthState.delete(source);
System.out.println("\n通过传入List生成state...");
List<String> listBody = new ArrayList<>();
listBody.add("xxxx");
listBody.add("xxxxxxxx");
String listState = AuthState.create(source, listBody);
System.out.println(listState);
AuthState.delete(source);
System.out.println("\n通过传入实体类生成state...");
AuthConfig entityBody = AuthConfig.builder()
.clientId("xxxxx")
.clientSecret("xxxxx")
.build();
String entityState = AuthState.create(source, entityBody);
System.out.println(entityState);
AuthState.delete(source);
}
/**
* 通过随机字符串生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9kaWNn
* dicg
*
* 通过传入自定义的字符串生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV/ov5nmmK/kuIDkuKrlrZfnrKbkuLI=
* 这是一个字符串
*
* 通过传入数字生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV8xMTE=
* 111
*
* 通过传入日期生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV8xNTQ2MzE1OTMyMDAw
* Tue Jan 01 12:12:12 CST 2019
*
* 通过传入map生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV97InVzZXJUb2tlbiI6Inh4eHh4IiwidXNlcklkIjoxfQ==
* {userToken=xxxxx, userId=1}
*
* 通过传入List生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9bInh4eHgiLCJ4eHh4eHh4eCJd
* [xxxx, xxxxxxxx]
*
* 通过传入实体类生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV97ImNsaWVudElkIjoieHh4eHgiLCJjbGllbnRTZWNyZXQiOiJ4eHh4eCIsInVuaW9uSWQiOmZhbHNlfQ==
* me.zhyd.oauth.config.AuthConfig@725bef66
*/
@Test
public void getBody() {
String source = "github";
System.out.println("\n通过随机字符串生成state...");
String state = AuthState.create(source);
System.out.println(state);
String body = AuthState.getBody(source, state, String.class);
System.out.println(body);
AuthState.delete(source);
System.out.println("\n通过传入自定义的字符串生成state...");
String stringBody = "这是一个字符串";
String stringState = AuthState.create(source, stringBody);
System.out.println(stringState);
stringBody = AuthState.getBody(source, stringState, String.class);
System.out.println(stringBody);
AuthState.delete(source);
System.out.println("\n通过传入数字生成state...");
Integer numberBody = 111;
String numberState = AuthState.create(source, numberBody);
System.out.println(numberState);
numberBody = AuthState.getBody(source, numberState, Integer.class);
System.out.println(numberBody);
AuthState.delete(source);
System.out.println("\n通过传入日期生成state...");
Date dateBody = DateUtil.parse("2019-01-01 12:12:12", DatePattern.NORM_DATETIME_PATTERN);
String dateState = AuthState.create(source, dateBody);
System.out.println(dateState);
dateBody = AuthState.getBody(source, dateState, Date.class);
System.out.println(dateBody);
AuthState.delete(source);
System.out.println("\n通过传入map生成state...");
Map<String, Object> mapBody = new HashMap<>();
mapBody.put("userId", 1);
mapBody.put("userToken", "xxxxx");
String mapState = AuthState.create(source, mapBody);
System.out.println(mapState);
mapBody = AuthState.getBody(source, mapState, Map.class);
System.out.println(mapBody);
AuthState.delete(source);
System.out.println("\n通过传入List生成state...");
List<String> listBody = new ArrayList<>();
listBody.add("xxxx");
listBody.add("xxxxxxxx");
String listState = AuthState.create(source, listBody);
System.out.println(listState);
listBody = AuthState.getBody(source, listState, List.class);
System.out.println(listBody);
AuthState.delete(source);
System.out.println("\n通过传入实体类生成state...");
AuthConfig entityBody = AuthConfig.builder()
.clientId("xxxxx")
.clientSecret("xxxxx")
.build();
String entityState = AuthState.create(source, entityBody);
System.out.println(entityState);
entityBody = AuthState.getBody(source, entityState, AuthConfig.class);
System.out.println(entityBody);
AuthState.delete(source);
}
@Test
public void getErrorStateBody() {
String source = "github";
String state = "1111111111111111111111111111111";
String body = AuthState.getBody(source, state, String.class);
System.out.println(body);
AuthState.delete(source);
}
}
@@ -0,0 +1,90 @@
package me.zhyd.oauth.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/7/19 15:52
* @since 1.8
*/
public class CustomTest {
/**
* 1000000: 23135ms
* 100000: 3016ms
* 10000: 328ms
* 1000: 26ms
*/
@Test
public void test() {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
callMethod();
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
/**
* 1000000: 19058ms
* 100000: 2772ms
* 10000: 323ms
* 1000: 29ms
*/
@Test
public void test2() {
long end = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
callMethod2();
}
long end2 = System.currentTimeMillis();
System.out.println((end2 - end) + "ms");
}
public String callMethod() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// for (StackTraceElement stackTraceElement : stackTrace) {
// System.out.println(stackTraceElement.getMethodName());
// }
return stackTrace[2].getMethodName();
}
public String callMethod2() {
StackTraceElement[] stackTrace = (new Throwable()).getStackTrace();
// for (StackTraceElement stackTraceElement : stackTrace) {
// System.out.println(stackTraceElement.getMethodName());
// }
return stackTrace[2].getMethodName();
}
@Test
public void jsonpath(){
List<Map<String, Map<String, Object>>> list = new ArrayList<>();
Map<String, Map<String, Object>> map = new HashMap<>();
Map<String, Object> node = new HashMap<>();
node.put("emailAddress", "xxxx");
map.put("handle~", node);
list.add(map);
Map<String, Object> master = new HashMap<>();
// master.put("elements", list);
JSONObject emailObj = JSONObject.parseObject(JSON.toJSONString(master));
Object object = JSONPath.eval(emailObj, "$['elements'][0]['handle~']['emailAddress']");
System.out.println(object);
}
}
@@ -0,0 +1,61 @@
package me.zhyd.oauth.utils;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class GlobalAuthUtilTest {
@Test
public void testGenerateDingTalkSignature() {
Assert.assertEquals("mLTZEMqIlpAA3xtJ43KcRT0EDLwgSamFe%2FNis5lq9ik%3D",
GlobalAuthUtil.generateDingTalkSignature(
"SHA-256", "1562325753000 "));
}
@Test
public void testUrlDecode() {
Assert.assertEquals("", GlobalAuthUtil.urlDecode(null));
Assert.assertEquals("https://www.foo.bar",
GlobalAuthUtil.urlDecode("https://www.foo.bar"));
Assert.assertEquals("mLTZEMqIlpAA3xtJ43KcRT0EDLwgSamFe/Nis5lq9ik=",
GlobalAuthUtil.urlDecode(
"mLTZEMqIlpAA3xtJ43KcRT0EDLwgSamFe%2FNis5lq9ik%3D"));
}
@Test
public void testParseStringToMap() {
Map expected = new HashMap();
expected.put("bar", "baz");
Assert.assertEquals(expected,
GlobalAuthUtil.parseStringToMap("foo&bar=baz"));
}
@Test
public void testIsHttpProtocol() {
Assert.assertFalse(GlobalAuthUtil.isHttpProtocol(""));
Assert.assertFalse(GlobalAuthUtil.isHttpProtocol("foo"));
Assert.assertTrue(GlobalAuthUtil.isHttpProtocol("http://www.foo.bar"));
}
@Test
public void testIsHttpsProtocol() {
Assert.assertFalse(GlobalAuthUtil.isHttpsProtocol(""));
Assert.assertFalse(GlobalAuthUtil.isHttpsProtocol("foo"));
Assert.assertTrue(
GlobalAuthUtil.isHttpsProtocol("https://www.foo.bar"));
}
@Test
public void testIsLocalHost() {
Assert.assertFalse(GlobalAuthUtil.isLocalHost("foo"));
Assert.assertTrue(GlobalAuthUtil.isLocalHost(""));
Assert.assertTrue(GlobalAuthUtil.isLocalHost("127.0.0.1"));
Assert.assertTrue(GlobalAuthUtil.isLocalHost("localhost"));
}
}
@@ -0,0 +1,38 @@
package me.zhyd.oauth.utils;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.request.AuthWeChatRequest;
import org.junit.Assert;
import org.junit.Test;
/**
* <p>
* UrlBuilder测试类
* </p>
*
* @author yangkai.shen (https://xkcoding.com)
* @date Created in 2019-07-18 16:36
*/
public class UrlBuilderTest {
@Test
public void testUrlBuilder() {
AuthConfig config = AuthConfig.builder()
.clientId("appid-110110110")
.clientSecret("secret-110110110")
.redirectUri("https://xkcoding.com")
.state(AuthState.create(AuthSource.WECHAT))
.build();
String build = UrlBuilder.fromBaseUrl(AuthSource.WECHAT.authorize())
.queryParam("appid", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("response_type", "code")
.queryParam("scope", "snsapi_login")
.queryParam("state", config.getState().concat("#wechat_redirect"))
.build(false);
AuthWeChatRequest request = new AuthWeChatRequest(config);
String authorize = request.authorize();
Assert.assertEquals(build, authorize);
AuthState.delete(AuthSource.WECHAT);
}
}
+119
View File
@@ -0,0 +1,119 @@
### 2019/07/22 [v1.9.2](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.2)
1. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#26](https://github.com/zhangyd-c/JustAuth/pull/26)AuthConfig类添加lombok注解,方便 [justauth-spring-boot-starter](https://github.com/xkcoding/justauth-spring-boot-starter) 直接使用
### 2019/07/22 [v1.9.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.1)
1. 增加`stackoverflow`参数校验
2. 解决`Pinterest`获取用户失败的问题
3. 添加注释
### 2019/07/19 [v1.9.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.0)
1. 合并github上[@dyc12ii](https://github.com/dyc12ii) 的[pr#25](https://github.com/zhangyd-c/JustAuth/pull/25),升级fastjson版本至1.2.58,避免安全漏洞
2. `AuthUserGender`枚举类挪到`enums`包下
3. 删除`AuthBaiduErrorCode``AuthDingTalkErrorCode`枚举类
4. 优化百度授权流程,增加refresh token的方法
5. 优化`AuthConfig``AuthResponse`类,去掉不必要的lombonk注解,减少编译后的代码量
6. 使用lombok注解优化枚举类
7. `AuthQqRequest`增加refresh方法
8. 修复google登录无法获取用户信息的问题
9. 优化代码
### 2019/07/18
1. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#19](https://github.com/zhangyd-c/JustAuth/pull/19),集成人人
2. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#20](https://github.com/zhangyd-c/JustAuth/pull/20),集成Pinterest
3. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#21](https://github.com/zhangyd-c/JustAuth/pull/21),集成StackOverflow
4. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#23](https://github.com/zhangyd-c/JustAuth/pull/23),重构代码、新增编辑器规范,规范PR代码风格
### 2019/07/17
1. 优化代码
2. 集成Teambition登录
### 2019/07/16
1. 重构UrlBuilder类
2. 将CSDN相关的类置为`Deprecated`,后续可能会删除,也可能一直保留。毕竟CSDN的openAPI已经不对外开放了。
3. `BaseAuthRequest` 改名为 `AuthDefaultRequest`
4. `ResponseStatus` 改名为 `AuthResponseStatus` 并且移动到 `me.zhyd.oauth.model`
5. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#18](https://github.com/zhangyd-c/JustAuth/pull/18),修复小米回调错误问题 同时 支持微信获取
### 2019/07/15 [v1.8.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.8.1)
1. 新增 `AuthState` 类,内置默认的state生成规则和校验规则
### 2019/07/12
1. 合并[Braavos96](https://github.com/Braavos96)提交的[PR#16](https://github.com/zhangyd-c/JustAuth/pull/16)
### 2019/06/28 [v1.8.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.8.0)
1. 修复百度登录获取不到token失效时间的问题
2. 增加state参数校验,预防CSRF。**强烈建议启用state**
### 2019/06/27
1. 修复百度登录获取不到token失效时间的问题
2. 增加state参数校验,预防CSRF。**强烈建议启用state**
3. 修改login方法的参数为AuthCallback,封装回调返回的参数
4. 支持state参数
5. 增加code和state参数校验
由于state安全问题,1.8.0以前的版本都有隐藏的CSRF漏洞问题,所以强烈建议正在使用JustAuth的朋友升级到1.8.0版本!
### 2019/06/25 [v1.7.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.7.1)
qq授权登录时,需要获取`openId`作为`uuid`,在`1.6.1-beta``1.7.0`版本中,引入了`unionId`这一属性。获取`unionid`需要单独向qq团队**发送邮件**申请权限,鉴于这一申请权限的步骤比较麻烦(需要填写的内容比较多),所以在`AuthConfig`中增加了一个`unionId`属性,当为**true**时才会获取unionid,当为false时只获取openId。如果你需要该功能, 则在自行申请了相关权限后,将该属性置为true即可。关于unionId的参考链接:[UnionID介绍](http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D)
### 2019/06/19 [v1.7.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.7.0)
1. 合并[xkcoding](https://github.com/xkcoding)提交的[PR](https://github.com/zhangyd-c/JustAuth/pull/14),重构了部分代码,jar包由原来的`130+kb`优化到现在的`110+kb`
2. 合并[skqing](https://gitee.com/skqing)提交的[PR](https://gitee.com/yadong.zhang/JustAuth/pulls/3) 解决抖音登录失败问题
### 2019/06/18 [v1.6.1-beta](https://gitee.com/yadong.zhang/JustAuth/releases/v1.6.1-beta)
1. 解决Issue [#IY2HW](https://gitee.com/yadong.zhang/JustAuth/issues/IY2HW)
2. 解决Issue [#IY2OH](https://gitee.com/yadong.zhang/JustAuth/issues/IY2OH)
3. 解决Issue [#IY2FV](https://gitee.com/yadong.zhang/JustAuth/issues/IY2FV)
4. 修复部分注释、拼写错误
5. 解决Issue [#IY1QR](https://gitee.com/yadong.zhang/JustAuth/issues/IY1QR) 增加对Config属性的校验功能,主要校验redirect uri的合法性
6. 合并[skqing](https://gitee.com/skqing)提交的[PR](https://gitee.com/yadong.zhang/JustAuth/pulls/2),解决一些BUG
### 2019/06/06 [v1.6.0-beta](https://gitee.com/yadong.zhang/JustAuth/releases/v1.6.0-beta)
1. 增加今日头条的授权登陆
2. 发布1.6.0-beta版本,今日头条开发者暂时不能认证, 所以无法做测试,等测试通过后,正式发布release版本
### 2019/05/28 [v1.5.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.5.0)
1. 增加小米账号和微软的授权登陆
2. 发布1.5.0版本
### 2019/05/26 [v1.4.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.4.0)
1. 增加抖音和Linkedin的授权登陆
2. 修改部分图片命名
3. 优化部分代码
4. 修复`AuthSource`中腾讯云开发平台的拼写错误:`TENCEN_CLOUD`->`TENCENT_CLOUD`
5. 修复支付宝登陆时用户名为空的问题
### 2019/05/24 [v1.3.3](https://gitee.com/yadong.zhang/JustAuth/releases/v1.3.3)
1. 修复一些问题
2. 升级api,在AuthUser中增加`uuid`属性,可以通过`uuid` + `source`唯一确定一个用户,此举解决了用户身份归属的问题。
3. 发布1.3.3版本的jar包到公开仓库(1.3.2忘记发布了,( ╯□╰ ))
4. 重要:经咨询官方客服得知,CSDN的授权开放平台已经下线,如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了
### 2019/05/23 [v1.3.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.3.1)
1. 修复QQ登录的问题
2. 发布1.3.1版本的jar包到公开仓库
### 2019/05/21 [v1.3.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.3.0)
1. 新增google授权登录
2. 新增facebook授权登录
3. 发布1.3.0版本的jar包到公开仓库
### 2019/05/18 [v1.1.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.1.0) | [v1.2.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.2.0)
1. 发布1.1.0版本的jar包到公开仓库(支持qq和微信登录)
2. 支持淘宝登录
3. 修改`AuthUser.java`类中的`accessToken`属性,由原本的~~accessToken (String)~~改为`token (AuthToken)`
4. 修复一些bug
5. 发布1.2.0版本的jar包到公开仓库(支持淘宝登录)
### 2019/05/17
1. 增加qq和微信的授权登录
2. 修改getAccessToken方法的返回值
### 2019/03/27 [v1.0.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.0.1)
集成 支付宝授权登录
### 2019/03/25 [v1.0.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.0.0)
史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉和百度、Coding、腾讯云开发者平台和OSChina登录。 Login, so easy!