1
0
mirror of synced 2026-05-22 13:43:20 +00:00

Compare commits

...

163 Commits

Author SHA1 Message Date
yadong.zhang 4d8411e978 📝 编写文档 2019-08-06 18:26:50 +08:00
yadong.zhang 4f303705d9 格式化代码 2019-08-06 18:01:18 +08:00
yadong.zhang 1fb8be6c82 Merge branch 'master' of https://github.com/zhangyd-c/JustAuth 2019-08-06 17:49:35 +08:00
yadong.zhang 48a368b516 合并pr,增加单测以及集成企业微信 2019-08-06 17:46:51 +08:00
yadong.zhang 6594d90a71 Merge pull request #35 from xkcoding/wechat-enterprise
 企业微信完成
2019-08-06 17:22:49 +08:00
yadong.zhang 31c983234f Merge pull request #34 from Diffblue-benchmarks/add-tests-for-stringutils-2
Add tests for me.zhyd.oauth.utils.StringUtils
2019-08-06 17:14:41 +08:00
Yangkai.Shen 52682debfb 企业微信完成 2019-08-06 15:44:02 +08:00
yadong.zhang d5e161eaef 📝 集成华为 2019-08-06 07:41:35 +08:00
yadong.zhang 1ab1cc124a 📝 集成华为 2019-08-05 22:32:35 +08:00
Chris Smowton 050686d85f Add unit tests for me.zhyd.oauth.utils.StringUtils
These tests were written using Diffblue Cover.
2019-08-05 15:12:37 +01:00
Chris Smowton 05f95b86b4 Prevent illegal redirect URI error in testing
Redirect URIs are now verified to ensure they contain "http://" or "https://"
prefixes, so we must supply a plausible URI in testing. This allows us to get
a step further when running `mvn test`.
2019-08-05 15:12:37 +01:00
yadong.zhang 665daa37b2 📝 集成华为 2019-08-05 22:05:58 +08:00
yadong.zhang 576402eec3 📝 文档 2019-08-05 18:34:09 +08:00
yadong.zhang ec6c7a92b6 📝 删除根目录下的更新记录文档,挪到https://docs.justauth.whnb.wang中 2019-08-03 14:52:54 +08:00
yadong.zhang d44cbd6e2b 📝 帮助文档 2019-08-03 14:47:36 +08:00
yadong.zhang 15caef01d3 📝 帮助文档 2019-08-03 14:45:18 +08:00
yadong.zhang ad2e7c8231 📝 帮助文档 2019-08-03 14:41:11 +08:00
yadong.zhang 57a7b7ff17 📝 帮助文档 2019-08-03 14:35:51 +08:00
yadong.zhang 7a40159d15 📝 帮助文档 2019-08-03 14:28:31 +08:00
yadong.zhang bcb27e4118 📝 帮助文档 2019-08-03 14:28:14 +08:00
yadong.zhang 0b9cb95103 📝 帮助文档 2019-08-03 14:27:54 +08:00
yadong.zhang d61bb58b93 Merge branch 'master' into dev 2019-08-03 11:22:12 +08:00
yadong.zhang d034722232 📝 修改文档 2019-08-03 11:21:58 +08:00
yadong.zhang 197f15dc4f Merge branch 'master' of https://gitee.com/yadong.zhang/JustAuth 2019-08-03 10:08:35 +08:00
yadong.zhang fcb6fd9937 📝 Writing docs. 2019-08-03 10:07:56 +08:00
yadong.zhang ce5c437289 🍻 规范代码注释 2019-08-03 09:49:49 +08:00
yadong.zhang fabfff60c9 Merge pull request #32 from xkcoding/extract-cache
抽取缓存接口
2019-08-03 09:35:37 +08:00
yadong.zhang d4296d160e Merge branch 'dev' into extract-cache 2019-08-03 09:35:29 +08:00
yadong.zhang d9967b2814 💡 添加源码注释 2019-08-02 20:47:10 +08:00
yadong.zhang 267b74bed7 📝 增加缓存配置、去掉slf4j依赖、增加Log工具类等 2019-08-02 20:41:06 +08:00
Yangkai.Shen 3e8c475d3f ♻️ 添加 Request 的 AuthStateCache 构造器 2019-08-02 14:57:13 +08:00
Yangkai.Shen c1f9e96a92 修改 cache 测试类 2019-08-02 14:21:20 +08:00
Yangkai.Shen bed01eef6d ♻️ 抽取 state 校验方法 2019-08-02 14:19:42 +08:00
Yangkai.Shen 909702e4da 添加私服仓库,开发时方便发布测试 2019-08-02 11:22:46 +08:00
Yangkai.Shen 57cb7fb0d1 抽取 cache 接口,方便用户自行集成 cache 2019-08-02 11:15:37 +08:00
yadong.zhang b9c29c7534 更新"致谢"目录 2019-08-02 09:11:03 +08:00
yadong.zhang 6474c46505 Merge branch 'master' into dev 2019-08-01 17:13:34 +08:00
yadong.zhang 864665f1b8 📝 添加图标 2019-08-01 13:54:31 +08:00
yadong.zhang 83d23302f8 jacoco 2019-08-01 12:03:53 +08:00
yadong.zhang ca1522adfd travis.yml 2019-08-01 07:43:19 +08:00
yadong.zhang 55459652b9 travis.yml 2019-08-01 06:50:33 +08:00
yadong.zhang 92bc4ab34a 测试hutool版本 2019-07-31 14:49:04 +08:00
yadong.zhang 4fab7561a4 测试hutool版本 2019-07-31 11:04:48 +08:00
yadong.zhang 16918ea13f 🔖 发布1.9.5,请使用1.9.5版本 2019-07-31 09:54:09 +08:00
yadong.zhang eec26580c9 🚑 优化代码 2019-07-30 21:26:54 +08:00
yadong.zhang 421cabb176 🚑 优化代码 2019-07-30 21:19:55 +08:00
yadong.zhang f71fce6858 Merge pull request #31 from xkcoding/refactor-1.9.3
调整部分代码
2019-07-30 21:15:50 +08:00
yadong.zhang 3199334ddc Merge branch 'dev' into refactor-1.9.3 2019-07-30 21:15:12 +08:00
yadong.zhang 79417395a1 🍻 升级hutool,alipay-sdk改为provided,添加注释,state校验 2019-07-30 21:10:00 +08:00
Yangkai.Shen 06934b5242 ♻️ 格式化代码 2019-07-30 18:47:14 +08:00
Yangkai.Shen fcaef297ff ♻️ 修改部分代码 2019-07-30 18:41:34 +08:00
yadong.zhang e590077701 💡 修改注释 2019-07-30 10:34:48 +08:00
yadong.zhang 6ce26d4ce1 📝 更新文档 2019-07-30 09:36:30 +08:00
yadong.zhang 43d9ec8631 📌 修改程序版本 2019-07-30 09:34:17 +08:00
yadong.zhang 8e99e54519 Merge branch 'dev' 2019-07-30 09:13:26 +08:00
yadong.zhang 33076971fe 🔖 v1.9.3,详细更新内容参考update.md 2019-07-30 09:12:28 +08:00
yadong.zhang a2d6dfe707 💡 规范注释 2019-07-27 07:55:52 +08:00
yadong.zhang 64aa1940e4 💡 优化注释 2019-07-27 07:34:01 +08:00
yadong.zhang 56c1e4ea35 📝 文档 2019-07-25 22:38:17 +08:00
yadong.zhang ea1e1ba665 State优化第一步:去掉AuthState工具类 2019-07-25 22:33:24 +08:00
yadong.zhang 55c4b391bc State优化第一步:去掉AuthState工具类 2019-07-25 22:32:55 +08:00
yadong.zhang 094c297920 Merge branch 'dev' of https://github.com/zhangyd-c/JustAuth 2019-07-23 22:21:13 +08:00
yadong.zhang 7f725b579b Merge pull request #29 from xkcoding/refactor-AuthState
♻️ 重载 AuthState 方法,适配 justauth-spring-boot-starter 直接使用
2019-07-23 22:20:52 +08:00
Yangkai.Shen 66a68c4ead ♻️ 重载 AuthState 方法,适配 justauth-spring-boot-starter 直接使用 2019-07-23 21:19:04 +08:00
yadong.zhang 504bf1cc7c Merge pull request #28 from xkcoding/fix-lombok
🐛 修复 lombok 引入问题
2019-07-23 19:20:01 +08:00
Yangkai.Shen 97e7018795 🐛 修复 lombok 引入问题 [ISSUE#27](https://github.com/zhangyd-c/JustAuth/issues/27) 2019-07-22 20:08:34 +08:00
yadong.zhang 993dc687fc 🔖 配套starter:justauth-spring-boot-starter 2019-07-22 19:06:15 +08:00
yadong.zhang 38264616b4 🔖 发布1.9.2,修改AuthConfig类,适配spring starter配置 2019-07-22 17:54:33 +08:00
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
92 changed files with 5098 additions and 2608 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
+6 -7
View File
@@ -1,13 +1,12 @@
### 哪个平台?
为更快的帮您定位问题,推荐您用以下模板反馈问题:
### 1. 出现问题时,您做了哪些操作?
### 2. 在哪个步骤出现了问题?
### 重现步骤
### 报错信息
### 3. 您希望得到什么结果?
### 4. 您实际得到什么结果?
### 5. 请附上您出现问题的整屏截图或者整个异常堆栈信息
+21 -10
View File
@@ -1,15 +1,26 @@
### 该Pull Request关联的Issue
[ ] 是否为解决Issue
### 修改描述
### 测试用例
### 修复效果的截屏
### 您做了哪些更新?
#### 新增
#### 修改
#### 修复
#### 其他
### 是否做了充分测试?
- [ ] 是,已经做过测试,并且测试通过
- [ ] 否,还没做测试,需要作者自测
注:测试demo可以使用:
- [Springboot版](https://gitee.com/yadong.zhang/JustAuth-demo)
- [jFinal版](https://github.com/xkcoding/jfinal-justauth-demo)
- [ActFramework版](https://github.com/xkcoding/act-justauth-demo)
+19
View File
@@ -0,0 +1,19 @@
language: java
sudo: false # faster builds
install: true
jdk:
- openjdk8
notifications:
email: false
script:
- export TZ=Asia/Shanghai
- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
- mvn cobertura:cobertura -Dcobertura.report.format=xml -Dmaven.javadoc.skip.true
after_success:
- bash <(curl -s https://codecov.io/bash)
+66 -15
View File
@@ -6,7 +6,7 @@
</p>
<p align="center">
<a target="_blank" href="https://search.maven.org/search?q=JustAuth">
<img src="https://img.shields.io/badge/Maven Central-1.7.0-blue.svg" ></img>
<img src="https://img.shields.io/badge/Maven Central-1.10.0-blue.svg" ></img>
</a>
<a target="_blank" href="https://gitee.com/yadong.zhang/JustAuth/blob/master/LICENSE">
<img src="https://img.shields.io/apm/l/vim-mode.svg?color=yellow" ></img>
@@ -14,6 +14,21 @@
<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/" title="API文档">
<img src="https://img.shields.io/badge/Api Docs-1.10.0-orange.svg" ></img>
</a>
<a target="_blank" href="https://docs.justauth.whnb.wang" title="参考文档">
<img src="https://img.shields.io/badge/Docs-latest-blueviolet.svg" ></img>
</a>
<a href="https://codecov.io/gh/zhangyd-c/JustAuth">
<img src="https://codecov.io/gh/zhangyd-c/JustAuth/branch/master/graph/badge.svg" />
</a>
<a href='https://gitee.com/yadong.zhang/JustAuth/stargazers'>
<img src='https://gitee.com/yadong.zhang/JustAuth/badge/star.svg?theme=white' alt='star'></img>
</a>
<a target="_blank" href='https://github.com/zhangyd-c/JustAuth'>
<img src="https://img.shields.io/github/stars/zhangyd-c/JustAuth.svg?style=social" alt="github star"></img>
</a>
</p>
<center>
@@ -34,10 +49,20 @@
<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="#授权华为"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/huawei.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权微信企业版" title="微信企业版"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/wechat.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>
@@ -49,7 +74,8 @@
JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具类库**,它可以让我们脱离繁琐的第三方登录SDK,让登录变得**So easy!**
项目开源地址:[gitee](https://gitee.com/yadong.zhang/JustAuth) | [github](https://github.com/zhangyd-c/JustAuth)
项目开源地址:[gitee](https://gitee.com/yadong.zhang/JustAuth) | [github](https://github.com/zhangyd-c/JustAuth)
项目文档:[参考文档](https://docs.justauth.whnb.wang)
## 特点
@@ -65,7 +91,7 @@ JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.7.0</version>
<version>1.10.0</version>
</dependency>
```
- 调用api
@@ -78,39 +104,58 @@ AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.build());
// 生成授权页面
authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的参数
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(callback);
```
**配套Demo**[JustAuth-demo](https://gitee.com/yadong.zhang/JustAuth-demo)
**配套Demo**
- [Springboot版](https://gitee.com/yadong.zhang/JustAuth-demo)
- [jFinal版](https://github.com/xkcoding/jfinal-justauth-demo)
- [ActFramework版](https://github.com/xkcoding/act-justauth-demo)
**扩展工具**
- [justauth-spring-boot-starter](https://github.com/xkcoding/justauth-spring-boot-starter): Spring Boot 集成 JustAuth 的最佳实践
**配套SpringBoot starter**
[justauth-spring-boot-starter](https://github.com/xkcoding/justauth-spring-boot-starter)
具体的例子可以参考:
- [实现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/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://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/wiki/%E5%BE%AE%E5%8D%9AAPI" 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="https://developer.baidu.com/" 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/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/openapi_user" 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="http://wiki.connect.qq.com/" 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" 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/huawei.png" width="20"> | [AuthHuaweiRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthHuaweiRequest.java) | <a href="https://developer.huawei.com/consumer/cn/devservice/doc/30101" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/wechat.png" width="20"> | [AuthWeChatEnterpriseRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthWeChatEnterpriseRequest.java) | <a href="https://open.work.weixin.qq.com/api/doc#90000/90135/90664" 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登录只能针对少部分用户使用了_
@@ -127,7 +172,7 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经
2. 把fork过去的项目也就是你仓库中的项目clone到你的本地
3. 修改代码
4. commit后push到自己的库
5. 发起PRpull request 请求
5. 发起PRpull request 请求,提交到`dev`分支
6. 等待作者合并
## 致谢
@@ -138,6 +183,12 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经
[阿里妈妈MUX倾力打造的矢量图标库-iconfont](https://www.iconfont.cn/search/index): 本文档中的图标大部分取自该平台
[mica](https://github.com/lets-mica/mica)Spring Cloud 微服务开发核心包,支持 `web ``webflux`。注:JustAuth项目中的[UuidUtils](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/utils/UuidUtils.java)就是直接使用的mica提供的高性能的uuid创建工具类源码[StringUtil.java](https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L335)
## 关于OAuth
[The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749)
## 关注&交流
| 公众号 | 微信(备注:加群) |
@@ -148,11 +199,11 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经
- JustAuth交流群 230017570):专业交流该项目
- 开源总群 (190886500):各个开源项目的都有,也有博客建设等方面的朋友。(注意,该群需付费进入,防止发垃圾广告、垃圾推广等人士)
- 开源总群 (190886500):各个开源项目的都有,也有博客建设等方面的朋友。
## 请喝咖啡
| 支付宝 | 微信 |
| :------------: | :------------: |
| <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" /> |
| <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" /> |
+1
View File
@@ -3,4 +3,5 @@
- <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>
- 千年等一回,我只为等你...
View File
+77
View File
@@ -0,0 +1,77 @@
## ~~升级到1.8.0后如何启用state~~
~~在原api使用方法的基础上,为config追加一个state即可。~~
```
AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.state("state") // 就是这儿
.build());
```
## ~~升级到1.8.0后login方法报错?~~
~~这是因为1.8.0版本中新增了[AuthCallback](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/model/AuthCallback.java)类,这个类封装了所有可能的回调参数。目前包含以下三个参数:~~
- ~~`code`: 访问AuthorizeUrl后回调时带的参数code,用来换取token~~
- ~~`auth_code`: 支付宝授权登陆时不会返回code而是返回`auth_code`参数~~
- ~~`state`: 访问AuthorizeUrl后回调时带的参数state,用于和请求AuthorizeUrl前的state比较,防止CSRF攻击~~
~~1.8.0版本之后的api,可以直接用AuthCallback类作为回调方法的入参,比如:~~
```
@RequestMapping("/callback/{source}")
public Object login(@PathVariable("source") String source, AuthCallback callback) {
System.out.println("进入callback" + source + " callback params" + JSONObject.toJSONString(callback));
AuthRequest authRequest = getAuthRequest(source);
AuthResponse response = authRequest.login(callback);
System.out.println(JSONObject.toJSONString(response));
return response;
}
```
~~_代码截取自_ https://gitee.com/yadong.zhang/JustAuth-demo~~
## ~~升级到1.8.0后对于state参数有什么特殊要求吗?~~
~~理论上没有,stata只是用来保持会话状态,因为http协议的无状态性,从授权到回调,无法感知具体是哪个用户触发的。所以可以使用state作为校验。注:state参数每次完整的授权链中只可用一次!(也是为了防止不必要的危险)~~
~~作者建议state命名格式如下:~~
- ~~授权登录:`{source}_{ip}_{random}`~~
- ~~账号绑定:`{source}_{userId}_{ip}_{random}`~~
~~其中`source`表示授权平台,可以直接去JustAuth中的source,`ip`为当前用户的ip(部分情况可能不适用),`random`为随机字符串,`userId`为当前登录用户的id。~~
~~注:`authorize`和`login`(不是指回调传回的`state`,而是声明`request`时传入的`state`)中传的`state`务必保证一致~~
## 升级到1.9.3+版本后编译失败
主要明显的就是`IpUtils.getIp`和request的`.state`报错。
这是因为从`v1.9.3`版本开始,对项目进行了一些优化,具体优化内容参考:[v1.9.3](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.3)和[v1.9.4](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.4)。
新版本的使用方式,参考[JustAuth-demo](https://gitee.com/yadong.zhang/JustAuth-demo/blob/master/src/main/java/me/zhyd/justauth/RestAuthController.java)
```
@RequestMapping("/render/{source}")
public void renderAuth(@PathVariable("source") String source, HttpServletResponse response) throws IOException {
AuthRequest authRequest = getAuthRequest(source);
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
response.sendRedirect(authorizeUrl);
}
@RequestMapping("/callback/{source}")
public Object login(@PathVariable("source") String source, AuthCallback callback) {
AuthRequest authRequest = getAuthRequest(source);
AuthResponse response = authRequest.login(callback);
return response;
}
```
## 升级到最新版本后为什么支付宝登录不能用了?
在升级到新版后,使用支付宝登录会提示`ClassNotFoundExcption`异常,这是因为从`1.9.4`版本开始,JustAuth将不在强依赖`alipay-sdk-java`,如果你需要用到Alipay的授权登陆,那么你还需要添加以下依赖:
```
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.7.4.ALL</version>
</dependency>
```
+137
View File
@@ -0,0 +1,137 @@
<p align="center">
<a href="https://www.justauth.cn/"><img src="./_media/cover.png" width="400"></a>
</p>
<p align="center">
<strong>Login, so easy!</strong>
</p>
<p align="center">
<strong>史上最全的整合第三方登录的开源库</strong>
</p>
<p align="center">
<a target="_blank" href="https://search.maven.org/search?q=JustAuth">
<img src="https://img.shields.io/badge/Maven Central-1.10.0-blue.svg" ></img>
</a>
<a target="_blank" href="https://gitee.com/yadong.zhang/JustAuth/blob/master/LICENSE">
<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/" title="API文档">
<img src="https://img.shields.io/badge/Api Docs-1.10.0-orange.svg" ></img>
</a>
<a target="_blank" href="https://docs.justauth.whnb.wang" title="参考文档">
<img src="https://img.shields.io/badge/Docs-latest-blueviolet.svg" ></img>
</a>
<a href="https://codecov.io/gh/zhangyd-c/JustAuth">
<img src="https://codecov.io/gh/zhangyd-c/JustAuth/branch/master/graph/badge.svg" />
</a>
<a href='https://gitee.com/yadong.zhang/JustAuth/stargazers'>
<img src='https://gitee.com/yadong.zhang/JustAuth/badge/star.svg?theme=white' alt='star'></img>
</a>
<a target="_blank" href='https://github.com/zhangyd-c/JustAuth'>
<img src="https://img.shields.io/github/stars/zhangyd-c/JustAuth.svg?style=social" alt="github star"></img>
</a>
</p>
<p align="center">
<strong>开源地址:</strong> <a target="_blank" href='https://gitee.com/yadong.zhang/JustAuth'>Gitee</a> | <a target="_blank" href='https://github.com/zhangyd-c/JustAuth'>Github</a>
</p>
<p align="center">
<strong>QQ群:</strong>230017570
</p>
<p align="center">
<strong>文档更新日期:</strong> {docsify-updated}
</p>
## 简介
JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具类库**,它可以让我们脱离繁琐的第三方登录SDK,让登录变得**So easy!**
## 特点
废话不多说,就俩字:
1. **全**:已集成十多家第三方平台(国内外常用的基本都已包含),后续依然还有扩展计划!
2. **简**:API就是奔着最简单去设计的,尽量让您用起来没有障碍感!
## 已集成的平台
| :computer: 平台 | :coffee: API类 | :page_facing_up: SDK |
|:------:|:-------:|:-------:|
| <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/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/huawei.png" width="20"> | [AuthHuaweiRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthHuaweiRequest.java) | <a href="https://developer.huawei.com/consumer/cn/devservice/doc/30101" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/wechat.png" width="20"> | [AuthWeChatEnterpriseRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthWeChatEnterpriseRequest.java) | <a href="https://open.work.weixin.qq.com/api/doc#90000/90135/90664" 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) | 无 |
## 快速开始
- 引入依赖
```xml
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.10.0</version>
</dependency>
```
- 调用api
```java
// 创建授权request
AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 生成授权页面
authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的参数
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(callback);
```
## 贡献代码
1. fork本项目到自己的repo
2. 把fork过去的项目也就是你仓库中的项目clone到你的本地
3. 修改代码
4. commit后push到自己的库
5. 发起PRpull request 请求,提交到`dev`分支
6. 等待作者合并
_注:JustAuth只接受集成oauth2.0的平台_
## 关于功能尝鲜
JustAuth一共有两个主要分支:
- 线上版分支(master):稳定版,发布版就是这个分支的代码
- 开发版分支(dev):不保证稳定,新功能都会优先推送到该分支,对于想尝鲜的朋友,可以直接下载代码,然后源码编译dev分支
## 捐赠
| 支付宝 | 微信 |
| :------------: | :------------: |
| <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" /> |
+16
View File
@@ -0,0 +1,16 @@
![](_media/logo.png)
# JustAuth <small>1.10.0</small>
<strong>史上最全的整合第三方登录的开源库</strong>
<strong>Login, so easy</strong>
<p>已集成国内外十多家平台</p>
<p>极简的API设计</p>
[Gitee](https://gitee.com/yadong.zhang/JustAuth)
[Github](https://github.com/zhangyd-c/JustAuth)
[Get Started](#简介)
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

+12
View File
@@ -0,0 +1,12 @@
- [入门](README.md)
- [更新记录](update.md)
- 引导
- [如何使用JustAuth集成一个平台](how-to-use.md)
- [获取授权链接](authorize.md)
- [登录](login.md)
- 其他特性
- [使用State](using-state.md)
- [自定义state缓存](customize-the-state-cache.md)
- [配套项目](supporting.md)
- [Q&A](Q&A.md)
- [Who is using](users.md)
+3
View File
@@ -0,0 +1,3 @@
# 获取授权链接
待补充
+3
View File
@@ -0,0 +1,3 @@
# 自定义state缓存
待补充
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

+3
View File
@@ -0,0 +1,3 @@
# 如何使用JustAuth集成一个平台
待补充
+55
View File
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JustAuth - 史上最全的整合第三方登录的开源库</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="description" content="JustAuth,史上最全的整合第三方登录的开源库" />
<meta name="keywords" content="JustAuth,第三方授权登录,OAuth" />
<meta itemprop="name" content="JustAuth,史上最全的整合第三方登录的开源库" />
<meta itemprop="description" content="JustAuth,如你所见,它仅仅是一个第三方授权登录的工具类库,它可以让我们脱离繁琐的第三方登录SDK,让登录变得So easy!" />
<meta itemprop="image" content="./_media/cover.png" />
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
<!-- 百度统计 -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?4c7a1a462477545b480a3dd1ed95f0a9";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>
<div id="app">Please wait...</div>
<script>
window.$docsify = {
name: '<img src="./_media/cover.png" width="200"><br>Login, so easy',
search: {
maxAge: 86400000,
noData: {
'/': '找不到结果'
},
paths: 'auto',
placeholder: {
'/': '搜索'
}
},
repo: 'https://gitee.com/yadong.zhang/JustAuth',
loadSidebar: true,
maxLevel: 4,
subMaxLevel: 2,
auto2top: true,
coverpage: true,
formatUpdated: '{YYYY}/{MM}/{DD} {HH}:{mm}:{ss}',
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
# 登录
待补充
+8
View File
@@ -0,0 +1,8 @@
## 配套demo
- [Springboot版](https://gitee.com/yadong.zhang/JustAuth-demo) JustAuth项目的demospringboot项目)
- [jFinal版](https://github.com/xkcoding/jfinal-justauth-demo) Jfinal集成JustAuth的demo
- [ActFramework版](https://github.com/xkcoding/act-justauth-demo) ActFramework 集成 JustAuth 的 demo
## 插件
- [justauth-spring-boot-starter](https://github.com/xkcoding/justauth-spring-boot-starter) Spring Boot 集成 JustAuth 的最佳实践
+191
View File
@@ -0,0 +1,191 @@
## v1.10.0
### 2019/08/06
- 合并[PR-34](https://github.com/zhangyd-c/JustAuth/pull/34),添加StringUtil单元测试,修复bug
- 合并[PR-35](https://github.com/zhangyd-c/JustAuth/pull/35),集成企业微信
### 2019/08/05
- 集成华为登录
- 修改`AuthChecker#checkCode`方法,对于不同平台使用不同参数接受code的情况统一做处理
### 2019/08/03
合并github上[xkcoding](https://github.com/xkcoding) 的[pr#32](https://github.com/zhangyd-c/JustAuth/pull/32),抽取 cache 接口,方便用户自行集成 cache
### 2019/08/02
- 增加`AuthCache`配置类`AuthCacheConfig.java`,可以自定义缓存有效期以及是否开启定时任务
- 去掉`slf4j`依赖,封装`Log.java`工具类
- 规范测试类
## v1.9.5
### 2019/07/31
`v1.9.4`版本发布失败,请升级到`1.9.5`版本!
由此给您带来的不便,敬请谅解!
## v1.9.4
### 2019/07/30
1. 升级`hutool-http`版本到`v4.6.1`
2. 去除`AuthCallback`中增加的默认的校验state的方法,挪到`AuthDefaultRequest`中做统一处理
3. `alipay-sdk-java`依赖改为`provided`,如果需要使用支付宝登录,需要使用方手动引入相关依赖,具体操作方式,见项目WIKI;
4. 规范注释
## v1.9.3
### 2019/07/30
1. 规范注释
2. 增加State缓存,`AuthCallback`中增加默认的校验state的方法
3. 增加默认的state生成方法,参考`AuthStateUtils.java``UuidUtils.java`
4. 升级`hutool-http`版本到`v4.6.0`
5. 修复其他一些问题
### 2019/07/27
1. `IpUtils.getIp`改名为`IpUtils.getLocalIp`
2. 规范注释
### 2019/07/25
1. `AuthConfig`类中去掉state参数
2. 删除`AuthState`
3. 增加`authorize(String)`方法,并且使用`@Deprecated`标记`authorize()`方法
## v1.9.2
### 2019/07/22
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) 直接使用
## v1.9.1
### 2019/07/22
1. 增加`stackoverflow`参数校验
2. 解决`Pinterest`获取用户失败的问题
3. 添加注释
## v1.9.0
### 2019/07/19
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),修复小米回调错误问题 同时 支持微信获取unionId
## v1.8.1
### 2019/07/15
1. 新增 `AuthState` 类,内置默认的state生成规则和校验规则
### 2019/07/12
1. 合并[Braavos96](https://github.com/Braavos96)提交的[PR#16](https://github.com/zhangyd-c/JustAuth/pull/16)
## v1.8.0
### 2019/06/28
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版本!
## v1.7.1
### 2019/06/25
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)
## v1.7.0
### 2019/06/19
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) 解决抖音登录失败问题
## v1.6.1-beta
### 2019/06/18
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
## v1.6.0-beta
### 2019/06/06
1. 增加今日头条的授权登陆
2. 发布1.6.0-beta版本,今日头条开发者暂时不能认证, 所以无法做测试,等测试通过后,正式发布release版本
## v1.5.0
### 2019/05/28
1. 增加小米账号和微软的授权登陆
2. 发布1.5.0版本
## v1.4.0
### 2019/05/26
1. 增加抖音和Linkedin的授权登陆
2. 修改部分图片命名
3. 优化部分代码
4. 修复`AuthSource`中腾讯云开发平台的拼写错误:`TENCEN_CLOUD`->`TENCENT_CLOUD`
5. 修复支付宝登陆时用户名为空的问题
## v1.3.3
### 2019/05/24
1. 修复一些问题
2. 升级api,在AuthUser中增加`uuid`属性,可以通过`uuid` + `source`唯一确定一个用户,此举解决了用户身份归属的问题。
3. 发布1.3.3版本的jar包到公开仓库(1.3.2忘记发布了,( ╯□╰ ))
4. 重要:经咨询官方客服得知,CSDN的授权开放平台已经下线,如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了
## v1.3.1
### 2019/05/23
1. 修复QQ登录的问题
2. 发布1.3.1版本的jar包到公开仓库
## v1.3.0
### 2019/05/21
1. 新增google授权登录
2. 新增facebook授权登录
3. 发布1.3.0版本的jar包到公开仓库
## v1.1.0
### 2019/05/18
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方法的返回值
## v1.0.1
### 2019/03/27
集成 支付宝授权登录
## v1.0.0
### 2019/03/25
史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉和百度、Coding、腾讯云开发者平台和OSChina登录。 Login, so easy!
+11
View File
@@ -0,0 +1,11 @@
# Who is using?
- [spring-boot-demo](https://github.com/xkcoding/spring-boot-demo): spring boot demo 是一个用来学习 spring boot 的项目,总共包含 55 个集成demo,已经完成 46 个。(注:[spring-boot-demo-social](https://github.com/xkcoding/spring-boot-demo/tree/master/spring-boot-demo-social)模块中集成了JustAuth)注:该作者是JustAuth的开发者之一,负责开发QQ登录、微信登录、小米登录、微软登录、谷歌登录。
- [Guns](https://gitee.com/stylefeng/guns): Guns基于Spring Boot2,致力于做更简洁的后台管理系统。(注:Guns企业版中使用到了JustAuth实现第三方OAuth登录)
- [Shiro-Action](https://github.com/zhaojun1998/Shiro-Action): 基于 Shiro 的权限管理系统,支持 restful url 授权
- [project-template](https://github.com/HobbyBear/project-template): 作为前后端分离项目的后端模板整个项目基于springboot2.1.3,集jsr303框架做参数校验,spring security 做权限管理,并实现限制同一账号登陆会话数量功能,spring mail 发送邮件,justAuth做的第三方登陆, spring session做session共享,orm框架采用jpa,集成七牛云实现图片上传,redis实现分布式锁
- [mica](https://github.com/lets-mica/mica/tree/master/mica-social)mica是Spring Cloud 微服务开发核心包。采用源码形式(保留了作者名)另加改了一些代码。
...
我在这儿等你 >>> :alien:
+3
View File
@@ -0,0 +1,3 @@
# 使用State
待补充
+22 -2
View File
@@ -70,7 +70,7 @@ _注:非全部平台,部分平台可能不存在图例_
#### 授权微软
暂无
![授权微软](https://images.gitee.com/uploads/images/2019/0718/224146_681aa535_784199.png "授权微软")
#### 授权小米
@@ -80,8 +80,28 @@ _注:非全部平台,部分平台可能不存在图例_
暂无
#### 授权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登录只能针对少部分用户使用了_
_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_
+252 -171
View File
@@ -2,183 +2,264 @@
<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>
<modelVersion>4.0.0</modelVersion>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.7.0</version>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.10.0</version>
<name>JustAuth</name>
<name>JustAuth</name>
<url>https://gitee.com/yadong.zhang/JustAuth</url>
<description>
史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。
Login, so easy!
</description>
<licenses>
<license>
<name>MIT</name>
<url>https://gitee.com/yadong.zhang/JustAuth/blob/master/LICENSE</url>
</license>
</licenses>
<scm>
<connection>scm:git:https://gitee.com/yadong.zhang/JustAuth.git</connection>
<developerConnection>scm:git:https://gitee.com/yadong.zhang/JustAuth.git</developerConnection>
<url>https://gitee.com/yadong.zhang/JustAuth</url>
<description>
史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。
Login, so easy!
</description>
</scm>
<licenses>
<license>
<name>MIT</name>
<url>https://gitee.com/yadong.zhang/JustAuth/blob/master/LICENSE</url>
</license>
</licenses>
<developers>
<developer>
<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>
<scm>
<connection>scm:git:https://gitee.com/yadong.zhang/JustAuth.git</connection>
<developerConnection>scm:git:https://gitee.com/yadong.zhang/JustAuth.git</developerConnection>
<url>https://gitee.com/yadong.zhang/JustAuth</url>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven-source.version>2.2.1</maven-source.version>
<maven-compiler.version>3.8.1</maven-compiler.version>
<maven-javadoc.version>3.1.0</maven-javadoc.version>
<cobertura-version>2.7</cobertura-version>
<maven-surefire-version>2.20</maven-surefire-version>
<maven-gpg-version>1.6</maven-gpg-version>
<maven.test.skip>false</maven.test.skip>
<hutool-version>4.6.1</hutool-version>
<lombok-version>1.18.4</lombok-version>
<junit-version>4.11</junit-version>
<fastjson-version>1.2.58</fastjson-version>
<alipay-sdk-version>3.7.4.ALL</alipay-sdk-version>
<jacoco-version>0.8.2</jacoco-version>
</properties>
<developers>
<developer>
<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>
</developers>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool-version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson-version}</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay-sdk-version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<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>
<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>
<guava-version>27.1-jre</guava-version>
<alipay-sdk-version>3.7.4.ALL</alipay-sdk-version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool-version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson-version}</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay-sdk-version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler.version}</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source.version}</version>
<inherited>true</inherited>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-version}</version>
<configuration>
<!-- 包含其他测试类 -->
<includes>
<include>**/*Test.java</include>
</includes>
<!-- 排除掉AuthRequestTest测试类,该类只做api演示用 -->
<excludes>
<exclude>**/AuthRequestTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler.version}</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source.version}</version>
<inherited>true</inherited>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source.version}</version>
<inherited>true</inherited>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>${cobertura-version}</version>
<configuration>
<formats>
<format>html</format>
<format>xml</format>
</formats>
<check/>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-version}</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-version}</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source.version}</version>
<inherited>true</inherited>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>sonatype-nexus-staging</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
</profile>
</profiles>
</project>
</build>
<distributionManagement>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>sonatype-nexus-staging</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
</profile>
<!--私服-->
<profile>
<id>nexus</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-version}</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<id>xkcoding-nexus</id>
<url>https://nexus.xkcoding.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>xkcoding-nexus</id>
<url>https://nexus.xkcoding.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
</profile>
</profiles>
</project>
+50
View File
@@ -0,0 +1,50 @@
package me.zhyd.oauth.cache;
/**
* JustAuth缓存,用来缓存State
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.9.3
*/
public interface AuthCache {
/**
* 设置缓存
*
* @param key 缓存KEY
* @param value 缓存内容
*/
void set(String key, String value);
/**
* 设置缓存,指定过期时间
*
* @param key 缓存KEY
* @param value 缓存内容
* @param timeout 指定缓存过期时间(毫秒)
*/
void set(String key, String value, long timeout);
/**
* 获取缓存
*
* @param key 缓存KEY
* @return 缓存内容
*/
String get(String key);
/**
* 是否存在key,如果对应key的value值已过期,也返回false
*
* @param key 缓存KEY
* @return true:存在key,并且value没过期;falsekey不存在或者已过期
*/
boolean containsKey(String key);
/**
* 清理过期的缓存
*/
default void pruneCache() {
}
}
+22
View File
@@ -0,0 +1,22 @@
package me.zhyd.oauth.cache;
/**
* AuthCache配置类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.10.0
*/
public class AuthCacheConfig {
/**
* 默认缓存过期时间:3分钟
* 鉴于授权过程中,根据个人的操作习惯,或者授权平台的不同(google等),每个授权流程的耗时也有差异,不过单个授权流程一般不会太长
* 本缓存工具默认的过期时间设置为3分钟,即程序默认认为3分钟内的授权有效,超过3分钟则默认失效,失效后删除
*/
public static long timeout = 3 * 60 * 1000;
/**
* 是否开启定时{@link AuthDefaultCache#pruneCache()}的任务
*/
public static boolean schedulePrune = true;
}
@@ -0,0 +1,42 @@
package me.zhyd.oauth.cache;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 缓存调度器
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.9.3
*/
public enum AuthCacheScheduler {
/**
* 当前实例
*/
INSTANCE;
private AtomicInteger cacheTaskNumber = new AtomicInteger(1);
private ScheduledExecutorService scheduler;
AuthCacheScheduler() {
create();
}
private void create() {
this.shutdown();
this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("JustAuth-Task-%s", cacheTaskNumber.getAndIncrement())));
}
private void shutdown() {
if (null != scheduler) {
this.scheduler.shutdown();
}
}
public void schedule(Runnable task, long delay) {
this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
}
}
+140
View File
@@ -0,0 +1,140 @@
package me.zhyd.oauth.cache;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 默认的缓存实现
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.9.3
*/
public class AuthDefaultCache implements AuthCache {
/**
* state cache
*/
private static Map<String, CacheState> stateCache = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(true);
private final Lock writeLock = cacheLock.writeLock();
private final Lock readLock = cacheLock.readLock();
public AuthDefaultCache() {
if (AuthCacheConfig.schedulePrune) {
this.schedulePrune(AuthCacheConfig.timeout);
}
}
/**
* 设置缓存
*
* @param key 缓存KEY
* @param value 缓存内容
*/
@Override
public void set(String key, String value) {
set(key, value, AuthCacheConfig.timeout);
}
/**
* 设置缓存
*
* @param key 缓存KEY
* @param value 缓存内容
* @param timeout 指定缓存过期时间(毫秒)
*/
@Override
public void set(String key, String value, long timeout) {
writeLock.lock();
try {
stateCache.put(key, new CacheState(value, timeout));
} finally {
writeLock.unlock();
}
}
/**
* 获取缓存
*
* @param key 缓存KEY
* @return 缓存内容
*/
@Override
public String get(String key) {
readLock.lock();
try {
CacheState cacheState = stateCache.get(key);
if (null == cacheState || cacheState.isExpired()) {
return null;
}
return cacheState.getState();
} finally {
readLock.unlock();
}
}
/**
* 是否存在key,如果对应key的value值已过期,也返回false
*
* @param key 缓存KEY
* @return true:存在key,并且value没过期;falsekey不存在或者已过期
*/
@Override
public boolean containsKey(String key) {
readLock.lock();
try {
CacheState cacheState = stateCache.get(key);
return null != cacheState && !cacheState.isExpired();
} finally {
readLock.unlock();
}
}
/**
* 清理过期的缓存
*/
@Override
public void pruneCache() {
Iterator<CacheState> values = stateCache.values().iterator();
CacheState cacheState;
while (values.hasNext()) {
cacheState = values.next();
if (cacheState.isExpired()) {
values.remove();
}
}
}
/**
* 定时清理
*
* @param delay 间隔时长,单位毫秒
*/
public void schedulePrune(long delay) {
AuthCacheScheduler.INSTANCE.schedule(this::pruneCache, delay);
}
@Getter
@Setter
private class CacheState implements Serializable {
private String state;
private long expire;
CacheState(String state, long expire) {
this.state = state;
// 实际过期时间等于当前时间加上有效期
this.expire = System.currentTimeMillis() + expire;
}
boolean isExpired() {
return System.currentTimeMillis() > this.expire;
}
}
}
@@ -0,0 +1,66 @@
package me.zhyd.oauth.cache;
/**
* 默认的state缓存实现
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.10.0
*/
public enum AuthDefaultStateCache implements AuthStateCache {
/**
* 当前实例
*/
INSTANCE;
private AuthCache authCache;
AuthDefaultStateCache() {
authCache = new AuthDefaultCache();
}
/**
* 存入缓存
*
* @param key 缓存key
* @param value 缓存内容
*/
@Override
public void cache(String key, String value) {
authCache.set(key, value);
}
/**
* 存入缓存
*
* @param key 缓存key
* @param value 缓存内容
* @param timeout 指定缓存过期时间(毫秒)
*/
@Override
public void cache(String key, String value, long timeout) {
authCache.set(key, value, timeout);
}
/**
* 获取缓存内容
*
* @param key 缓存key
* @return 缓存内容
*/
@Override
public String get(String key) {
return authCache.get(key);
}
/**
* 是否存在key,如果对应key的value值已过期,也返回false
*
* @param key 缓存key
* @return true:存在key,并且value没过期;falsekey不存在或者已过期
*/
@Override
public boolean containsKey(String key) {
return authCache.containsKey(key);
}
}
+44
View File
@@ -0,0 +1,44 @@
package me.zhyd.oauth.cache;
/**
* <p>
* State缓存接口,方便用户扩展
* </p>
*
* @author yangkai.shen
* @since 1.10.0
*/
public interface AuthStateCache {
/**
* 存入缓存
*
* @param key 缓存key
* @param value 缓存内容
*/
void cache(String key, String value);
/**
* 存入缓存
*
* @param key 缓存key
* @param value 缓存内容
* @param timeout 指定缓存过期时间(毫秒)
*/
void cache(String key, String value, long timeout);
/**
* 获取缓存内容
*
* @param key 缓存key
* @return 缓存内容
*/
String get(String key);
/**
* 是否存在key,如果对应key的value值已过期,也返回false
*
* @param key 缓存key
* @return true:存在key,并且value没过期;falsekey不存在或者已过期
*/
boolean containsKey(String key);
}
@@ -6,11 +6,10 @@ import lombok.*;
* JustAuth配置类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
@Setter
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@@ -35,4 +34,28 @@ public class AuthConfig {
* 支付宝公钥:当选择支付宝登录时,该值可用
*/
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;
/**
* Stack Overflow Key
* <p>
*
* @since 1.9.0
*/
private String stackOverflowKey;
/**
* 企业微信,授权方的网页应用ID
*
* @since 1.10.0
*/
private String agentId;
}
@@ -1,13 +1,12 @@
package me.zhyd.oauth.config;
import me.zhyd.oauth.enums.AuthResponseStatus;
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
* @since 1.0
*/
public enum AuthSource {
@@ -79,7 +78,7 @@ public enum AuthSource {
@Override
public String accessToken() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
@Override
@@ -110,6 +109,11 @@ public enum AuthSource {
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
@@ -224,6 +228,11 @@ public enum AuthSource {
public String userInfo() {
return "https://graph.qq.com/user/get_user_info";
}
@Override
public String refresh() {
return "https://graph.qq.com/oauth2.0/token";
}
},
/**
* 微信
@@ -265,7 +274,7 @@ public enum AuthSource {
@Override
public String userInfo() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
},
/**
@@ -284,7 +293,7 @@ public enum AuthSource {
@Override
public String userInfo() {
return "https://oauth2.googleapis.com/tokeninfo";
return "https://www.googleapis.com/oauth2/v3/userinfo";
}
},
/**
@@ -327,7 +336,7 @@ public enum AuthSource {
@Override
public String refresh() {
return "https://open.douyin.com/oauth/refresh_token";
return "https://open.douyin.com/oauth/refresh_token/";
}
},
/**
@@ -420,6 +429,144 @@ public enum AuthSource {
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";
}
},
/**
* 华为
*
* @since 1.10.0
*/
HUAWEI {
@Override
public String authorize() {
return "https://oauth-login.cloud.huawei.com/oauth2/v2/authorize";
}
@Override
public String accessToken() {
return "https://oauth-login.cloud.huawei.com/oauth2/v2/token";
}
@Override
public String userInfo() {
return "https://api.vmall.com/rest.php";
}
@Override
public String refresh() {
return "https://oauth-login.cloud.huawei.com/oauth2/v2/token";
}
},
/**
* 企业微信
*
* @since 1.10.0
*/
WECHAT_ENTERPRISE {
@Override
public String authorize() {
return "https://open.work.weixin.qq.com/wwopen/sso/qrConnect";
}
@Override
public String accessToken() {
return "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
}
@Override
public String userInfo() {
return "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo";
}
};
/**
@@ -449,7 +596,7 @@ public enum AuthSource {
* @return url
*/
public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
/**
@@ -458,7 +605,7 @@ public enum AuthSource {
* @return url
*/
public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED);
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
}
}
@@ -1,11 +1,21 @@
package me.zhyd.oauth.request;
package me.zhyd.oauth.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* JustAuth通用的状态码对照表
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public enum ResponseStatus {
@Getter
@AllArgsConstructor
public enum AuthResponseStatus {
/**
* 2000正常
* other调用异常具体异常内容见{@code msg}
*/
SUCCESS(2000, "Success"),
FAILURE(5000, "Failure"),
NOT_IMPLEMENTED(5001, "Not Implemented"),
@@ -14,22 +24,11 @@ public enum ResponseStatus {
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;
ResponseStatus(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
@@ -1,13 +1,21 @@
package me.zhyd.oauth.model;
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 {
/**
* 0正常
* other调用异常具体异常内容见{@code desc}
*/
EC0(0, "接口调用成功"),
EC1(1, "API配置错误,未传入Client Key"),
EC2(2, "API配置错误,Client Key错误,请检查是否和开放平台的ClientKey一致"),
@@ -29,11 +37,6 @@ public enum AuthToutiaoErrorCode {
private int code;
private String desc;
AuthToutiaoErrorCode(int code, String desc) {
this.code = code;
this.desc = desc;
}
public static AuthToutiaoErrorCode getErrorCode(int errorCode) {
AuthToutiaoErrorCode[] errorCodes = AuthToutiaoErrorCode.values();
for (AuthToutiaoErrorCode code : errorCodes) {
@@ -43,12 +46,4 @@ public enum AuthToutiaoErrorCode {
}
return EC999;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
@@ -0,0 +1,42 @@
package me.zhyd.oauth.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户性别
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.8
*/
@Getter
@AllArgsConstructor
public enum AuthUserGender {
/**
* MALE/FAMALE为正常值,通过{@link AuthUserGender#getRealGender(String)}方法获取真实的性别
* UNKNOWN为容错值,部分平台不会返回用户性别,为了方便统一,使用UNKNOWN标记所有未知或不可测的用户性别信息
*/
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,10 +1,11 @@
package me.zhyd.oauth.exception;
import me.zhyd.oauth.request.ResponseStatus;
import me.zhyd.oauth.enums.AuthResponseStatus;
/**
* JustAuth通用异常类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthException extends RuntimeException {
@@ -13,16 +14,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());
}
@@ -30,6 +31,10 @@ public class AuthException extends RuntimeException {
super(message, cause);
}
public AuthException(Throwable cause) {
super(cause);
}
public int getErrorCode() {
return errorCode;
}
+150
View File
@@ -0,0 +1,150 @@
package me.zhyd.oauth.log;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.PrintStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 针对JustAuth提供的轻量级的日志打印工具
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @see Log#debug(String)
* @see Log#debug(String, Throwable)
* @see Log#warn(String)
* @see Log#warn(String, Throwable)
* @see Log#error(String)
* @see Log#error(String, Throwable)
* @since 1.10.0
*/
public class Log {
public static void debug(String msg) {
debug(msg, null);
}
public static void warn(String msg) {
warn(msg, null);
}
public static void error(String msg) {
error(msg, null);
}
public static void debug(String msg, Throwable t) {
print(Level.DEBUG, msg, t, System.out);
}
public static void warn(String msg, Throwable t) {
print(Level.WARN, msg, t, System.out);
}
public static void error(String msg, Throwable t) {
print(Level.ERROR, msg, t, System.err);
}
/**
* 打印日志内容,格式:2019-08-02 20:44:07 main me.zhyd.oauth.log.Log(debug:39) [DEBUG] - xxxx
*
* @param level 日志级别
* @param msg 日志内容
* @param t 异常信息
* @param ps 实际执行打印的PrintStream
*/
private static void print(Level level, String msg, Throwable t, PrintStream ps) {
if (Config.enable) {
if (level.getLevelNum() >= Config.level.getLevelNum()) {
ps.println(String.format("%s %s %s [%s] - %s", getDate(), Thread.currentThread().getName(), getCaller(), level, msg));
writeThrowable(t, ps);
ps.flush();
}
}
}
/**
* 获取调用方的信息
*
* @return 返回调用方的信息,格式:class(method:lineNumber)
*/
private static String getCaller() {
int offset = 2;
StackTraceElement[] stackTraceArr = (new Throwable()).getStackTrace();
StackTraceElement stackTrace = null;
if (offset >= stackTraceArr.length) {
offset = offset - 1;
}
stackTrace = stackTraceArr[offset];
return stackTrace.getClassName() +
"(" +
stackTrace.getMethodName() +
':' +
stackTrace.getLineNumber() +
")";
}
/**
* 获取格式化后的日期
*
* @return string
*/
private static String getDate() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
/**
* 打印异常信息
*
* @param t 异常
* @param targetStream 实际执行打印的PrintStream
*/
private static void writeThrowable(Throwable t, PrintStream targetStream) {
if (t != null) {
t.printStackTrace(targetStream);
}
}
/**
* 日志级别
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.10.0
*/
@Getter
@AllArgsConstructor
public enum Level {
/**
* DEBUG: 普通级别
*/
DEBUG(10),
/**
* WARN: 警告级别
*/
WARN(30),
/**
* ERROR: 异常级别
*/
ERROR(40);
private int levelNum;
}
/**
* 日志配置
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.10.0
*/
static class Config {
/**
* 需要打印的日志级别
*/
static Level level = Level.DEBUG;
/**
* 是否启用日志打印功能,默认启用
*/
static boolean enable = true;
}
}
@@ -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
* @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,37 @@
package me.zhyd.oauth.model;
import lombok.Getter;
import lombok.Setter;
/**
* 授权回调时的参数类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.8.0
*/
@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;
/**
* 华为授权登录接受code的参数名
*
* @since 1.10.0
*/
private String authorization_code;
}
@@ -1,404 +0,0 @@
package me.zhyd.oauth.model;
/**
* 钉钉授权登录时的异常状态码
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @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,18 +1,17 @@
package me.zhyd.oauth.model;
import lombok.Builder;
import lombok.Data;
import me.zhyd.oauth.request.ResponseStatus;
import lombok.Getter;
import me.zhyd.oauth.enums.AuthResponseStatus;
/**
* JustAuth统一授权响应类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
@Getter
@Builder
@Data
public class AuthResponse<T> {
/**
* 授权响应状态码
@@ -35,6 +34,6 @@ public class AuthResponse<T> {
* @return true or false
*/
public boolean ok() {
return this.code == ResponseStatus.SUCCESS.getCode();
return this.code == AuthResponseStatus.SUCCESS.getCode();
}
}
@@ -1,16 +1,17 @@
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
* @since 1.8
*/
@Data
@Getter
@Setter
@Builder
public class AuthToken {
private String accessToken;
@@ -34,4 +35,11 @@ public class AuthToken {
private String macAlgorithm;
private String macKey;
/**
* 企业微信附带属性
*
* @since 1.10.0
*/
private String code;
}
@@ -1,19 +1,27 @@
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
* @since 1.8
*/
@Getter
@Setter
@Builder
@Data
public class AuthUser {
/**
* 用户第三方系统的唯一id。在调用方集成改组件时,可以用uuid + source唯一确定一个用户
*
* @since 1.3.3
*/
private String uuid;
/**
* 用户名
*/
@@ -58,8 +66,4 @@ public class AuthUser {
* 用户授权的token信息
*/
private AuthToken token;
/**
* 用户第三方系统的唯一id。在调用方集成改组件时,可以用uuid + source唯一确定一个用户
*/
private String uuid;
}
@@ -1,44 +0,0 @@
package me.zhyd.oauth.model;
import java.util.Arrays;
/**
* 用户性别
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @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;
}
String[] males = {"m", "", "1", "male", "F"};
if (Arrays.asList(males).contains(code)) {
return MALE;
}
String[] females = {"f", "", "0", "female"};
if (Arrays.asList(females).contains(code)) {
return FEMALE;
}
return UNKNOW;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
@@ -7,12 +7,14 @@ 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.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
@@ -20,39 +22,44 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 支付宝登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.1
*/
public class AuthAlipayRequest extends BaseAuthRequest {
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");
.getAlipayPublicKey(), "RSA2");
}
public AuthAlipayRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.ALIPAY, authStateCache);
this.alipayClient = new DefaultAlipayClient(AuthSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(), "json", "UTF-8", config
.getAlipayPublicKey(), "RSA2");
}
@Override
protected AuthToken getAccessToken(String code) {
protected AuthToken getAccessToken(AuthCallback authCallback) {
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setGrantType("authorization_code");
request.setCode(code);
request.setCode(authCallback.getAuth_code());
AlipaySystemOauthTokenResponse response = null;
try {
response = this.alipayClient.execute(request);
} catch (Exception e) {
throw new AuthException("Unable to get token from alipay using code [" + code + "]", 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();
.accessToken(response.getAccessToken())
.uid(response.getUserId())
.expireIn(Integer.parseInt(response.getExpiresIn()))
.refreshToken(response.getRefreshToken())
.build();
}
@Override
@@ -69,29 +76,35 @@ public class AuthAlipayRequest extends BaseAuthRequest {
throw new AuthException(response.getSubMsg());
}
String province = response.getProvince(),
city = response.getCity();
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(AuthSource.ALIPAY)
.build();
.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,可自行跳转页面
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize() {
return UrlBuilder.getAlipayAuthorizeUrl(config.getClientId(), config.getRedirectUri());
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("app_id", config.getClientId())
.queryParam("scope", "auth_user")
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(state))
.build();
}
}
@@ -3,82 +3,127 @@ package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
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
* @since 1.0.0
*/
public class AuthBaiduRequest extends BaseAuthRequest {
public class AuthBaiduRequest extends AuthDefaultRequest {
public AuthBaiduRequest(AuthConfig config) {
super(config, AuthSource.BAIDU);
}
public AuthBaiduRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.BAIDU, authStateCache);
}
@Override
protected AuthToken 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 AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build();
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
return getAuthToken(response);
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getBaiduUserInfoUrl(accessToken)).execute();
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()
.uuid(object.getString("userid"))
.username(object.getString("username"))
.nickname(object.getString("username"))
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.token(authToken)
.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();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getBaiduAuthorizeUrl(config.getClientId(), config.getRedirectUri());
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(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getBaiduRevokeUrl(accessToken)).execute();
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
if (object.containsKey("error_code")) {
return AuthResponse.builder()
.code(ResponseStatus.FAILURE.getCode())
.msg(object.getString("error_msg"))
.build();
}
ResponseStatus status = object.getIntValue("result") == 1 ? ResponseStatus.SUCCESS : ResponseStatus.FAILURE;
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();
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
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(state))
.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,73 +1,94 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* Cooding登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class AuthCodingRequest extends BaseAuthRequest {
public class AuthCodingRequest extends AuthDefaultRequest {
public AuthCodingRequest(AuthConfig config) {
super(config, AuthSource.CODING);
}
public AuthCodingRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.CODING, authStateCache);
}
@Override
protected AuthToken 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 AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build();
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(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getCodingUserInfoUrl(accessToken)).execute();
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
if (object.getIntValue("code") != 0) {
throw new AuthException(object.getString("msg"));
}
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(AuthSource.CODING)
.build();
.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();
}
/**
* 返回认证url,可自行跳转页面
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.getIntValue("code") != 0) {
throw new AuthException(object.getString("msg"));
}
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize() {
return UrlBuilder.getCodingAuthorizeUrl(config.getClientId(), config.getRedirectUri());
public String authorize(String state) {
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(state))
.build();
}
}
@@ -1,67 +1,65 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* CSDN登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class AuthCsdnRequest extends BaseAuthRequest {
@Deprecated
public class AuthCsdnRequest extends AuthDefaultRequest {
public AuthCsdnRequest(AuthConfig config) {
super(config, AuthSource.CSDN);
}
public AuthCsdnRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.CSDN, authStateCache);
}
@Override
protected AuthToken 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 + "]");
}
this.checkResponse(accessTokenObject);
return AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getCsdnUserInfoUrl(accessToken)).execute();
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error_code")) {
throw new AuthException(object.getString("error"));
}
this.checkResponse(object);
return AuthUser.builder()
.uuid(object.getString("username"))
.username(object.getString("username"))
.remark(object.getString("description"))
.blog(object.getString("website"))
.gender(AuthUserGender.UNKNOW)
.token(authToken)
.source(AuthSource.CSDN)
.build();
.uuid(object.getString("username"))
.username(object.getString("username"))
.remark(object.getString("description"))
.blog(object.getString("website"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 返回认证url,可自行跳转页面
* 检查响应内容是否正确
*
* @return 返回授权地址
* @param object 请求响应内容
*/
@Override
public String authorize() {
return UrlBuilder.getCsdnAuthorizeUrl(config.getClientId(), config.getRedirectUri());
private void checkResponse(JSONObject object) {
if (object.containsKey("error_code")) {
throw new AuthException(object.getString("error"));
}
}
}
@@ -0,0 +1,275 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import me.zhyd.oauth.cache.AuthDefaultStateCache;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.log.Log;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.AuthChecker;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import me.zhyd.oauth.utils.UuidUtils;
/**
* 默认的request处理类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @author yangkai.shen (https://xkcoding.com)
* @since 1.0.0
*/
public abstract class AuthDefaultRequest implements AuthRequest {
protected AuthConfig config;
protected AuthSource source;
protected AuthStateCache authStateCache;
public AuthDefaultRequest(AuthConfig config, AuthSource source) {
this(config, source, AuthDefaultStateCache.INSTANCE);
}
public AuthDefaultRequest(AuthConfig config, AuthSource source, AuthStateCache authStateCache) {
this.config = config;
this.source = source;
this.authStateCache = authStateCache;
if (!AuthChecker.isSupportedAuth(config, source)) {
throw new AuthException(AuthResponseStatus.PARAMETER_INCOMPLETE);
}
// 校验配置合法性
AuthChecker.checkConfig(config, source);
}
/**
* 获取access token
*
* @param authCallback 授权成功后的回调参数
* @return token
* @see AuthDefaultRequest#authorize()
* @see AuthDefaultRequest#authorize(String)
*/
protected abstract AuthToken getAccessToken(AuthCallback authCallback);
/**
* 使用token换取用户信息
*
* @param authToken token信息
* @return 用户信息
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
*/
protected abstract AuthUser getUserInfo(AuthToken authToken);
/**
* 统一的登录入口。当通过{@link AuthDefaultRequest#authorize(String)}授权成功后,会跳转到调用方的相关回调方法中
* 方法的入参可以使用{@code AuthCallback}{@code AuthCallback}类中封装好了OAuth2授权回调所需要的参数
*
* @param authCallback 用于接收回调参数的实体
* @return AuthResponse
*/
@Override
public AuthResponse login(AuthCallback authCallback) {
try {
AuthChecker.checkCode(source, authCallback);
this.checkState(authCallback.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);
}
}
/**
* 处理{@link AuthDefaultRequest#login(AuthCallback)} 发生异常的情况,统一响应参数
*
* @param e 具体的异常
* @return AuthResponse
*/
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,可自行跳转页面
* <p>
* 不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击。
* 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址,在回调方法中对{@code state}进行校验
*
* @return 返回授权地址
* @see AuthDefaultRequest#authorize(String)
*/
@Deprecated
@Override
public String authorize() {
return this.authorize(null);
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(state))
.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) {
if (StringUtils.isEmpty(state)) {
state = UuidUtils.getUUID();
}
// 缓存state
authStateCache.cache(state, state);
return 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
*/
@Deprecated
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
*/
@Deprecated
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();
}
/**
* 校验回调传回的state
*
* @param state {@code state}一定不为空
*/
protected void checkState(String state) {
if (StringUtils.isEmpty(state) || !authStateCache.containsKey(state)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST);
}
}
}
@@ -4,13 +4,14 @@ import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.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.UrlBuilder;
@@ -18,59 +19,82 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 钉钉登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class AuthDingTalkRequest extends BaseAuthRequest {
public class AuthDingTalkRequest extends AuthDefaultRequest {
public AuthDingTalkRequest(AuthConfig config) {
super(config, AuthSource.DINGTALK);
}
public AuthDingTalkRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.DINGTALK, authStateCache);
}
@Override
protected AuthToken getAccessToken(String code) {
return AuthToken.builder().accessCode(code).build();
protected AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder().accessCode(authCallback.getCode()).build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String code = authToken.getAccessCode();
// 根据timestamp, appSecret计算签名值
String timestamp = System.currentTimeMillis() + "";
String urlEncodeSignature = GlobalAuthUtil.generateDingTalkSignature(config.getClientSecret(), timestamp);
JSONObject param = new JSONObject();
param.put("tmp_auth_code", code);
HttpResponse response = HttpRequest.post(UrlBuilder.getDingTalkUserInfoUrl(urlEncodeSignature, timestamp, config.getClientId()))
.body(param.toJSONString())
.execute();
String userInfo = response.body();
JSONObject object = JSON.parseObject(userInfo);
AuthDingTalkErrorCode errorCode = AuthDingTalkErrorCode.getErrorCode(object.getIntValue("errcode"));
if (!AuthDingTalkErrorCode.EC0.equals(errorCode)) {
throw new AuthException(errorCode.getDesc());
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();
.openId(object.getString("openid"))
.unionId(object.getString("unionid"))
.build();
return AuthUser.builder()
.uuid(object.getString("unionid"))
.nickname(object.getString("nick"))
.username(object.getString("nick"))
.gender(AuthUserGender.UNKNOW)
.source(AuthSource.DINGTALK)
.token(token)
.build();
.uuid(object.getString("unionid"))
.nickname(object.getString("nick"))
.username(object.getString("nick"))
.gender(AuthUserGender.UNKNOWN)
.source(source)
.token(token)
.build();
}
/**
* 返回认证url,可自行跳转页面
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize() {
return UrlBuilder.getDingTalkQrConnectUrl(config.getClientId(), config.getRedirectUri());
public String authorize(String state) {
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(state))
.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();
}
}
@@ -3,13 +3,16 @@ package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
@@ -17,76 +20,60 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 抖音登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.4.0
*/
public class AuthDouyinRequest extends BaseAuthRequest {
public class AuthDouyinRequest extends AuthDefaultRequest {
public AuthDouyinRequest(AuthConfig config) {
super(config, AuthSource.DOUYIN);
}
public AuthDouyinRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.DOUYIN, authStateCache);
}
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getDouyinAccessTokenUrl(config.getClientId(), config.getClientSecret(), code);
return this.getToken(accessTokenUrl);
protected AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
String openId = authToken.getOpenId();
String url = UrlBuilder.getDouyinUserInfoUrl(accessToken, openId);
HttpResponse response = HttpRequest.get(url).execute();
JSONObject object = JSONObject.parseObject(response.body());
JSONObject userInfoObject = this.checkResponse(object);
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.UNKNOW)
.token(authToken)
.source(AuthSource.DOUYIN)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getDouyinAuthorizeUrl(config.getClientId(), config.getRedirectUri());
.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) {
String refreshTokenUrl = UrlBuilder.getDouyinRefreshUrl(config.getClientId(), oldToken.getRefreshToken());
return AuthResponse.builder()
.code(ResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl))
.build();
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
* @return 实际请求数据的json对象
*/
private JSONObject checkResponse(JSONObject 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"));
}
return data;
}
/**
@@ -99,14 +86,76 @@ public class AuthDouyinRequest extends BaseAuthRequest {
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
String accessTokenStr = response.body();
JSONObject object = JSONObject.parseObject(accessTokenStr);
JSONObject accessTokenObject = this.checkResponse(object);
this.checkResponse(object);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.openId(accessTokenObject.getString("open_id"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.scope(accessTokenObject.getString("scope"))
.build();
.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();
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_key", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("scope", "user_info")
.queryParam("state", getRealState(state))
.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();
}
}
@@ -1,56 +1,65 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* Facebook登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.3.0
*/
public class AuthFacebookRequest extends BaseAuthRequest {
public class AuthFacebookRequest extends AuthDefaultRequest {
public AuthFacebookRequest(AuthConfig config) {
super(config, AuthSource.FACEBOOK);
}
public AuthFacebookRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.FACEBOOK, authStateCache);
}
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getFacebookAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config
.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error")) {
throw new AuthException(object.getJSONObject("error").getString("message"));
}
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.tokenType(object.getString("token_type"))
.build();
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.tokenType(accessTokenObject.getString("token_type"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getFacebookUserInfoUrl(accessToken)).execute();
HttpResponse response = doGetUserInfo(authToken);
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
if (object.containsKey("error")) {
throw new AuthException(object.getJSONObject("error").getString("message"));
}
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");
@@ -59,26 +68,31 @@ public class AuthFacebookRequest extends BaseAuthRequest {
picture = pictureObj.getString("url");
}
}
return AuthUser.builder()
.uuid(object.getString("id"))
.username(object.getString("name"))
.nickname(object.getString("name"))
.avatar(picture)
.location(object.getString("locale"))
.email(object.getString("email"))
.gender(AuthUserGender.getRealGender(object.getString("gender")))
.token(authToken)
.source(AuthSource.FACEBOOK)
.build();
return picture;
}
/**
* 返回认证url,可自行跳转页面
* 返回获取userInfo的url
*
* @return 返回授权地址
* @param authToken 用户token
* @return 返回获取userInfo的url
*/
@Override
public String authorize() {
return UrlBuilder.getFacebookAuthorizeUrl(config.getClientId(), config.getRedirectUri());
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,70 +1,76 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* Gitee登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class AuthGiteeRequest extends BaseAuthRequest {
public class AuthGiteeRequest extends AuthDefaultRequest {
public AuthGiteeRequest(AuthConfig config) {
super(config, AuthSource.GITEE);
}
public AuthGiteeRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.GITEE, authStateCache);
}
@Override
protected AuthToken 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 AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build();
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(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getGiteeUserInfoUrl(accessToken)).execute();
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("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.UNKNOW)
.token(authToken)
.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();
}
/**
* 返回认证url,可自行跳转页面
* 检查响应内容是否正确
*
* @return 返回授权地址
* @param object 请求响应内容
*/
@Override
public String authorize() {
return UrlBuilder.getGiteeAuthorizeUrl(config.getClientId(), config.getRedirectUri());
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
}
}
@@ -1,16 +1,16 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Map;
@@ -18,56 +18,53 @@ import java.util.Map;
* Github登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class AuthGithubRequest extends BaseAuthRequest {
public class AuthGithubRequest extends AuthDefaultRequest {
public AuthGithubRequest(AuthConfig config) {
super(config, AuthSource.GITHUB);
}
public AuthGithubRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.GITHUB, authStateCache);
}
@Override
protected AuthToken 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 AuthToken.builder().accessToken(res.get("access_token")).build();
return AuthToken.builder()
.accessToken(res.get("access_token"))
.scope(res.get("scope"))
.tokenType(res.get("token_type"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getGithubUserInfoUrl(accessToken)).execute();
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
return AuthUser.builder()
.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.UNKNOW)
.token(authToken)
.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();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getGithubAuthorizeUrl(config.getClientId(), config.getRedirectUri());
}
}
@@ -3,74 +3,104 @@ package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* Google登录
*
* @author yangkai.shen (https://xkcoding.com)
* @version 1.3
* @since 1.3
* @since 1.3.0
*/
public class AuthGoogleRequest extends BaseAuthRequest {
public class AuthGoogleRequest extends AuthDefaultRequest {
public AuthGoogleRequest(AuthConfig config) {
super(config, AuthSource.GOOGLE);
}
public AuthGoogleRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.GOOGLE, authStateCache);
}
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getGoogleAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config
.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error") || object.containsKey("error_description")) {
throw new AuthException("get google access_token has error:[" + object.getString("error") + "], error_description:[" + object
.getString("error_description") + "]");
}
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.scope(object.getString("scope"))
.tokenType(object.getString("token_type"))
.idToken(object.getString("id_token"))
.build();
.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) {
String accessToken = authToken.getIdToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getGoogleUserInfoUrl(accessToken)).execute();
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("name"))
.avatar(object.getString("picture"))
.nickname(object.getString("name"))
.location(object.getString("locale"))
.email(object.getString("email"))
.gender(AuthUserGender.UNKNOW)
.token(authToken)
.source(AuthSource.GOOGLE)
.build();
.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,可自行跳转页面
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize() {
return UrlBuilder.getGoogleAuthorizeUrl(config.getClientId(), config.getRedirectUri());
public String authorize(String state) {
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(state))
.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,196 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
import static me.zhyd.oauth.enums.AuthResponseStatus.SUCCESS;
/**
* 华为授权登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.10.0
*/
public class AuthHuaweiRequest extends AuthDefaultRequest {
public AuthHuaweiRequest(AuthConfig config) {
super(config, AuthSource.HUAWEI);
}
public AuthHuaweiRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.HUAWEI, authStateCache);
}
/**
* 获取access token
*
* @param authCallback 授权成功后的回调参数
* @return token
* @see AuthDefaultRequest#authorize()
* @see AuthDefaultRequest#authorize(String)
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpRequest request = HttpRequest.post(source.accessToken())
.form("grant_type", "authorization_code")
.form("code", authCallback.getAuthorization_code())
.form("client_id", config.getClientId())
.form("client_secret", config.getClientSecret())
.form("redirect_uri", config.getRedirectUri());
return getAuthToken(request);
}
/**
* 使用token换取用户信息
*
* @param authToken token信息
* @return 用户信息
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
*/
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = HttpRequest.post(source.userInfo())
.form("nsp_ts", System.currentTimeMillis())
.form("access_token", authToken.getAccessToken())
.form("nsp_fmt", "JS")
.form("nsp_svc", "OpenUP.User.getInfo")
.execute();
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
AuthUserGender gender = getRealGender(object);
return AuthUser.builder()
.uuid(object.getString("userID"))
.username(object.getString("userName"))
.nickname(object.getString("userName"))
.gender(gender)
.avatar(object.getString("headPictureURL"))
.token(authToken)
.source(source)
.build();
}
/**
* 刷新access token (续期)
*
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
HttpRequest request = HttpRequest.post(source.refresh())
.form("client_id", config.getClientId())
.form("client_secret", config.getClientSecret())
.form("refresh_token", authToken.getRefreshToken())
.form("grant_type", "refresh_token");
return AuthResponse.builder()
.code(SUCCESS.getCode())
.data(getAuthToken(request))
.build();
}
private AuthToken getAuthToken(HttpRequest request) {
HttpResponse response = request.execute();
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.refreshToken(object.getString("refresh_token"))
.build();
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("access_type", "offline")
.queryParam("scope", "https%3A%2F%2Fwww.huawei.com%2Fauth%2Faccount%2Fbase.profile")
.queryParam("state", getRealState(state))
.build();
}
/**
* 返回获取accessToken的url
*
* @param code 授权码
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("grant_type", "authorization_code")
.queryParam("code", code)
.queryParam("client_id", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.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())
.queryParam("nsp_ts", System.currentTimeMillis())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("nsp_fmt", "JS")
.queryParam("nsp_svc", "OpenUP.User.getInfo")
.build();
}
/**
* 获取用户的实际性别。华为系统中,用户的性别:1表示女,0表示男
*
* @param object obj
* @return AuthUserGender
*/
private AuthUserGender getRealGender(JSONObject object) {
int genderCodeInt = object.getIntValue("gender");
String genderCode = genderCodeInt == 1 ? "0" : (genderCodeInt == 0) ? "1" : genderCodeInt + "";
return AuthUserGender.getRealGender(genderCode);
}
/**
* 校验响应结果
*
* @param object 接口返回的结果
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("NSP_STATUS")) {
throw new AuthException(object.getString("error"));
}
if (object.containsKey("error")) {
throw new AuthException(object.getString("sub_error") + ":" + object.getString("error_description"));
}
}
}
@@ -4,13 +4,17 @@ 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.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
@@ -19,35 +23,61 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 领英登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.4.0
*/
public class AuthLinkedinRequest extends BaseAuthRequest {
public class AuthLinkedinRequest extends AuthDefaultRequest {
public AuthLinkedinRequest(AuthConfig config) {
super(config, AuthSource.LINKEDIN);
}
public AuthLinkedinRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.LINKEDIN, authStateCache);
}
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getLinkedinAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config
.getRedirectUri());
return this.getToken(accessTokenUrl);
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(UrlBuilder.getLinkedinUserInfoUrl())
.header("Host", "api.linkedin.com")
.header("Connection", "Keep-Alive")
.header("Authorization", "Bearer " + accessToken)
.execute();
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")) {
@@ -61,60 +91,45 @@ public class AuthLinkedinRequest extends BaseAuthRequest {
} else {
lastName = getUserName(userInfoObject, "lastName");
}
String userName = firstName + " " + 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");
.getJSONArray("elements");
if (null != displayImageElements && displayImageElements.size() > 0) {
JSONObject largestImageObj = displayImageElements.getJSONObject(displayImageElements.size() - 1);
avatar = largestImageObj.getJSONArray("identifiers").getJSONObject(0).getString("identifier");
}
}
// 获取用户邮箱地址
String email = this.getUserEmail(accessToken);
return AuthUser.builder()
.uuid(userInfoObject.getString("id"))
.username(userName)
.nickname(userName)
.avatar(avatar)
.email(email)
.token(authToken)
.gender(AuthUserGender.UNKNOW)
.source(AuthSource.LINKEDIN)
.build();
return avatar;
}
/**
* 返回认证url,可自行跳转页面
* 获取用户的email
*
* @return 返回授权地址
* @param accessToken 用户授权后返回的token
* @return 用户的邮箱地址
*/
@Override
public String authorize() {
return UrlBuilder.getLinkedinAuthorizeUrl(config.getClientId(), config.getRedirectUri());
}
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();
System.out.println(emailResponse.body());
.header("Host", "api.linkedin.com")
.header("Connection", "Keep-Alive")
.header("Authorization", "Bearer " + accessToken)
.execute();
JSONObject emailObj = JSONObject.parseObject(emailResponse.body());
if (emailObj.containsKey("elements")) {
email = emailObj.getJSONArray("elements")
.getJSONObject(0)
.getJSONObject("handle~")
.getString("emailAddress");
}
return email;
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) {
@@ -128,20 +143,25 @@ public class AuthLinkedinRequest extends BaseAuthRequest {
@Override
public AuthResponse refresh(AuthToken oldToken) {
if (StringUtils.isEmpty(oldToken.getRefreshToken())) {
throw new AuthException(ResponseStatus.UNSUPPORTED);
String refreshToken = oldToken.getRefreshToken();
if (StringUtils.isEmpty(refreshToken)) {
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
}
String refreshTokenUrl = UrlBuilder.getLinkedinRefreshUrl(config.getClientId(), config.getClientSecret(), oldToken
.getRefreshToken());
String refreshTokenUrl = refreshTokenUrl(refreshToken);
return AuthResponse.builder()
.code(ResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl))
.build();
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl))
.build();
}
private void checkResponse(JSONObject userInfoObject) {
if (userInfoObject.containsKey("error")) {
throw new AuthException(userInfoObject.getString("error_description"));
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
}
@@ -153,18 +173,49 @@ public class AuthLinkedinRequest extends BaseAuthRequest {
*/
private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.post(accessTokenUrl)
.header("Host", "www.linkedin.com")
.header("Content-Type", "application/x-www-form-urlencoded")
.execute();
.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();
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.build();
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("scope", "r_liteprofile%20r_emailaddress%20w_member_social")
.queryParam("state", getRealState(state))
.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();
}
}
@@ -4,13 +4,17 @@ 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.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.log.Log;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
import java.text.MessageFormat;
@@ -19,91 +23,84 @@ import java.text.MessageFormat;
* 小米登录
*
* @author yangkai.shen (https://xkcoding.com)
* @version 1.5
* @since 1.5
* @since 1.5.0
*/
public class AuthMiRequest extends BaseAuthRequest {
public class AuthMiRequest extends AuthDefaultRequest {
private static final String PREFIX = "&&&START&&&";
public AuthMiRequest(AuthConfig config) {
super(config, AuthSource.MI);
}
public AuthMiRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.MI, authStateCache);
}
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getMiAccessTokenUrl(config.getClientId(), config.getClientSecret(), config.getRedirectUri(), code);
return getToken(accessTokenUrl);
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 object = JSONObject.parseObject(jsonStr);
JSONObject accessTokenObject = JSONObject.parseObject(jsonStr);
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
if (accessTokenObject.containsKey("error")) {
throw new AuthException(accessTokenObject.getString("error_description"));
}
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.scope(object.getString("scope"))
.tokenType(object.getString("token_type"))
.refreshToken(object.getString("refresh_token"))
.openId(object.getString("openId"))
.macAlgorithm(object.getString("mac_algorithm"))
.macKey(object.getString("mac_key"))
.build();
.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 = HttpRequest.get(UrlBuilder.getMiUserInfoUrl(config.getClientId(), authToken.getAccessToken()))
.execute();
HttpResponse userResponse = doGetUserInfo(authToken);
JSONObject userProfile = JSONObject.parseObject(userResponse.body());
if (StrUtil.equalsIgnoreCase(userProfile.getString("result"), "error")) {
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.UNKNOW)
.token(authToken)
.source(AuthSource.MI)
.build();
.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());
.getClientId(), authToken.getAccessToken());
HttpResponse emailResponse = HttpRequest.get(emailPhoneUrl).execute();
JSONObject userEmailPhone = JSONObject.parseObject(emailResponse.body());
if (!StrUtil.equalsIgnoreCase(userEmailPhone.getString("result"), "error")) {
if (!"error".equalsIgnoreCase(userEmailPhone.getString("result"))) {
JSONObject emailPhone = userEmailPhone.getJSONObject("data");
authUser.setEmail(emailPhone.getString("email"));
} else {
Log.warn("小米开发平台暂时不对外开放用户手机及邮箱信息的获取");
}
return authUser;
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getMiAuthorizeUrl(config.getClientId(), config.getRedirectUri());
}
/**
* 刷新access token (续期)
*
@@ -112,9 +109,42 @@ public class AuthMiRequest extends BaseAuthRequest {
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
String miRefreshUrl = UrlBuilder.getMiRefreshUrl(config.getClientId(), config.getClientSecret(), config.getRedirectUri(), authToken
.getRefreshToken());
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(authToken.getRefreshToken())))
.build();
}
return AuthResponse.builder().code(ResponseStatus.SUCCESS.getCode()).data(getToken(miRefreshUrl)).build();
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("scope", "user/profile%20user/openIdV2%20user/phoneAndEmail")
.queryParam("skip_confirm", "false")
.queryParam("state", getRealState(state))
.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();
}
}
@@ -2,38 +2,39 @@ package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.HashMap;
import java.util.Map;
import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap;
/**
* 微软登录
*
* @author yangkai.shen (https://xkcoding.com)
* @version 1.5
* @since 1.5
* @since 1.5.0
*/
public class AuthMicrosoftRequest extends BaseAuthRequest {
public class AuthMicrosoftRequest extends AuthDefaultRequest {
public AuthMicrosoftRequest(AuthConfig config) {
super(config, AuthSource.MICROSOFT);
}
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getMicrosoftAccessTokenUrl(config.getClientId(), config.getClientSecret(), config
.getRedirectUri(), code);
public AuthMicrosoftRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.MICROSOFT, authStateCache);
}
return getToken(accessTokenUrl);
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return getToken(accessTokenUrl(authCallback.getCode()));
}
/**
@@ -43,30 +44,33 @@ public class AuthMicrosoftRequest extends BaseAuthRequest {
* @return token对象
*/
private AuthToken getToken(String accessTokenUrl) {
Map<String, Object> paramMap = new HashMap<>(6);
HttpUtil.decodeParamMap(accessTokenUrl, "UTF-8").forEach(paramMap::put);
HttpResponse response = HttpRequest.post(accessTokenUrl)
.header("Host", "https://login.microsoftonline.com")
.header("Content-Type", "application/x-www-form-urlencoded")
.form(paramMap)
.execute();
.header("Host", "https://login.microsoftonline.com")
.contentType("application/x-www-form-urlencoded")
.form(parseQueryToMap(accessTokenUrl))
.execute();
String accessTokenStr = response.body();
JSONObject object = JSONObject.parseObject(accessTokenStr);
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
this.checkResponse(object);
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.scope(object.getString("scope"))
.tokenType(object.getString("token_type"))
.refreshToken(object.getString("refresh_token"))
.build();
.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();
}
private void checkResponse(JSONObject response) {
if (response.containsKey("error")) {
throw new AuthException(response.getString("error_description"));
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
}
@@ -75,31 +79,20 @@ public class AuthMicrosoftRequest extends BaseAuthRequest {
String token = authToken.getAccessToken();
String tokenType = authToken.getTokenType();
String jwt = tokenType + " " + token;
HttpResponse response = HttpRequest.get(UrlBuilder.getMicrosoftUserInfoUrl())
.header("Authorization", jwt)
.execute();
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.UNKNOW)
.token(authToken)
.source(AuthSource.MICROSOFT)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getMicrosoftAuthorizeUrl(config.getClientId(), config.getRedirectUri());
.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();
}
/**
@@ -110,9 +103,75 @@ public class AuthMicrosoftRequest extends BaseAuthRequest {
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
String refreshTokenUrl = UrlBuilder.getMicrosoftRefreshUrl(config.getClientId(), config.getClientSecret(), config
.getRedirectUri(), authToken.getRefreshToken());
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(authToken.getRefreshToken())))
.build();
}
return AuthResponse.builder().code(ResponseStatus.SUCCESS.getCode()).data(getToken(refreshTokenUrl)).build();
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
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(state))
.build();
}
/**
* 返回获取accessToken的url
*
* @param code 授权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,70 +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.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* oschina登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class AuthOschinaRequest extends BaseAuthRequest {
public class AuthOschinaRequest extends AuthDefaultRequest {
public AuthOschinaRequest(AuthConfig config) {
super(config, AuthSource.OSCHINA);
}
public AuthOschinaRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.OSCHINA, authStateCache);
}
@Override
protected AuthToken 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 AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build();
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(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getOschinaUserInfoUrl(accessToken)).execute();
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
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(AuthSource.OSCHINA)
.build();
.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();
}
/**
* 返回认证url,可自行跳转页面
* 返回获取accessToken的url
*
* @return 返回授权地址
* @param code 授权回调时带回的授权码
* @return 返回获取accessToken的url
*/
@Override
public String authorize() {
return UrlBuilder.getOschinaAuthorizeUrl(config.getClientId(), config.getRedirectUri());
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"));
}
}
}
@@ -0,0 +1,119 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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)
* @since 1.9.0
*/
public class AuthPinterestRequest extends AuthDefaultRequest {
private static final String FAILURE = "failure";
public AuthPinterestRequest(AuthConfig config) {
super(config, PINTEREST);
}
public AuthPinterestRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, PINTEREST, authStateCache);
}
@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");
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("scope", "read_public")
.queryParam("state", getRealState(state))
.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("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"));
}
}
}
@@ -4,12 +4,16 @@ 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.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
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.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
@@ -21,36 +25,33 @@ import java.util.Map;
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @author yangkai.shen (https://xkcoding.com)
* @version 1.0
* @since 1.8
* @since 1.1.0
*/
public class AuthQqRequest extends BaseAuthRequest {
public class AuthQqRequest extends AuthDefaultRequest {
public AuthQqRequest(AuthConfig config) {
super(config, AuthSource.QQ);
}
public AuthQqRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.QQ, authStateCache);
}
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getQqAccessTokenUrl(config.getClientId(), config.getClientSecret(), code, config
.getRedirectUri());
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
Map<String, String> accessTokenObject = GlobalAuthUtil.parseStringToMap(response.body());
if (!accessTokenObject.containsKey("access_token")) {
throw new AuthException("Unable to get token from qq using code [" + code + "]");
}
return AuthToken.builder()
.accessToken(accessTokenObject.get("access_token"))
.expireIn(Integer.valueOf(accessTokenObject.get("expires_in")))
.refreshToken(accessTokenObject.get("refresh_token"))
.build();
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 accessToken = authToken.getAccessToken();
String openId = this.getOpenId(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getQqUserInfoUrl(config.getClientId(), accessToken, openId))
.execute();
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
if (object.getIntValue("ret") != 0) {
throw new AuthException(object.getString("msg"));
@@ -62,31 +63,29 @@ public class AuthQqRequest extends BaseAuthRequest {
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(AuthSource.QQ)
.build();
.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();
}
/**
* 返回认证url,可自行跳转页面
* 获取QQ用户的OpenId,支持自定义是否启用查询unionid的功能,如果启用查询unionid的功能,
* 那就需要调用者先通过邮件申请unionid功能,参考链接 {@see http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D}
*
* @return 返回授权地址
* @param authToken 通过{@link AuthQqRequest#getAccessToken(AuthCallback)}获取到的{@code authToken}
* @return openId
*/
@Override
public String authorize() {
return UrlBuilder.getQqAuthorizeUrl(config.getClientId(), config.getRedirectUri());
}
private String getOpenId(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getQqOpenidUrl("https://graph.qq.com/oauth2.0/me", accessToken))
.execute();
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(", "");
@@ -97,10 +96,39 @@ public class AuthQqRequest extends BaseAuthRequest {
throw new AuthException(object.get("error") + ":" + object.get("error_description"));
}
authToken.setOpenId(object.getString("openid"));
authToken.setUnionId(object.getString("unionid"));
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,120 @@
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.cache.AuthStateCache;
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.AuthResponse;
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.RENREN;
import static me.zhyd.oauth.enums.AuthResponseStatus.SUCCESS;
/**
* 人人登录
*
* @author hongwei.peng (pengisgood(at)gmail(dot)com)
* @since 1.9.0
*/
public class AuthRenrenRequest extends AuthDefaultRequest {
public AuthRenrenRequest(AuthConfig config) {
super(config, RENREN);
}
public AuthRenrenRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, RENREN, authStateCache);
}
@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,33 +1,48 @@
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.enums.AuthResponseStatus;
import me.zhyd.oauth.model.AuthToken;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public interface AuthRequest {
/**
* 返回认证url,可自行跳转页面
* 返回授权url,可自行跳转页面
* <p>
* 不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击。
* 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址,在回调方法中对{@code state}进行校验
*
* @return 返回授权地址
*/
@Deprecated
default String authorize() {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED);
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
*/
default String authorize(String state) {
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);
}
/**
@@ -37,7 +52,7 @@ public interface AuthRequest {
* @return AuthResponse
*/
default AuthResponse revoke(AuthToken authToken) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED);
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
/**
@@ -47,6 +62,6 @@ public interface AuthRequest {
* @return AuthResponse
*/
default AuthResponse refresh(AuthToken authToken) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED);
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
}
@@ -0,0 +1,102 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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)
* @since 1.9.0
*/
public class AuthStackOverflowRequest extends AuthDefaultRequest {
public AuthStackOverflowRequest(AuthConfig config) {
super(config, STACK_OVERFLOW);
}
public AuthStackOverflowRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, STACK_OVERFLOW, authStateCache);
}
@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();
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("scope", "read_inbox")
.queryParam("state", getRealState(state))
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
}
}
@@ -1,14 +1,15 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.UrlBuilder;
@@ -16,53 +17,62 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 淘宝登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.1.0
*/
public class AuthTaobaoRequest extends BaseAuthRequest {
public class AuthTaobaoRequest extends AuthDefaultRequest {
public AuthTaobaoRequest(AuthConfig config) {
super(config, AuthSource.TAOBAO);
}
public AuthTaobaoRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.TAOBAO, authStateCache);
}
@Override
protected AuthToken getAccessToken(String code) {
return AuthToken.builder().accessCode(code).build();
protected AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder().accessCode(authCallback.getCode()).build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessCode = authToken.getAccessCode();
HttpResponse response = HttpRequest.post(UrlBuilder.getTaobaoAccessTokenUrl(this.config.getClientId(), this.config
.getClientSecret(), accessCode, this.config.getRedirectUri())).execute();
JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error")) {
throw new AuthException(ResponseStatus.FAILURE + ":" + object.getString("error_description"));
HttpResponse response = doPostAuthorizationCode(authToken.getAccessCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error")) {
throw new AuthException(accessTokenObject.getString("error_description"));
}
authToken.setAccessToken(object.getString("access_token"));
authToken.setRefreshToken(object.getString("refresh_token"));
authToken.setExpireIn(object.getIntValue("expires_in"));
authToken.setUid(object.getString("taobao_user_id"));
authToken.setOpenId(object.getString("taobao_open_uid"));
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(object.getString("taobao_user_nick"));
String nick = GlobalAuthUtil.urlDecode(accessTokenObject.getString("taobao_user_nick"));
return AuthUser.builder()
.uuid(object.getString("taobao_user_id"))
.username(nick)
.nickname(nick)
.gender(AuthUserGender.UNKNOW)
.token(authToken)
.source(AuthSource.TAOBAO)
.build();
.uuid(accessTokenObject.getString("taobao_user_id"))
.username(nick)
.nickname(nick)
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 返回认证url,可自行跳转页面
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize() {
return UrlBuilder.getTaobaoAuthorizeUrl(config.getClientId(), config.getRedirectUri());
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("view", "web")
.queryParam("state", getRealState(state))
.build();
}
}
@@ -0,0 +1,113 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
/**
* Teambition授权登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.9.0
*/
public class AuthTeambitionRequest extends AuthDefaultRequest {
public AuthTeambitionRequest(AuthConfig config) {
super(config, AuthSource.TEAMBITION);
}
public AuthTeambitionRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.TEAMBITION, authStateCache);
}
/**
* @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,72 +1,94 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 腾讯云登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class AuthTencentCloudRequest extends BaseAuthRequest {
public class AuthTencentCloudRequest extends AuthDefaultRequest {
public AuthTencentCloudRequest(AuthConfig config) {
super(config, AuthSource.TENCENT_CLOUD);
}
public AuthTencentCloudRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.TENCENT_CLOUD, authStateCache);
}
@Override
protected AuthToken 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 AuthToken.builder().accessToken(object.getString("access_token")).build();
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(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getTencentCloudUserInfoUrl(accessToken)).execute();
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body());
if (object.getIntValue("code") != 0) {
throw new AuthException(object.getString("msg"));
}
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(AuthSource.TENCENT_CLOUD)
.build();
.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();
}
/**
* 返回认证url,可自行跳转页面
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if (object.getIntValue("code") != 0) {
throw new AuthException(object.getString("msg"));
}
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize() {
return UrlBuilder.getTencentCloudAuthorizeUrl(config.getClientId(), config.getRedirectUri());
public String authorize(String state) {
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(state))
.build();
}
}
@@ -1,56 +1,55 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.AuthToutiaoErrorCode;
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.5
* @since 1.5
* @since 1.6.0-beta
*/
public class AuthToutiaoRequest extends BaseAuthRequest {
public class AuthToutiaoRequest extends AuthDefaultRequest {
public AuthToutiaoRequest(AuthConfig config) {
super(config, AuthSource.TOUTIAO);
}
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getToutiaoAccessTokenUrl(config.getClientId(), config.getClientSecret(), code);
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
JSONObject object = JSONObject.parseObject(response.body());
public AuthToutiaoRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.TOUTIAO, authStateCache);
}
if (object.containsKey("error_code")) {
throw new AuthException(AuthToutiaoErrorCode.getErrorCode(object.getIntValue("error_code")).getDesc());
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.openId(object.getString("open_id"))
.build();
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.openId(accessTokenObject.getString("open_id"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse userResponse = HttpRequest.get(UrlBuilder.getToutiaoUserInfoUrl(config.getClientId(), authToken.getAccessToken())).execute();
HttpResponse userResponse = doGetUserInfo(authToken);
JSONObject userProfile = JSONObject.parseObject(userResponse.body());
if (userProfile.containsKey("error_code")) {
throw new AuthException(AuthToutiaoErrorCode.getErrorCode(userProfile.getIntValue("error_code")).getDesc());
}
this.checkResponse(userProfile);
JSONObject user = userProfile.getJSONObject("data");
@@ -58,24 +57,74 @@ public class AuthToutiaoRequest extends BaseAuthRequest {
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(AuthSource.TOUTIAO)
.build();
.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,可自行跳转页面
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize() {
return UrlBuilder.getToutiaoAuthorizeUrl(config.getClientId(), config.getRedirectUri());
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_key", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("auth_only", 1)
.queryParam("display", 0)
.queryParam("state", getRealState(state))
.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,171 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
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;
/**
* <p>
* 企业微信登录
* </p>
*
* @author yangkai.shen (https://xkcoding.com)
* @since 1.10.0
*/
public class AuthWeChatEnterpriseRequest extends AuthDefaultRequest {
public AuthWeChatEnterpriseRequest(AuthConfig config) {
super(config, AuthSource.WECHAT_ENTERPRISE);
}
public AuthWeChatEnterpriseRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.WECHAT_ENTERPRISE, authStateCache);
}
/**
* 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
*
* @param authCallback 回调返回的参数
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = doGetAuthorizationCode(accessTokenUrl(authCallback.getCode()));
JSONObject object = this.checkResponse(response);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.code(authCallback.getCode())
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject object = this.checkResponse(response);
// 返回 OpenId 或其他,均代表非当前企业用户,不支持
if (!object.containsKey("UserId")) {
throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM);
}
String userId = object.getString("UserId");
HttpResponse userDetailResponse = getUserDetail(authToken.getAccessToken(), userId);
JSONObject userDetail = this.checkResponse(userDetailResponse);
String gender = getRealGender(userDetail);
return AuthUser.builder()
.username(userDetail.getString("name"))
.nickname(userDetail.getString("alias"))
.avatar(userDetail.getString("avatar"))
.location(userDetail.getString("address"))
.email(userDetail.getString("email"))
.uuid(userId)
.gender(AuthUserGender.getRealGender(gender))
.token(authToken)
.source(source)
.build();
}
/**
* 校验请求结果
*
* @param response 请求结果
* @return 如果请求结果正常,则返回JSONObject
*/
private JSONObject checkResponse(HttpResponse response) {
JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("errcode") && object.getIntValue("errcode") != 0) {
throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg"));
}
return object;
}
/**
* 获取用户的实际性别,0表示未定义,1表示男性,2表示女性
*
* @param userDetail 用户详情
* @return 用户性别
*/
private String getRealGender(JSONObject userDetail) {
int gender = userDetail.getIntValue("gender");
if (AuthUserGender.MALE.getCode() == gender) {
return "1";
}
return 2 == gender ? "0" : null;
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("appid", config.getClientId())
.queryParam("agentid", config.getAgentId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(state))
.build();
}
/**
* 返回获取accessToken的url
*
* @param code 授权码
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("corpid", config.getClientId())
.queryParam("corpsecret", config.getClientSecret())
.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("code", authToken.getCode())
.build();
}
/**
* 用户详情
*
* @param accessToken accessToken
* @param userId 企业内用户id
* @return 用户详情
*/
private HttpResponse getUserDetail(String accessToken, String userId) {
String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
.queryParam("access_token", accessToken)
.queryParam("userid", userId)
.build();
return HttpRequest.get(userDetailUrl).execute();
}
}
@@ -3,79 +3,77 @@ package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
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 yangkai.shen (https://xkcoding.com)
* @version 1.0
* @since 1.8
* @since 1.1.0
*/
public class AuthWeChatRequest extends BaseAuthRequest {
public class AuthWeChatRequest extends AuthDefaultRequest {
public AuthWeChatRequest(AuthConfig config) {
super(config, AuthSource.WECHAT);
}
public AuthWeChatRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.WECHAT, authStateCache);
}
/**
* 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
*
* @param code 授权码
* @param authCallback 回调返回的参数
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(String code) {
String accessTokenUrl = UrlBuilder.getWeChatAccessTokenUrl(config.getClientId(), config.getClientSecret(), code);
return this.getToken(accessTokenUrl);
protected AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
String openId = authToken.getOpenId();
HttpResponse response = HttpRequest.get(UrlBuilder.getWeChatUserInfoUrl(accessToken, openId)).execute();
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"));
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(AuthSource.WECHAT)
.build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getWeChatAuthorizeUrl(config.getClientId(), config.getRedirectUri());
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) {
String refreshTokenUrl = UrlBuilder.getWeChatRefreshUrl(config.getClientId(), oldToken.getRefreshToken());
return AuthResponse.builder()
.code(ResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl))
.build();
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build();
}
/**
@@ -97,15 +95,79 @@ public class AuthWeChatRequest extends BaseAuthRequest {
*/
private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
JSONObject object = JSONObject.parseObject(response.body());
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(object);
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.refreshToken(object.getString("refresh_token"))
.expireIn(object.getIntValue("expires_in"))
.openId(object.getString("openid"))
.build();
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.openId(accessTokenObject.getString("openid"))
.build();
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
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(state))
.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();
}
}
@@ -3,12 +3,14 @@ package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
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.model.AuthUserGender;
import me.zhyd.oauth.utils.IpUtils;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
@@ -18,30 +20,32 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 微博登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class AuthWeiboRequest extends BaseAuthRequest {
public class AuthWeiboRequest extends AuthDefaultRequest {
public AuthWeiboRequest(AuthConfig config) {
super(config, AuthSource.WEIBO);
}
public AuthWeiboRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthSource.WEIBO, authStateCache);
}
@Override
protected AuthToken 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 weibo using code [" + code + "]:" + accessTokenObject.getString("error_description"));
throw new AuthException(accessTokenObject.getString("error_description"));
}
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.uid(accessTokenObject.getString("uid"))
.expireIn(accessTokenObject.getIntValue("remind_in"))
.build();
.accessToken(accessTokenObject.getString("access_token"))
.uid(accessTokenObject.getString("uid"))
.openId(accessTokenObject.getString("uid"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.build();
}
@Override
@@ -49,37 +53,41 @@ public class AuthWeiboRequest extends BaseAuthRequest {
String accessToken = authToken.getAccessToken();
String uid = authToken.getUid();
String oauthParam = String.format("uid=%s&access_token=%s", uid, accessToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getWeiboUserInfoUrl(oauthParam))
.header("Authorization", "OAuth2 " + oauthParam)
.header("API-RemoteIP", IpUtils.getIp())
.execute();
HttpResponse response = HttpRequest.get(userInfoUrl(authToken))
.header("Authorization", "OAuth2 " + oauthParam)
.header("API-RemoteIP", IpUtils.getLocalIp())
.execute();
String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo);
if (object.containsKey("error")) {
throw new AuthException(object.getString("error"));
}
return AuthUser.builder()
.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(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();
}
/**
* 返回认证url,可自行跳转页面
* 返回获取userInfo的url
*
* @return 返回授权地址
* @param authToken authToken
* @return 返回获取userInfo的url
*/
@Override
public String authorize() {
return UrlBuilder.getWeiboAuthorizeUrl(config.getClientId(), config.getRedirectUri());
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("uid", authToken.getUid())
.build();
}
}
@@ -1,62 +0,0 @@
package me.zhyd.oauth.request;
import lombok.Data;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.AuthConfigChecker;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @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, source)) {
throw new AuthException(ResponseStatus.PARAMETER_INCOMPLETE);
}
// 校验配置合法性
AuthConfigChecker.check(config, source);
}
protected abstract AuthToken getAccessToken(String code);
protected abstract AuthUser getUserInfo(AuthToken authToken);
@Override
public AuthResponse login(String code) {
try {
AuthToken authToken = this.getAccessToken(code);
AuthUser user = this.getUserInfo(authToken);
return AuthResponse.builder().code(ResponseStatus.SUCCESS.getCode()).data(user).build();
} catch (Exception e) {
return this.responseError(e);
}
}
private AuthResponse responseError(Exception e) {
int errorCode = ResponseStatus.FAILURE.getCode();
if (e instanceof AuthException) {
errorCode = ((AuthException) e).getErrorCode();
}
return AuthResponse.builder().code(errorCode).msg(e.getMessage()).build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public abstract String authorize();
}
@@ -2,17 +2,17 @@ package me.zhyd.oauth.utils;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.request.ResponseStatus;
import me.zhyd.oauth.model.AuthCallback;
/**
* 授权配置类的校验器
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.6.1-beta
*/
public class AuthConfigChecker {
public class AuthChecker {
/**
* 是否支持第三方登录
@@ -20,12 +20,19 @@ public class AuthConfigChecker {
* @param config config
* @param source source
* @return true or false
* @since 1.6.1-beta
*/
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());
}
if (isSupported && AuthSource.WECHAT_ENTERPRISE == source){
isSupported = StringUtils.isNotEmpty(config.getAgentId());
}
return isSupported;
}
@@ -34,19 +41,41 @@ public class AuthConfigChecker {
*
* @param config config
* @param source source
* @since 1.6.1-beta
*/
public static void check(AuthConfig config, AuthSource source) {
public static void checkConfig(AuthConfig config, AuthSource source) {
String redirectUri = config.getRedirectUri();
if (!GlobalAuthUtil.isHttpProtocol(redirectUri) && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) {
throw new AuthException(ResponseStatus.ILLEGAL_REDIRECT_URI);
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI);
}
// facebook的回调地址必须为https的链接
if (AuthSource.FACEBOOK == source && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) {
throw new AuthException(ResponseStatus.ILLEGAL_REDIRECT_URI);
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI);
}
// 支付宝在创建回调地址时不允许使用localhost或者127.0.0.1
if (AuthSource.ALIPAY == source && GlobalAuthUtil.isLocalHost(redirectUri)) {
throw new AuthException(ResponseStatus.ILLEGAL_REDIRECT_URI);
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI);
}
}
/**
* 校验回调传回的code
* <p>
* {@code v1.10.0}版本中改为传入{@code source}{@code callback}对于不同平台使用不同参数接受code的情况统一做处理
*
* @param source 当前授权平台
* @param callback 从第三方授权回调回来时传入的参数集合
* @since 1.8.0
*/
public static void checkCode(AuthSource source, AuthCallback callback) {
String code = callback.getCode();
if (source == AuthSource.ALIPAY) {
code = callback.getAuth_code();
} else if (source == AuthSource.HUAWEI) {
code = callback.getAuthorization_code();
}
if (StringUtils.isEmpty(code)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_CODE);
}
}
}
@@ -0,0 +1,19 @@
package me.zhyd.oauth.utils;
/**
* AuthState工具类,默认只提供一个创建随机uuid的方法
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.9.3
*/
public class AuthStateUtils {
/**
* 生成随机state,采用https://github.com/lets-mica/mica的UUID工具
*
* @return 随机的state字符串
*/
public static String createState() {
return UuidUtils.getUUID();
}
}
@@ -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,30 +11,25 @@ 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
* @since 1.0.0
*/
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 secretKey, String timestamp) {
try {
byte[] signData = sign(secretKey.getBytes(DEFAULT_ENCODING), timestamp.getBytes(DEFAULT_ENCODING));
return urlEncode(new String(Base64.encode(signData, false)));
} catch (UnsupportedEncodingException ex) {
throw new AuthException("Unsupported algorithm: " + DEFAULT_ENCODING, ex);
}
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) {
@@ -46,15 +44,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);
}
@@ -65,7 +62,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);
}
@@ -85,6 +82,26 @@ 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;
@@ -7,8 +7,7 @@ import java.net.UnknownHostException;
* 获取IP的工具类
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.0
* @since 1.0.0
*/
public class IpUtils {
@@ -17,7 +16,7 @@ public class IpUtils {
*
* @return ip
*/
public static String getIp() {
public static String getLocalIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
@@ -25,4 +24,4 @@ public class IpUtils {
return null;
}
}
}
}
@@ -1,9 +1,11 @@
package me.zhyd.oauth.utils;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
* @since 1.0.0
*/
public class StringUtils {
@@ -14,4 +16,24 @@ public class StringUtils {
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* 如果给定字符串{@code str}中不包含{@code appendStr},则在{@code str}后追加{@code appendStr}
* 如果已包含{@code appendStr},则在{@code str}后追加{@code otherwise}
*
* @param str 给定的字符串
* @param appendStr 需要追加的内容
* @param otherwise 当{@code appendStr}不满足时追加到{@code str}后的内容
* @return 追加后的字符串
*/
public static String appendIfNotContain(String str, String appendStr, String otherwise) {
if (isEmpty(str) || isEmpty(appendStr)) {
return str;
}
if (str.contains(appendStr)) {
return str.concat(otherwise);
}
return str.concat(appendStr);
}
}
+46 -790
View File
@@ -1,822 +1,78 @@
package me.zhyd.oauth.utils;
import me.zhyd.oauth.config.AuthSource;
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)
* @version 1.0
* @since 1.0
* @author yangkai.shen (https://xkcoding.com)
* @since 1.9.0
*/
@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 GOOGLE_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&scope=openid%20email%20profile&redirect_uri={2}&state={3}";
private static final String GOOGLE_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code";
private static final String GOOGLE_USER_INFO_PATTERN = "{0}?id_token={1}";
private UrlBuilder() {
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 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}";
private static final String ALIPAY_AUTHORIZE_PATTERN = "{0}?app_id={1}&scope=auth_user&redirect_uri={2}&state=init";
private static final String QQ_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}";
private static final String QQ_USER_INFO_PATTERN = "{0}?oauth_consumer_key={1}&access_token={2}&openid={3}";
private static final String QQ_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}";
private static final String QQ_OPENID_PATTERN = "{0}?access_token={1}&unionid=1";
private static final String WECHAT_AUTHORIZE_PATTERN = "{0}?appid={1}&redirect_uri={2}&response_type=code&scope=snsapi_login&state={3}#wechat_redirect";
private static final String WECHAT_ACCESS_TOKEN_PATTERN = "{0}?appid={1}&secret={2}&code={3}&grant_type=authorization_code";
private static final String WECHAT_REFRESH_TOKEN_PATTERN = "{0}?appid={1}&grant_type=refresh_token&refresh_token={2}";
private static final String WECHAT_USER_INFO_PATTERN = "{0}?access_token={1}&openid={2}&lang=zh_CN";
private static final String TAOBAO_AUTHORIZE_PATTERN = "{0}?response_type=code&client_id={1}&redirect_uri={2}&state={3}&view=web";
private static final String TAOBAO_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code";
private static final String FACEBOOK_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&state={3}&response_type=code&scope=";
private static final String FACEBOOK_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code";
private static final String FACEBOOK_USER_INFO_PATTERN = "{0}?access_token={1}&fields=id,name,birthday,gender,hometown,email,devices,picture.width(400)";
private static final String DOUYIN_AUTHORIZE_PATTERN = "{0}?client_key={1}&redirect_uri={2}&state={3}&response_type=code&scope=user_info";
private static final String DOUYIN_ACCESS_TOKEN_PATTERN = "{0}?client_key={1}&client_secret={2}&code={3}&grant_type=authorization_code";
private static final String DOUYIN_USER_INFO_PATTERN = "{0}?access_token={1}&open_id={2}";
private static final String DOUYIN_REFRESH_TOKEN_PATTERN = "{0}?client_key={1}&refresh_token={2}&grant_type=refresh_token";
private static final String LINKEDIN_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&state={3}&response_type=code&scope=r_liteprofile%20r_emailaddress%20w_member_social";
private static final String LINKEDIN_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code";
private static final String LINKEDIN_USER_INFO_PATTERN = "{0}?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))";
private static final String LINKEDIN_REFRESH_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&refresh_token={3}&grant_type=refresh_token";
private static final String MICROSOFT_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&response_mode=query&scope=offline_access%20user.read%20mail.read&state={3}";
private static final String MICROSOFT_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&scope=user.read%20mail.read&redirect_uri={3}&code={4}&grant_type=authorization_code";
private static final String MICROSOFT_USER_INFO_PATTERN = "{0}";
private static final String MICROSOFT_REFRESH_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&scope=user.read%20mail.read&redirect_uri={3}&refresh_token={4}&grant_type=refresh_token";
private static final String MI_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&response_type=code&scope=user/profile%20user/openIdV2%20user/phoneAndEmail&state={3}&skip_confirm=false";
private static final String MI_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&redirect_uri={3}&code={4}&grant_type=authorization_code";
private static final String MI_USER_INFO_PATTERN = "{0}?clientId={1}&token={2}";
private static final String MI_REFRESH_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&redirect_uri={3}&refresh_token={4}&grant_type=refresh_token";
private static final String TOUTIAO_ACCESS_TOKEN_PATTERN = "{0}?client_key={1}&client_secret={2}&code={3}&grant_type=authorize_code";
private static final String TOUTIAO_USER_INFO_PATTERN = "{0}?client_key={1}&access_token={2}";
private static final String TOUTIAO_AUTHORIZE_PATTERN = "{0}?client_key={1}&redirect_uri={2}&state={3}&response_type=code&auth_only=1&display=0";
/**
* 获取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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.BAIDU.authorize(), clientId, redirectUrl);
}
/**
* 获取收回baidu授权的地址
*
* @param accessToken baidu 授权登录后的token
* @return json
*/
public static String getBaiduRevokeUrl(String accessToken) {
return MessageFormat.format(BAIDU_REVOKE_PATTERN, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.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, AuthSource.TENCENT_CLOUD.accessToken(), clientId, clientSecret, code);
}
/**
* 获取腾讯云开发者平台用户详情的接口地址
*
* @param token coding 应用的token
* @return full url
*/
public static String getTencentCloudUserInfoUrl(String token) {
return MessageFormat.format(TENCENT_USER_INFO_PATTERN, AuthSource.TENCENT_CLOUD.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, AuthSource.TENCENT_CLOUD.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, AuthSource.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, AuthSource.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, AuthSource.OSCHINA.authorize(), clientId, redirectUrl);
}
/**
* 获取qq token的接口地址
*
* @param clientId qq 应用的App Key
* @param clientSecret qq 应用的App Secret
* @param code qq 授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getQqAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(QQ_ACCESS_TOKEN_PATTERN, AuthSource.QQ.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取qq用户详情的接口地址
*
* @param clientId qq 应用的clientId
* @param token qq 应用的token
* @param openId qq 应用的openId
* @return full url
*/
public static String getQqUserInfoUrl(String clientId, String token, String openId) {
return MessageFormat.format(QQ_USER_INFO_PATTERN, AuthSource.QQ.userInfo(), clientId, token, openId);
}
/**
* 获取qq授权地址
*
* @param clientId qq 应用的Client ID
* @param redirectUrl qq 应用授权成功后的回调地址
* @return full url
*/
public static String getQqAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(QQ_AUTHORIZE_PATTERN, AuthSource.QQ.authorize(), clientId, redirectUrl, System.currentTimeMillis());
}
/**
* 获取qq授权地址
*
* @param url 获取qqopenid的api接口地址
* @param token qq 应用授权的token
* @return full url
*/
public static String getQqOpenidUrl(String url, String token) {
return MessageFormat.format(QQ_OPENID_PATTERN, url, token);
}
/**
* 获取alipay授权地址
*
* @param clientId alipay 应用的Client ID
* @param redirectUrl alipay 应用授权成功后的回调地址
* @return full url
*/
public static String getAlipayAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(ALIPAY_AUTHORIZE_PATTERN, AuthSource.ALIPAY.authorize(), clientId, redirectUrl);
}
/**
* 获取微信 授权地址
*
* @param clientId 微信 应用的appid
* @param redirectUrl 微信 应用授权成功后的回调地址
* @return full url
*/
public static String getWeChatAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(WECHAT_AUTHORIZE_PATTERN, AuthSource.WECHAT.authorize(), clientId, redirectUrl, System
.currentTimeMillis());
}
/**
* 获取微信 token的接口地址
*
* @param clientId 微信 应用的appid
* @param clientSecret 微信 应用的secret
* @param code 微信 授权前的code,用来换token
* @return full url
*/
public static String getWeChatAccessTokenUrl(String clientId, String clientSecret, String code) {
return MessageFormat.format(WECHAT_ACCESS_TOKEN_PATTERN, AuthSource.WECHAT.accessToken(), clientId, clientSecret, code);
}
/**
* 获取微信 用户详情的接口地址
*
* @param token 微信 应用返回的 access token
* @param openId 微信 应用返回的openId
* @return full url
*/
public static String getWeChatUserInfoUrl(String token, String openId) {
return MessageFormat.format(WECHAT_USER_INFO_PATTERN, AuthSource.WECHAT.userInfo(), token, openId);
}
/**
* 获取微信 刷新令牌 地址
*
* @param clientId 微信 应用的appid
* @param refreshToken 微信 应用返回的刷新token
* @return full url
*/
public static String getWeChatRefreshUrl(String clientId, String refreshToken) {
return MessageFormat.format(WECHAT_REFRESH_TOKEN_PATTERN, AuthSource.WECHAT.refresh(), clientId, refreshToken);
}
/**
* 获取Taobao token的接口地址: 淘宝的授权登录,在这一步就会返回用户信息
*
* @param clientId taobao 应用的App Key
* @param clientSecret taobao 应用的App Secret
* @param code taobao 授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getTaobaoAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(TAOBAO_ACCESS_TOKEN_PATTERN, AuthSource.TAOBAO.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取Taobao授权地址
*
* @param clientId Taobao 应用的Client ID
* @param redirectUrl Taobao 应用授权成功后的回调地址
* @return full url
*/
public static String getTaobaoAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(TAOBAO_AUTHORIZE_PATTERN, AuthSource.TAOBAO.authorize(), clientId, redirectUrl, System
.currentTimeMillis());
}
/**
* 获取Google授权地址
*
* @param clientId google 应用的Client ID
* @param redirectUrl google 应用授权成功后的回调地址
* @return full url
*/
public static String getGoogleAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(GOOGLE_AUTHORIZE_PATTERN, AuthSource.GOOGLE.authorize(), clientId, redirectUrl, System
.currentTimeMillis());
}
/**
* 获取Google token的接口地址
*
* @param clientId google 应用的Client ID
* @param clientSecret google 应用的Client Secret
* @param code google 授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getGoogleAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(GOOGLE_ACCESS_TOKEN_PATTERN, AuthSource.GOOGLE.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取Google用户详情的接口地址
*
* @param token google 应用的token
* @return full url
*/
public static String getGoogleUserInfoUrl(String token) {
return MessageFormat.format(GOOGLE_USER_INFO_PATTERN, AuthSource.GOOGLE.userInfo(), token);
}
/**
* 获取Facebook授权地址
*
* @param clientId Facebook 应用的Client ID
* @param redirectUrl Facebook 应用授权成功后的回调地址
* @return full url
*/
public static String getFacebookAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(FACEBOOK_AUTHORIZE_PATTERN, AuthSource.FACEBOOK.authorize(), clientId, redirectUrl, System
.currentTimeMillis());
}
/**
* 获取Facebook token的接口地址
*
* @param clientId Facebook 应用的Client ID
* @param clientSecret Facebook 应用的Client Secret
* @param code Facebook 授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/
public static String getFacebookAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) {
return MessageFormat.format(FACEBOOK_ACCESS_TOKEN_PATTERN, AuthSource.FACEBOOK.accessToken(), clientId, clientSecret, code, redirectUri);
}
/**
* 获取Facebook用户详情的接口地址
*
* @param token Facebook 应用的token
* @return full url
*/
public static String getFacebookUserInfoUrl(String token) {
return MessageFormat.format(FACEBOOK_USER_INFO_PATTERN, AuthSource.FACEBOOK.userInfo(), token);
}
/**
* 获取Douyin授权地址
*
* @param clientId Douyin 应用的Client ID
* @param redirectUrl Douyin 应用授权成功后的回调地址
* @return full url
*/
public static String getDouyinAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(DOUYIN_AUTHORIZE_PATTERN, AuthSource.DOUYIN.authorize(), clientId, redirectUrl, System
.currentTimeMillis());
}
/**
* 获取Douyin token的接口地址
*
* @param clientId Douyin 应用的Client ID
* @param clientSecret Douyin 应用的Client Secret
* @param code Douyin 授权前的code,用来换token
* @return full url
*/
public static String getDouyinAccessTokenUrl(String clientId, String clientSecret, String code) {
return MessageFormat.format(DOUYIN_ACCESS_TOKEN_PATTERN, AuthSource.DOUYIN.accessToken(), clientId, clientSecret, code);
}
/**
* 获取Douyin用户详情的接口地址
*
* @param token Douyin 应用的token
* @param openId 用户在当前应用的唯一标识 通过token接口获取
* @return full url
*/
public static String getDouyinUserInfoUrl(String token, String openId) {
return MessageFormat.format(DOUYIN_USER_INFO_PATTERN, AuthSource.DOUYIN.userInfo(), token, openId);
}
/**
* 获取Douyin 刷新令牌 地址
*
* @param clientId Douyin 应用的client_key
* @param refreshToken Douyin 应用返回的refresh_token
* @return full url
*/
public static String getDouyinRefreshUrl(String clientId, String refreshToken) {
return MessageFormat.format(DOUYIN_REFRESH_TOKEN_PATTERN, AuthSource.DOUYIN.refresh(), clientId, refreshToken);
}
/**
* 获取Linkedin授权地址
*
* @param clientId Linkedin 应用的Client ID
* @param redirectUrl Linkedin 应用授权成功后的回调地址
* @return full url
*/
public static String getLinkedinAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(LINKEDIN_AUTHORIZE_PATTERN, AuthSource.LINKEDIN.authorize(), clientId, redirectUrl, System
.currentTimeMillis());
}
/**
* 获取Linkedin token的接口地址
*
* @param clientId Linkedin 应用的Client ID
* @param clientSecret Linkedin 应用的Client Secret
* @param code Linkedin 授权前的code,用来换token
* @param redirectUrl Linkedin 应用授权成功后的回调地址
* @return full url
*/
public static String getLinkedinAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUrl) {
return MessageFormat.format(LINKEDIN_ACCESS_TOKEN_PATTERN, AuthSource.LINKEDIN.accessToken(), clientId, clientSecret, code, redirectUrl);
}
/**
* 获取Linkedin用户详情的接口地址
*
* @return full url
*/
public static String getLinkedinUserInfoUrl() {
return MessageFormat.format(LINKEDIN_USER_INFO_PATTERN, AuthSource.LINKEDIN.userInfo());
}
/**
* 获取Linkedin 刷新令牌 地址
*
* @param clientId Linkedin 应用的client_key
* @param clientSecret Linkedin 应用的Client Secret
* @param refreshToken Linkedin 应用返回的refresh_token
* @return full url
*/
public static String getLinkedinRefreshUrl(String clientId, String clientSecret, String refreshToken) {
return MessageFormat.format(LINKEDIN_REFRESH_TOKEN_PATTERN, AuthSource.LINKEDIN.refresh(), clientId, clientSecret, refreshToken);
}
/**
* 获取微软授权地址
*
* @param clientId 微软 应用的Client ID
* @param redirectUrl 微软 应用授权成功后的回调地址
* @return full url
*/
public static String getMicrosoftAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(MICROSOFT_AUTHORIZE_PATTERN, AuthSource.MICROSOFT.authorize(), clientId, redirectUrl, System
.currentTimeMillis());
}
/**
* 获取微软 token的接口地址
*
* @param clientId 微软 应用的Client ID
* @param clientSecret 微软 应用的Client Secret
* @param redirectUrl 微软 应用授权成功后的回调地址
* @param code 微软 授权前的code,用来换token
* @return full url
*/
public static String getMicrosoftAccessTokenUrl(String clientId, String clientSecret, String redirectUrl, String code) {
return MessageFormat.format(MICROSOFT_ACCESS_TOKEN_PATTERN, AuthSource.MICROSOFT.accessToken(), clientId, clientSecret, redirectUrl, code);
}
/**
* 获取微软用户详情的接口地址
*
* @return full url
*/
public static String getMicrosoftUserInfoUrl() {
return MessageFormat.format(MICROSOFT_USER_INFO_PATTERN, AuthSource.MICROSOFT.userInfo());
}
/**
* 获取微软 刷新令牌 地址
*
* @param clientId 微软 应用的client_key
* @param clientSecret 微软 应用的Client Secret
* @param redirectUrl 微软 应用授权成功后的回调地址
* @param refreshToken 微软 应用返回的refresh_token
* @return full url
*/
public static String getMicrosoftRefreshUrl(String clientId, String clientSecret, String redirectUrl, String refreshToken) {
return MessageFormat.format(MICROSOFT_REFRESH_TOKEN_PATTERN, AuthSource.MICROSOFT.refresh(), clientId, clientSecret, redirectUrl, refreshToken);
}
/**
* 获取小米授权地址
*
* @param clientId 小米 应用的Client ID
* @param redirectUrl 小米 应用授权成功后的回调地址
* @return full url
*/
public static String getMiAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(MI_AUTHORIZE_PATTERN, AuthSource.MI.authorize(), clientId, redirectUrl, System.currentTimeMillis());
}
/**
* 获取小米 token的接口地址
*
* @param clientId 小米 应用的Client ID
* @param clientSecret 小米 应用的Client Secret
* @param redirectUrl 小米 应用授权成功后的回调地址
* @param code 小米 授权前的code,用来换token
* @return full url
*/
public static String getMiAccessTokenUrl(String clientId, String clientSecret, String redirectUrl, String code) {
return MessageFormat.format(MI_ACCESS_TOKEN_PATTERN, AuthSource.MI.accessToken(), clientId, clientSecret, redirectUrl, code);
}
/**
* 获取小米用户详情的接口地址
*
* @param clientId 小米 应用的client_key
* @param token token
* @return full url
*/
public static String getMiUserInfoUrl(String clientId, String token) {
return MessageFormat.format(MI_USER_INFO_PATTERN, AuthSource.MI.userInfo(), clientId, token);
}
/**
* 获取小米 刷新令牌 地址
*
* @param clientId 小米 应用的client_key
* @param clientSecret 小米 应用的Client Secret
* @param redirectUrl 小米 应用授权成功后的回调地址
* @param refreshToken 小米 应用返回的refresh_token
* @return full url
*/
public static String getMiRefreshUrl(String clientId, String clientSecret, String redirectUrl, String refreshToken) {
return MessageFormat.format(MI_REFRESH_TOKEN_PATTERN, AuthSource.MI.refresh(), clientId, clientSecret, redirectUrl, refreshToken);
}
/**
* 获取今日头条授权地址
*
* @param clientId 今日头条 应用的Client ID
* @param redirectUrl 今日头条 应用授权成功后的回调地址
* @return full url
*/
public static String getToutiaoAuthorizeUrl(String clientId, String redirectUrl) {
return MessageFormat.format(TOUTIAO_AUTHORIZE_PATTERN, AuthSource.TOUTIAO.authorize(), clientId, redirectUrl, System
.currentTimeMillis());
}
/**
* 获取今日头条 token的接口地址
*
* @param clientId 今日头条 应用的Client ID
* @param clientSecret 今日头条 应用的Client Secret
* @param code 今日头条 授权前的code,用来换token
* @return full url
*/
public static String getToutiaoAccessTokenUrl(String clientId, String clientSecret, String code) {
return MessageFormat.format(TOUTIAO_ACCESS_TOKEN_PATTERN, AuthSource.TOUTIAO.accessToken(), clientId, clientSecret, code);
}
/**
* 获取今日头条用户详情的接口地址
*
* @param clientId 今日头条 应用的client_key
* @param token token
* @return full url
*/
public static String getToutiaoUserInfoUrl(String clientId, String token) {
return MessageFormat.format(TOUTIAO_USER_INFO_PATTERN, AuthSource.TOUTIAO.userInfo(), clientId, token);
public String build(boolean encode) {
if (MapUtil.isEmpty(this.params)) {
return this.baseUrl;
}
String baseUrl = StringUtils.appendIfNotContain(this.baseUrl, "?", "&");
String paramString = GlobalAuthUtil.parseMapToString(this.params, encode);
return baseUrl + paramString;
}
}
@@ -0,0 +1,65 @@
package me.zhyd.oauth.utils;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;
/**
* 高性能的创建UUID的工具类,https://github.com/lets-mica/mica
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.9.3
*/
public class UuidUtils {
/**
* All possible chars for representing a number as a String
* copy from micahttps://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/NumberUtil.java#L113
*/
private final static byte[] DIGITS = {
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z'
};
/**
* 生成uuid,采用 jdk 9 的形式,优化性能
* copy from micahttps://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L335
* <p>
* 关于mica uuid生成方式的压测结果,可以参考:https://github.com/lets-mica/mica-jmh/wiki/uuid
*
* @return UUID
*/
public static String getUUID() {
ThreadLocalRandom random = ThreadLocalRandom.current();
long lsb = random.nextLong();
long msb = random.nextLong();
byte[] buf = new byte[32];
formatUnsignedLong(lsb, buf, 20, 12);
formatUnsignedLong(lsb >>> 48, buf, 16, 4);
formatUnsignedLong(msb, buf, 12, 4);
formatUnsignedLong(msb >>> 16, buf, 8, 4);
formatUnsignedLong(msb >>> 32, buf, 0, 8);
return new String(buf, StandardCharsets.UTF_8);
}
/**
* copy from micahttps://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L348
*/
private static void formatUnsignedLong(long val, byte[] buf, int offset, int len) {
int charPos = offset + len;
int radix = 1 << 4;
int mask = radix - 1;
do {
buf[--charPos] = DIGITS[((int) val) & mask];
val >>>= 4;
} while (charPos > offset);
}
}
+212 -114
View File
@@ -1,196 +1,294 @@
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
* @since 1.8
*/
public class AuthRequestTest {
@Test
public void giteeTest() {
AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback());
}
@Test
public void githubTest() {
AuthRequest authRequest = new AuthGithubRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback());
}
@Test
public void weiboTest() {
AuthRequest authRequest = new AuthWeiboRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback());
}
@Test
public void dingdingTest() {
AuthRequest authRequest = new AuthDingTalkRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback());
}
@Test
public void baiduTest() {
AuthRequest authRequest = new AuthBaiduRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback());
}
@Test
public void codingTest() {
AuthRequest authRequest = new AuthCodingRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback());
}
@Test
public void tencentCloudTest() {
AuthRequest authRequest = new AuthTencentCloudRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback());
}
@Test
public void oschinaTest() {
AuthRequest authRequest = new AuthOschinaRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback());
}
@Test
public void alipayTest() {
AuthRequest authRequest = new AuthAlipayRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.alipayPublicKey("publicKey")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void qqTest() {
AuthRequest authRequest = new AuthQqRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
AuthResponse login = authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void wechatTest() {
AuthRequest authRequest = new AuthWeChatRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
AuthResponse login = authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void taobaoTest() {
AuthRequest authRequest = new AuthTaobaoRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void googleTest() {
AuthRequest authRequest = new AuthGoogleRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
AuthResponse login = authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void facebookTest() {
AuthRequest authRequest = new AuthFacebookRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
AuthResponse login = authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("https://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void douyinTest() {
AuthRequest authRequest = new AuthDouyinRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void linkedinTest() {
AuthRequest authRequest = new AuthLinkedinRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void microsoftTest() {
AuthRequest authRequest = new AuthMicrosoftRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
AuthResponse login = authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void miTest() {
AuthRequest authRequest = new AuthMiRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行调整
String url = authRequest.authorize();
// 授权登录后会返回一个code,用这个code进行登录
AuthResponse login = authRequest.login("code");
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void toutiaoTest() {
AuthRequest authRequest = new AuthToutiaoRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("http://redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
@Test
public void huaweiTest() {
AuthRequest authRequest = new AuthHuaweiRequest(AuthConfig.builder()
.clientId("clientId")
.clientSecret("clientSecret")
.redirectUri("redirectUri")
.build());
// 返回授权页面,可自行跳转
authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback());
}
}
@@ -0,0 +1,32 @@
package me.zhyd.oauth.cache;
import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
public class AuthStateCacheTest {
@Test
public void cache1() throws InterruptedException {
AuthDefaultStateCache.INSTANCE.cache("key", "value");
Assert.assertEquals(AuthDefaultStateCache.INSTANCE.get("key"), "value");
TimeUnit.MILLISECONDS.sleep(4);
Assert.assertEquals(AuthDefaultStateCache.INSTANCE.get("key"), "value");
}
@Test
public void cache2() throws InterruptedException {
AuthDefaultStateCache.INSTANCE.cache("key", "value", 10);
Assert.assertEquals(AuthDefaultStateCache.INSTANCE.get("key"), "value");
// 没过期
TimeUnit.MILLISECONDS.sleep(5);
Assert.assertEquals(AuthDefaultStateCache.INSTANCE.get("key"), "value");
// 过期
TimeUnit.MILLISECONDS.sleep(6);
Assert.assertNull(AuthDefaultStateCache.INSTANCE.get("key"));
}
}
@@ -0,0 +1,111 @@
package me.zhyd.oauth.log;
import org.junit.Test;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @date 2019/8/2 19:36
* @since 1.8
*/
public class LogTest {
public static void main(String[] args) {
// 测试正常打印
Log.debug("[1] This is a test.");
Log.debug("[1] This is a test.", new NullPointerException("npe"));
Log.warn("[1] This is a test.");
Log.warn("[1] This is a test.", new NullPointerException("npe"));
Log.error("[1] This is a test.");
Log.error("[1] This is a test.", new NullPointerException("npe"));
// 测试只打印 error级别的日志
Log.Config.level = Log.Level.ERROR;
Log.debug("[2] This is a test.");
Log.warn("[2] This is a test.");
Log.error("[2] This is a test.");
// 测试关闭日志
Log.Config.enable = false;
Log.debug("[3] This is a test.");
Log.warn("[3] This is a test.");
Log.error("[3] This is a test.");
}
/**
* 1000000: 23135ms
* 100000: 3016ms
* 10000: 328ms
* 1000: 26ms
*/
@Test
public void testByThread() {
long start = System.currentTimeMillis();
for (int i = 0; i < 1; i++) {
System.out.println(callMethodByThread());
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
/**
* 1000000: 19058ms
* 100000: 2772ms
* 10000: 323ms
* 1000: 29ms
*/
@Test
public void testByThrowable() {
long end = System.currentTimeMillis();
for (int i = 0; i < 1; i++) {
System.out.println(callMethodByThrowable());
}
long end2 = System.currentTimeMillis();
System.out.println((end2 - end) + "ms");
}
@Test
public void testBySecurityManager() {
long end = System.currentTimeMillis();
for (int i = 0; i < 1; i++) {
System.out.println(callMethodBySecurityManager());
}
long end2 = System.currentTimeMillis();
System.out.println((end2 - end) + "ms");
}
private String callMethodByThread() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
System.out.println(stackTraceElement.getMethodName());
}
return stackTrace[2].getMethodName();
}
private String callMethodByThrowable() {
StackTraceElement[] stackTrace = (new Throwable()).getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
System.out.println(stackTraceElement.getMethodName());
}
return stackTrace[2].getMethodName();
}
private String callMethodBySecurityManager() {
return new SecurityManager() {
String getClassName() {
for (Class clazz : getClassContext()) {
System.out.println(clazz);
}
return getClassContext()[0].getName();
}
}.getClassName();
}
}
@@ -0,0 +1,41 @@
package me.zhyd.oauth.sdk;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.10.0
*/
public class ThirdPartSdkTest {
@Test
public void huawei() {
String code = "CF1IwmFc6uZABI9Y795BkhXfvHidIFFw04I4Zc4KML4n+vlXxwNUcQKS4xlopjFDpEk6LzQbjwdTNxvjZ9jqnd/1m5nswhx8X7e0/dL2kyGAMVZWFgVq9ClxNN18b+Z0xtfJjkm7bDnfC3W5h4COgTCoLSjiWKSHWp5hCunp6pQRo1FHovZXm13TLNlhF9mCVtJx3kTQ";
HttpResponse response = HttpRequest.post("https://oauth-login.cloud.huawei.com/oauth2/v2/token")
.form("grant_type", "authorization_code")
.form("code", code)
.form("client_id", "100994535")
.form("client_secret", "22aea400bef603fef26d15a79c806eb477b35de0a529758f2a3b1bda32bfb80d")
.form("redirect_uri", "http://127.0.0.1:8443/oauth/callback/huawei")
.execute();
System.out.println(response.body());
// {"access_token":"accessToken","expires_in":3600,"refresh_token":"refreshToken","scope":"https:\/\/www.huawei.com\/auth\/account\/base.profile","token_type":"Bearer"}
//
HttpResponse response2 = HttpRequest.post("https://api.vmall.com/rest.php")
.form("nsp_ts", System.currentTimeMillis())
.form("access_token", JSONObject.parseObject(response.body()).getString("access_token"))
.form("nsp_fmt", "JS")
// .form("nsp_cb", "_jqjsp")
.form("nsp_svc", "OpenUP.User.getInfo")
.execute();
System.out.println(response2.body());
// 华为性别 0是男,女是1
// {"gender":1,"headPictureURL":"https://upfile-drcn.platform.hicloud.com/FileServer/image/b.0260086000226601572.20190415065228.iBKdTsqaNkdPXSz4N7pIRWAgeu45ec3k.1000.9A5467309F9284B267ECA33B59D3D7DA4A71BC732D3BB24EC6B880A73DEE9BAB.jpg","languageCode":"zh-CN","userID":"260086000226601572","userName":"151****2326","userState":1,"userValidStatus":1}
}
}
@@ -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,37 @@
package me.zhyd.oauth.utils;
import com.alibaba.fastjson.JSON;
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;
/**
* JsonPath用法测试
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
*/
public class JsonPathTest {
@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,94 @@
package me.zhyd.oauth.utils;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class StringUtilsTest {
@Rule
public final ExpectedException thrown =
ExpectedException.none();
@Test
public void isEmptyNonEmptyInput() {
Assert.assertFalse(StringUtils.isEmpty("non-empty string"));
}
@Test
public void isEmptyEmptyInput() {
Assert.assertTrue(StringUtils.isEmpty(""));
}
@Test
public void isEmptyInputNull() {
Assert.assertTrue(StringUtils.isEmpty(null));
}
@Test
public void isNotEmptyNonEmptyInput() {
Assert.assertTrue(StringUtils.isNotEmpty("non-empty string"));
}
@Test
public void isNotEmptyEmptyInput() {
Assert.assertFalse(StringUtils.isNotEmpty(""));
}
@Test
public void isNotEmptyInputNull() {
Assert.assertFalse(StringUtils.isNotEmpty(null));
}
@Test
public void appendIfNotContainAppendedStringNotPresent() {
// (Check the case where appendStr doesn't occur in str)
final String str = "Prefix ";
final String appendStr = "suffix";
final String otherwise = "should be discarded";
final String result =
StringUtils.appendIfNotContain(str, appendStr, otherwise);
Assert.assertEquals("Prefix suffix", result);
}
@Test
public void appendIfNotContainAppendedStringPresent() {
// (Check the case where appendStr occurs in str)
final String str = "Prefix ";
final String appendStr = "Prefix";
final String otherwise = "should be appended";
final String result =
StringUtils.appendIfNotContain(str, appendStr, otherwise);
Assert.assertEquals("Prefix should be appended", result);
}
@Test
public void appendIfNotContainEmptyString() {
// (Check the special-case for str being empty)
final String str = "";
final String appendStr = "should not be appended";
final String otherwise = "should also not be appended";
final String result =
StringUtils.appendIfNotContain(str, appendStr, otherwise);
Assert.assertEquals("", result);
}
@Test
public void appendIfNotContainAppendingEmptyString() {
// (Check the special-case for appendStr being empty)
final String str = "should be kept";
final String appendStr = "";
final String otherwise = "should also not be appended";
final String result =
StringUtils.appendIfNotContain(str, appendStr, otherwise);
Assert.assertEquals("should be kept", result);
}
}
@@ -0,0 +1,63 @@
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")
.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", "")
.build(false);
System.out.println(build);
AuthWeChatRequest request = new AuthWeChatRequest(config);
String authorize = request.authorize("state");
System.out.println(authorize);
}
@Test
public void build() {
String url = UrlBuilder.fromBaseUrl("https://www.zhyd.me")
.queryParam("name", "yadong.zhang")
.build();
Assert.assertEquals(url, "https://www.zhyd.me?name=yadong.zhang");
url = UrlBuilder.fromBaseUrl(url)
.queryParam("github", "https://github.com/zhangyd-c")
.build();
Assert.assertEquals(url, "https://www.zhyd.me?name=yadong.zhang&github=https://github.com/zhangyd-c");
}
@Test
public void build1() {
String url = UrlBuilder.fromBaseUrl("https://www.zhyd.me")
.queryParam("name", "yadong.zhang")
.build(true);
Assert.assertEquals(url, "https://www.zhyd.me?name=yadong.zhang");
url = UrlBuilder.fromBaseUrl(url)
.queryParam("github", "https://github.com/zhangyd-c")
.build(true);
Assert.assertEquals(url, "https://www.zhyd.me?name=yadong.zhang&github=https%3A%2F%2Fgithub.com%2Fzhangyd-c");
}
}
@@ -0,0 +1,13 @@
package me.zhyd.oauth.utils;
import org.junit.Test;
public class UuidUtilsTest {
@Test
public void getUUID() {
String uuid = UuidUtils.getUUID();
System.out.println(uuid);
}
}
-53
View File
@@ -1,53 +0,0 @@
### 2019/06/19
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
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
1. 增加今日头条的授权登陆
2. 发布1.6.0-beta版本,今日头条开发者暂时不能认证, 所以无法做测试,等测试通过后,正式发布release版本
### 2019/05/28
1. 增加小米账号和微软的授权登陆
2. 发布1.5.0版本
### 2019/05/26
1. 增加抖音和Linkedin的授权登陆
2. 修改部分图片命名
3. 优化部分代码
4. 修复`AuthSource`中腾讯云开发平台的拼写错误:`TENCEN_CLOUD`->`TENCENT_CLOUD`
5. 修复支付宝登陆时用户名为空的问题
### 2019/05/24
1. 修复一些问题
2. 升级api,在AuthUser中增加`uuid`属性,可以通过`uuid` + `source`唯一确定一个用户,此举解决了用户身份归属的问题。
3. 发布1.3.3版本的jar包到公开仓库(1.3.2忘记发布了,( ╯□╰ ))
4. 重要:经咨询官方客服得知,CSDN的授权开放平台已经下线,如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了
### 2019/05/23
1. 修复QQ登录的问题
2. 发布1.3.1版本的jar包到公开仓库
### 2019/05/21
1. 新增google授权登录
2. 新增facebook授权登录
3. 发布1.3.0版本的jar包到公开仓库
### 2019/05/18
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方法的返回值