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

Compare commits

...

86 Commits

Author SHA1 Message Date
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
64 changed files with 3145 additions and 2940 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
+30 -8
View File
@@ -6,7 +6,7 @@
</p> </p>
<p align="center"> <p align="center">
<a target="_blank" href="https://search.maven.org/search?q=JustAuth"> <a target="_blank" href="https://search.maven.org/search?q=JustAuth">
<img src="https://img.shields.io/badge/Maven Central-1.8.1-blue.svg" ></img> <img src="https://img.shields.io/badge/Maven Central-1.9.3-blue.svg" ></img>
</a> </a>
<a target="_blank" href="https://gitee.com/yadong.zhang/JustAuth/blob/master/LICENSE"> <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> <img src="https://img.shields.io/apm/l/vim-mode.svg?color=yellow" ></img>
@@ -15,7 +15,7 @@
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" ></img> <img src="https://img.shields.io/badge/JDK-1.8+-green.svg" ></img>
</a> </a>
<a target="_blank" href="https://apidoc.gitee.com/yadong.zhang/JustAuth/"> <a target="_blank" href="https://apidoc.gitee.com/yadong.zhang/JustAuth/">
<img src="https://img.shields.io/badge/Docs-1.8.1-orange.svg" ></img> <img src="https://img.shields.io/badge/Docs-1.9.3-orange.svg" ></img>
</a> </a>
</p> </p>
@@ -37,10 +37,18 @@
<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="#授权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="#授权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> <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/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/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/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="#授权今日头条"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/toutiao.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权Teambition"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/teambition.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权人人"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/renren.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权Pinterest"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/pinterest.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权Stack Overflow"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/stackoverflow.png" width="20"></a></td>
<td align="center" width="200"><a href="#授权csdn"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/csdn.png" width="20"></a></td> <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> </tr>
</table> </table>
@@ -68,7 +76,7 @@ JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具
<dependency> <dependency>
<groupId>me.zhyd.oauth</groupId> <groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId> <artifactId>JustAuth</artifactId>
<version>1.8.1</version> <version>1.9.3</version>
</dependency> </dependency>
``` ```
- 调用api - 调用api
@@ -78,17 +86,27 @@ AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state")
.build()); .build());
// 生成授权页面 // 生成授权页面
authRequest.authorize(); authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的参数 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的参数
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(callback); authRequest.login(callback);
``` ```
注:`1.8.0`版本后,增加了`state`参数校验,用于防止[CSRF](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)。强烈建议,保证单次流程内`state`的唯一性,且每个`state`只可用一次。 **配套Demo**
- [Springboot版](https://gitee.com/yadong.zhang/JustAuth-demo)
- [jFinal版](https://github.com/xkcoding/jfinal-justauth-demo)
- [ActFramework版](https://github.com/xkcoding/act-justauth-demo)
**配套Demo**[JustAuth-demo](https://gitee.com/yadong.zhang/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)
具体的例子可以参考: 具体的例子可以参考:
@@ -118,6 +136,10 @@ authRequest.login(callback);
| <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/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/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/toutiao.png" width="20"> | [AuthToutiaoRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java) | <a href="https://open.mp.toutiao.com/#/resource?_k=y7mfgk" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/teambition.png" width="20"> | [AuthTeambitionRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java) | <a href="https://docs.teambition.com/" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/renren.png" width="20"> | [AuthRenrenRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java) | <a href="http://open.renren.com/wiki/OAuth2.0" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/pinterest.png" width="20"> | [AuthPinterestRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java) | <a href="https://developers.pinterest.com/docs/api/overview/?" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/stackoverflow.png" width="20"> | [AuthStackOverflowRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java) | <a href="https://api.stackexchange.com/docs/authentication" target="_blank">参考文档</a> |
| <img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/csdn.png" width="20"> | [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 无 | | <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登录只能针对少部分用户使用了_ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_
@@ -134,7 +156,7 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经
2. 把fork过去的项目也就是你仓库中的项目clone到你的本地 2. 把fork过去的项目也就是你仓库中的项目clone到你的本地
3. 修改代码 3. 修改代码
4. commit后push到自己的库 4. commit后push到自己的库
5. 发起PRpull request 请求 5. 发起PRpull request 请求,提交到`dev`分支
6. 等待作者合并 6. 等待作者合并
## 致谢 ## 致谢
@@ -166,4 +188,4 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经
| 支付宝 | 微信 | | 支付宝 | 微信 |
| :------------: | :------------: | | :------------: | :------------: |
| <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://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://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://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>
- 千年等一回,我只为等你... - 千年等一回,我只为等你...
+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官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_
+181 -175
View File
@@ -2,187 +2,193 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" <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"> 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> <groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId> <artifactId>JustAuth</artifactId>
<version>1.8.1</version> <version>1.9.3</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> <url>https://gitee.com/yadong.zhang/JustAuth</url>
<description> </scm>
史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。
Login, so easy!
</description>
<licenses> <developers>
<license> <developer>
<name>MIT</name> <name>Yadong.Zhang</name>
<url>https://gitee.com/yadong.zhang/JustAuth/blob/master/LICENSE</url> <email>yadong.zhang0415@gmail.com</email>
</license> <url>https://www.zhyd.me</url>
</licenses> </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> <properties>
<connection>scm:git:https://gitee.com/yadong.zhang/JustAuth.git</connection> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<developerConnection>scm:git:https://gitee.com/yadong.zhang/JustAuth.git</developerConnection> <java.version>1.8</java.version>
<url>https://gitee.com/yadong.zhang/JustAuth</url> <maven.compiler.source>1.8</maven.compiler.source>
</scm> <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.6.0</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>
<slf4j-version>1.7.25</slf4j-version>
</properties>
<developers> <dependencies>
<developer> <dependency>
<name>yadong.zhang</name> <groupId>org.projectlombok</groupId>
<email>yadong.zhang0415@gmail.com</email> <artifactId>lombok</artifactId>
<url>https://www.zhyd.me</url> <version>${lombok-version}</version>
</developer> <optional>true</optional>
<developer> </dependency>
<name>Yangkai.Shen</name> <dependency>
<email>shenyangkai1994@gmail.com</email> <groupId>cn.hutool</groupId>
<url>https://xkcoding.com</url> <artifactId>hutool-http</artifactId>
</developer> <version>${hutool-version}</version>
</developers> </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>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j-version}</version>
</dependency>
</dependencies>
<properties> <build>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <finalName>${project.artifactId}-${project.version}</finalName>
<java.version>1.8</java.version> <plugins>
<maven.compiler.source>1.8</maven.compiler.source> <plugin>
<maven.compiler.target>1.8</maven.compiler.target> <groupId>org.apache.maven.plugins</groupId>
<maven-source.version>2.2.1</maven-source.version> <artifactId>maven-compiler-plugin</artifactId>
<maven-compiler.version>3.7.0</maven-compiler.version> <version>${maven-compiler.version}</version>
<maven.test.skip>true</maven.test.skip> <configuration>
<hutool-version>4.5.15</hutool-version> <encoding>${project.build.sourceEncoding}</encoding>
<lombok-version>1.18.4</lombok-version> <source>${java.version}</source>
<junit-version>4.11</junit-version> <target>${java.version}</target>
<fastjson-version>1.2.44</fastjson-version> </configuration>
<alipay-sdk-version>3.7.4.ALL</alipay-sdk-version> </plugin>
<slf4j-version>1.7.25</slf4j-version> <plugin>
</properties> <artifactId>maven-source-plugin</artifactId>
<version>${maven-source.version}</version>
<dependencies> <inherited>true</inherited>
<dependency> <executions>
<groupId>org.projectlombok</groupId> <execution>
<artifactId>lombok</artifactId> <phase>package</phase>
<version>${lombok-version}</version> <goals>
</dependency> <goal>jar-no-fork</goal>
<dependency> </goals>
<groupId>cn.hutool</groupId> </execution>
<artifactId>hutool-http</artifactId> </executions>
<version>${hutool-version}</version> </plugin>
</dependency> <plugin>
<dependency> <groupId>org.apache.maven.plugins</groupId>
<groupId>junit</groupId> <artifactId>maven-javadoc-plugin</artifactId>
<artifactId>junit</artifactId> </plugin>
<version>${junit-version}</version> <plugin>
<scope>test</scope> <groupId>org.apache.maven.plugins</groupId>
</dependency> <artifactId>maven-gpg-plugin</artifactId>
<dependency> </plugin>
<groupId>com.alibaba</groupId> </plugins>
<artifactId>fastjson</artifactId> </build>
<version>${fastjson-version}</version> <profiles>
</dependency> <profile>
<dependency> <id>release</id>
<groupId>com.alipay.sdk</groupId> <build>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay-sdk-version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j-version}</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>${maven-compiler.version}</version> <version>${maven-source.version}</version>
<configuration> <inherited>true</inherited>
<encoding>${project.build.sourceEncoding}</encoding> <executions>
<source>${java.version}</source> <execution>
<target>${java.version}</target> <phase>package</phase>
</configuration> <goals>
</plugin> <goal>jar-no-fork</goal>
<plugin> </goals>
<artifactId>maven-source-plugin</artifactId> </execution>
<version>${maven-source.version}</version> </executions>
<inherited>true</inherited> </plugin>
<executions> <plugin>
<execution> <groupId>org.apache.maven.plugins</groupId>
<phase>package</phase> <artifactId>maven-javadoc-plugin</artifactId>
<goals> <executions>
<goal>jar-no-fork</goal> <execution>
</goals> <phase>package</phase>
</execution> <goals>
</executions> <goal>jar</goal>
</plugin> </goals>
<plugin> </execution>
<groupId>org.apache.maven.plugins</groupId> </executions>
<artifactId>maven-javadoc-plugin</artifactId> </plugin>
</plugin> <plugin>
<plugin> <groupId>org.apache.maven.plugins</groupId>
<groupId>org.apache.maven.plugins</groupId> <artifactId>maven-gpg-plugin</artifactId>
<artifactId>maven-gpg-plugin</artifactId> <executions>
</plugin> <execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins> </plugins>
</build> </build>
<profiles> <distributionManagement>
<profile> <snapshotRepository>
<id>release</id> <id>sonatype-nexus-snapshots</id>
<build> <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<plugins> </snapshotRepository>
<plugin> <repository>
<groupId>org.apache.maven.plugins</groupId> <id>sonatype-nexus-staging</id>
<artifactId>maven-source-plugin</artifactId> <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
<version>${maven-source.version}</version> </repository>
<inherited>true</inherited> </distributionManagement>
<executions> </profile>
<execution> </profiles>
<phase>package</phase> </project>
<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>
+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() {
}
}
@@ -0,0 +1,39 @@
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);
}
}
+144
View File
@@ -0,0 +1,144 @@
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 {
/**
* 默认缓存过期时间:3分钟
* 鉴于授权过程中,根据个人的操作习惯,或者授权平台的不同(google等),每个授权流程的耗时也有差异,不过单个授权流程一般不会太长
* 本缓存工具默认的过期时间设置为3分钟,即程序默认认为3分钟内的授权有效,超过3分钟则默认失效,失效后删除
*/
private static final long DEF_TIMEOUT = 3 * 60 * 1000;
/**
* 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() {
this.schedulePrune(DEF_TIMEOUT);
}
/**
* 设置缓存
*
* @param key 缓存KEY
* @param value 缓存内容
*/
@Override
public void set(String key, String value) {
set(key, value, DEF_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;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
package me.zhyd.oauth.cache;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
public class AuthStateCache {
private static AuthCache authCache = new AuthDefaultCache();
/**
* 存入缓存
*
* @param key 缓存key
* @param value 缓存内容
*/
public static void cache(String key, String value) {
authCache.set(key, value);
}
/**
* 存入缓存
*
* @param key 缓存key
* @param value 缓存内容
* @param timeout 指定缓存过期时间(毫秒)
*/
public static void cache(String key, String value, long timeout) {
authCache.set(key, value, timeout);
}
/**
* 获取缓存内容
*
* @param key 缓存key
* @return 缓存内容
*/
public static String get(String key) {
return authCache.get(key);
}
/**
* 是否存在key,如果对应key的value值已过期,也返回false
*
* @param key 缓存key
* @return true:存在key,并且value没过期;falsekey不存在或者已过期
*/
public static boolean containsKey(String key) {
return authCache.containsKey(key);
}
}
@@ -6,11 +6,10 @@ import lombok.*;
* JustAuth配置类 * JustAuth配置类
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8 * @since 1.8
*/ */
@Setter
@Getter @Getter
@Setter
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@@ -46,9 +45,9 @@ public class AuthConfig {
private boolean unionId; private boolean unionId;
/** /**
* 一个神奇的参数,最好使用随机的不可测的内容,可以用来防止CSRF攻击 * Stack Overflow Key
* <p> * <p>
* 1.8.0版本新增参数 * 1.9.0版本新增参数
*/ */
private String state; private String stackOverflowKey;
} }
@@ -1,13 +1,12 @@
package me.zhyd.oauth.config; package me.zhyd.oauth.config;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.request.ResponseStatus; import me.zhyd.oauth.model.AuthResponseStatus;
/** /**
* 各api需要的url, 用枚举类分平台类型管理 * 各api需要的url, 用枚举类分平台类型管理
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.0 * @since 1.0
*/ */
public enum AuthSource { public enum AuthSource {
@@ -79,7 +78,7 @@ public enum AuthSource {
@Override @Override
public String accessToken() { public String accessToken() {
throw new AuthException(ResponseStatus.UNSUPPORTED); throw new AuthException(AuthResponseStatus.UNSUPPORTED);
} }
@Override @Override
@@ -110,6 +109,11 @@ public enum AuthSource {
public String revoke() { public String revoke() {
return "https://openapi.baidu.com/rest/2.0/passport/auth/revokeAuthorization"; return "https://openapi.baidu.com/rest/2.0/passport/auth/revokeAuthorization";
} }
@Override
public String refresh() {
return "https://openapi.baidu.com/oauth/2.0/token";
}
}, },
/** /**
* csdn * csdn
@@ -224,6 +228,11 @@ public enum AuthSource {
public String userInfo() { public String userInfo() {
return "https://graph.qq.com/user/get_user_info"; 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 @Override
public String userInfo() { public String userInfo() {
throw new AuthException(ResponseStatus.UNSUPPORTED); throw new AuthException(AuthResponseStatus.UNSUPPORTED);
} }
}, },
/** /**
@@ -284,7 +293,7 @@ public enum AuthSource {
@Override @Override
public String userInfo() { public String userInfo() {
return "https://oauth2.googleapis.com/tokeninfo"; return "https://www.googleapis.com/oauth2/v3/userinfo";
} }
}, },
/** /**
@@ -420,6 +429,95 @@ public enum AuthSource {
public String userInfo() { public String userInfo() {
return "https://open.snssdk.com/data/user_profile"; 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";
}
}; };
/** /**
@@ -449,7 +547,7 @@ public enum AuthSource {
* @return url * @return url
*/ */
public String revoke() { public String revoke() {
throw new AuthException(ResponseStatus.UNSUPPORTED); throw new AuthException(AuthResponseStatus.UNSUPPORTED);
} }
/** /**
@@ -458,7 +556,7 @@ public enum AuthSource {
* @return url * @return url
*/ */
public String refresh() { public String refresh() {
throw new AuthException(ResponseStatus.UNSUPPORTED); throw new AuthException(AuthResponseStatus.UNSUPPORTED);
} }
} }
@@ -1,62 +0,0 @@
package me.zhyd.oauth.enums;
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;
}
}
@@ -1,404 +0,0 @@
package me.zhyd.oauth.enums;
/**
* 钉钉授权登录时的异常状态码
*
* @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,12 +1,16 @@
package me.zhyd.oauth.enums; package me.zhyd.oauth.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/** /**
* 今日头条授权登录时的异常状态码 * 今日头条授权登录时的异常状态码
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8 * @since 1.8
*/ */
@Getter
@AllArgsConstructor
public enum AuthToutiaoErrorCode { public enum AuthToutiaoErrorCode {
EC0(0, "接口调用成功"), EC0(0, "接口调用成功"),
EC1(1, "API配置错误,未传入Client Key"), EC1(1, "API配置错误,未传入Client Key"),
@@ -29,11 +33,6 @@ public enum AuthToutiaoErrorCode {
private int code; private int code;
private String desc; private String desc;
AuthToutiaoErrorCode(int code, String desc) {
this.code = code;
this.desc = desc;
}
public static AuthToutiaoErrorCode getErrorCode(int errorCode) { public static AuthToutiaoErrorCode getErrorCode(int errorCode) {
AuthToutiaoErrorCode[] errorCodes = AuthToutiaoErrorCode.values(); AuthToutiaoErrorCode[] errorCodes = AuthToutiaoErrorCode.values();
for (AuthToutiaoErrorCode code : errorCodes) { for (AuthToutiaoErrorCode code : errorCodes) {
@@ -43,12 +42,4 @@ public enum AuthToutiaoErrorCode {
} }
return EC999; return EC999;
} }
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
} }
@@ -0,0 +1,35 @@
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(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,9 @@
package me.zhyd.oauth.exception; package me.zhyd.oauth.exception;
import me.zhyd.oauth.request.ResponseStatus; import me.zhyd.oauth.model.AuthResponseStatus;
/** /**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8 * @since 1.8
*/ */
public class AuthException extends RuntimeException { public class AuthException extends RuntimeException {
@@ -13,16 +12,16 @@ public class AuthException extends RuntimeException {
private String errorMsg; private String errorMsg;
public AuthException(String errorMsg) { public AuthException(String errorMsg) {
this(ResponseStatus.FAILURE.getCode(), errorMsg); this(AuthResponseStatus.FAILURE.getCode(), errorMsg);
} }
public AuthException(int errorCode, String errorMsg) { public AuthException(int errorCode, String errorMsg) {
super(errorCode + ":" + errorMsg); super(errorMsg);
this.errorCode = errorCode; this.errorCode = errorCode;
this.errorMsg = errorMsg; this.errorMsg = errorMsg;
} }
public AuthException(ResponseStatus status) { public AuthException(AuthResponseStatus status) {
super(status.getMsg()); super(status.getMsg());
} }
@@ -30,6 +29,10 @@ public class AuthException extends RuntimeException {
super(message, cause); super(message, cause);
} }
public AuthException(Throwable cause) {
super(cause);
}
public int getErrorCode() { public int getErrorCode() {
return errorCode; return errorCode;
} }
@@ -2,13 +2,13 @@ package me.zhyd.oauth.model;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import me.zhyd.oauth.cache.AuthStateCache;
/** /**
* 授权回调时的参数类 * 授权回调时的参数类
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.8.0
* @since 1.8
*/ */
@Getter @Getter
@Setter @Setter
@@ -28,4 +28,14 @@ public class AuthCallback {
* 访问AuthorizeUrl后回调时带的参数state,用于和请求AuthorizeUrl前的state比较,防止CSRF攻击 * 访问AuthorizeUrl后回调时带的参数state,用于和请求AuthorizeUrl前的state比较,防止CSRF攻击
*/ */
private String state; private String state;
/**
* 内置的检验state合法性的方法
*
* @return true state正常;false:state不正常,可能授权时间过长导致state失效
* @since 1.9.3
*/
public boolean checkState() {
return AuthStateCache.containsKey(this.state);
}
} }
@@ -3,17 +3,14 @@ package me.zhyd.oauth.model;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import me.zhyd.oauth.request.ResponseStatus;
/** /**
* JustAuth统一授权响应类 * JustAuth统一授权响应类
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8 * @since 1.8
*/ */
@Getter @Getter
@Setter
@Builder @Builder
public class AuthResponse<T> { public class AuthResponse<T> {
/** /**
@@ -37,6 +34,6 @@ public class AuthResponse<T> {
* @return true or false * @return true or false
*/ */
public boolean ok() { public boolean ok() {
return this.code == ResponseStatus.SUCCESS.getCode(); return this.code == AuthResponseStatus.SUCCESS.getCode();
} }
} }
@@ -1,11 +1,15 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
/** /**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8 * @since 1.8
*/ */
public enum ResponseStatus { @Getter
@AllArgsConstructor
public enum AuthResponseStatus {
SUCCESS(2000, "Success"), SUCCESS(2000, "Success"),
FAILURE(5000, "Failure"), FAILURE(5000, "Failure"),
NOT_IMPLEMENTED(5001, "Not Implemented"), NOT_IMPLEMENTED(5001, "Not Implemented"),
@@ -20,18 +24,5 @@ public enum ResponseStatus {
private int code; private int code;
private String msg; private String msg;
ResponseStatus(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
} }
@@ -9,7 +9,6 @@ import lombok.Setter;
* 授权所需的token * 授权所需的token
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8 * @since 1.8
*/ */
@Getter @Getter
@@ -1,16 +1,15 @@
package me.zhyd.oauth.model; package me.zhyd.oauth.model;
import lombok.Builder; import lombok.Builder;
import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
/** /**
* 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同 * 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8 * @since 1.8
*/ */
@Getter @Getter
@@ -19,6 +18,8 @@ import me.zhyd.oauth.config.AuthSource;
public class AuthUser { public class AuthUser {
/** /**
* 用户第三方系统的唯一id。在调用方集成改组件时,可以用uuid + source唯一确定一个用户 * 用户第三方系统的唯一id。在调用方集成改组件时,可以用uuid + source唯一确定一个用户
*
* @since 1.3.3
*/ */
private String uuid; 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;
}
}
@@ -9,11 +9,11 @@ import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.alipay.api.response.AlipayUserInfoShareResponse; import com.alipay.api.response.AlipayUserInfoShareResponse;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
@@ -21,17 +21,16 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 支付宝登录 * 支付宝登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.1
* @since 1.8
*/ */
public class AuthAlipayRequest extends BaseAuthRequest { public class AuthAlipayRequest extends AuthDefaultRequest {
private AlipayClient alipayClient; private AlipayClient alipayClient;
public AuthAlipayRequest(AuthConfig config) { public AuthAlipayRequest(AuthConfig config) {
super(config, AuthSource.ALIPAY); super(config, AuthSource.ALIPAY);
this.alipayClient = new DefaultAlipayClient(AuthSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(), "json", "UTF-8", config this.alipayClient = new DefaultAlipayClient(AuthSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(), "json", "UTF-8", config
.getAlipayPublicKey(), "RSA2"); .getAlipayPublicKey(), "RSA2");
} }
@Override @Override
@@ -43,17 +42,17 @@ public class AuthAlipayRequest extends BaseAuthRequest {
try { try {
response = this.alipayClient.execute(request); response = this.alipayClient.execute(request);
} catch (Exception e) { } catch (Exception e) {
throw new AuthException("Unable to get token from alipay using code [" + authCallback.getAuth_code() + "]", e); throw new AuthException(e);
} }
if (!response.isSuccess()) { if (!response.isSuccess()) {
throw new AuthException(response.getSubMsg()); throw new AuthException(response.getSubMsg());
} }
return AuthToken.builder() return AuthToken.builder()
.accessToken(response.getAccessToken()) .accessToken(response.getAccessToken())
.uid(response.getUserId()) .uid(response.getUserId())
.expireIn(Integer.parseInt(response.getExpiresIn())) .expireIn(Integer.parseInt(response.getExpiresIn()))
.refreshToken(response.getRefreshToken()) .refreshToken(response.getRefreshToken())
.build(); .build();
} }
@Override @Override
@@ -70,29 +69,35 @@ public class AuthAlipayRequest extends BaseAuthRequest {
throw new AuthException(response.getSubMsg()); throw new AuthException(response.getSubMsg());
} }
String province = response.getProvince(), String province = response.getProvince(), city = response.getCity();
city = response.getCity();
String location = String.format("%s %s", StringUtils.isEmpty(province) ? "" : province, StringUtils.isEmpty(city) ? "" : city); String location = String.format("%s %s", StringUtils.isEmpty(province) ? "" : province, StringUtils.isEmpty(city) ? "" : city);
return AuthUser.builder() return AuthUser.builder()
.uuid(response.getUserId()) .uuid(response.getUserId())
.username(StringUtils.isEmpty(response.getUserName()) ? response.getNickName() : response.getUserName()) .username(StringUtils.isEmpty(response.getUserName()) ? response.getNickName() : response.getUserName())
.nickname(response.getNickName()) .nickname(response.getNickName())
.avatar(response.getAvatar()) .avatar(response.getAvatar())
.location(location) .location(location)
.gender(AuthUserGender.getRealGender(response.getGender())) .gender(AuthUserGender.getRealGender(response.getGender()))
.token(authToken) .token(authToken)
.source(AuthSource.ALIPAY) .source(source)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
* *
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址 * @return 返回授权地址
* @since 1.9.3
*/ */
@Override @Override
public String authorize() { public String authorize(String state) {
return UrlBuilder.getAlipayAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("app_id", config.getClientId())
.queryParam("scope", "auth_user")
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(state))
.build();
} }
} }
@@ -5,19 +5,19 @@ import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthBaiduErrorCode; import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*; import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
/** /**
* 百度账号登录 * 百度账号登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthBaiduRequest extends BaseAuthRequest { public class AuthBaiduRequest extends AuthDefaultRequest {
public AuthBaiduRequest(AuthConfig config) { public AuthBaiduRequest(AuthConfig config) {
super(config, AuthSource.BAIDU); super(config, AuthSource.BAIDU);
@@ -25,66 +25,96 @@ public class AuthBaiduRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getBaiduAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
.getRedirectUri()); return getAuthToken(response);
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
AuthBaiduErrorCode errorCode = AuthBaiduErrorCode.getErrorCode(accessTokenObject.getString("error"));
if (AuthBaiduErrorCode.OK != errorCode) {
throw new AuthException(errorCode.getDesc());
}
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.scope(accessTokenObject.getString("scope"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getBaiduUserInfoUrl(accessToken)).execute();
String userInfo = response.body(); String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo); JSONObject object = JSONObject.parseObject(userInfo);
AuthBaiduErrorCode errorCode = AuthBaiduErrorCode.getErrorCode(object.getString("error")); this.checkResponse(object);
if (AuthBaiduErrorCode.OK != errorCode) {
throw new AuthException(errorCode.getDesc());
}
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("userid")) .uuid(object.getString("userid"))
.username(object.getString("username")) .username(object.getString("username"))
.nickname(object.getString("username")) .nickname(object.getString("username"))
.gender(AuthUserGender.getRealGender(object.getString("sex"))) .avatar(getAvatar(object))
.token(authToken) .remark(object.getString("userdetail"))
.source(AuthSource.BAIDU) .gender(AuthUserGender.getRealGender(object.getString("sex")))
.build(); .token(authToken)
.source(source)
.build();
} }
/** private String getAvatar(JSONObject object) {
* 返回认证url,可自行跳转页面 String protrait = object.getString("portrait");
* return StringUtils.isEmpty(protrait) ? null : String.format("http://himg.bdimg.com/sys/portrait/item/%s.jpg", protrait);
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getBaiduAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState());
} }
@Override @Override
public AuthResponse revoke(AuthToken authToken) { public AuthResponse revoke(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetRevoke(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getBaiduRevokeUrl(accessToken)).execute(); JSONObject object = JSONObject.parseObject(response.body());
String userInfo = response.body(); this.checkResponse(object);
JSONObject object = JSONObject.parseObject(userInfo); // 返回1表示取消授权成功,否则失败
if (object.containsKey("error_code")) { AuthResponseStatus status = object.getIntValue("result") == 1 ? AuthResponseStatus.SUCCESS : AuthResponseStatus.FAILURE;
return AuthResponse.builder()
.code(ResponseStatus.FAILURE.getCode())
.msg(object.getString("error_msg"))
.build();
}
ResponseStatus status = object.getIntValue("result") == 1 ? ResponseStatus.SUCCESS : ResponseStatus.FAILURE;
return AuthResponse.builder().code(status.getCode()).msg(status.getMsg()).build(); 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,25 +1,23 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
/** /**
* Cooding登录 * Cooding登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthCodingRequest extends BaseAuthRequest { public class AuthCodingRequest extends AuthDefaultRequest {
public AuthCodingRequest(AuthConfig config) { public AuthCodingRequest(AuthConfig config) {
super(config, AuthSource.CODING); super(config, AuthSource.CODING);
@@ -27,52 +25,65 @@ public class AuthCodingRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getCodingAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.getIntValue("code") != 0) { this.checkResponse(accessTokenObject);
throw new AuthException("Unable to get token from coding using code [" + authCallback.getCode() + "]");
}
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token")) .refreshToken(accessTokenObject.getString("refresh_token"))
.build(); .build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getCodingUserInfoUrl(accessToken)).execute();
JSONObject object = JSONObject.parseObject(response.body()); JSONObject object = JSONObject.parseObject(response.body());
if (object.getIntValue("code") != 0) { this.checkResponse(object);
throw new AuthException(object.getString("msg"));
}
object = object.getJSONObject("data"); object = object.getJSONObject("data");
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("id")) .uuid(object.getString("id"))
.username(object.getString("name")) .username(object.getString("name"))
.avatar("https://coding.net/" + object.getString("avatar")) .avatar("https://coding.net/" + object.getString("avatar"))
.blog("https://coding.net/" + object.getString("path")) .blog("https://coding.net/" + object.getString("path"))
.nickname(object.getString("name")) .nickname(object.getString("name"))
.company(object.getString("company")) .company(object.getString("company"))
.location(object.getString("location")) .location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("sex"))) .gender(AuthUserGender.getRealGender(object.getString("sex")))
.email(object.getString("email")) .email(object.getString("email"))
.remark(object.getString("slogan")) .remark(object.getString("slogan"))
.token(authToken) .token(authToken)
.source(AuthSource.CODING) .source(source)
.build(); .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 返回授权地址 * @return 返回授权地址
* @since 1.9.3
*/ */
@Override @Override
public String authorize() { public String authorize(String state) {
return UrlBuilder.getCodingAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); 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,25 +1,23 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/** /**
* CSDN登录 * CSDN登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthCsdnRequest extends BaseAuthRequest { @Deprecated
public class AuthCsdnRequest extends AuthDefaultRequest {
public AuthCsdnRequest(AuthConfig config) { public AuthCsdnRequest(AuthConfig config) {
super(config, AuthSource.CSDN); super(config, AuthSource.CSDN);
@@ -27,42 +25,36 @@ public class AuthCsdnRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getCsdnAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error_code")) { this.checkResponse(accessTokenObject);
throw new AuthException("Unable to get token from csdn using code [" + authCallback.getCode() + "]");
}
return AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build(); return AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getCsdnUserInfoUrl(accessToken)).execute();
JSONObject object = JSONObject.parseObject(response.body()); JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error_code")) { this.checkResponse(object);
throw new AuthException(object.getString("error"));
}
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("username")) .uuid(object.getString("username"))
.username(object.getString("username")) .username(object.getString("username"))
.remark(object.getString("description")) .remark(object.getString("description"))
.blog(object.getString("website")) .blog(object.getString("website"))
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.token(authToken) .token(authToken)
.source(AuthSource.CSDN) .source(source)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 检查响应内容是否正确
* *
* @return 返回授权地址 * @param object 请求响应内容
*/ */
@Override private void checkResponse(JSONObject object) {
public String authorize() { if (object.containsKey("error_code")) {
return UrlBuilder.getCsdnAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); throw new AuthException(object.getString("error"));
}
} }
} }
@@ -0,0 +1,224 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.AuthChecker;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
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
*/
@Slf4j
public abstract class AuthDefaultRequest implements AuthRequest {
protected AuthConfig config;
protected AuthSource source;
public AuthDefaultRequest(AuthConfig config, AuthSource source) {
this.config = config;
this.source = source;
if (!AuthChecker.isSupportedAuth(config, source)) {
throw new AuthException(AuthResponseStatus.PARAMETER_INCOMPLETE);
}
// 校验配置合法性
AuthChecker.checkConfig(config, source);
}
protected abstract AuthToken getAccessToken(AuthCallback authCallback);
protected abstract AuthUser getUserInfo(AuthToken authToken);
@Override
public AuthResponse login(AuthCallback authCallback) {
try {
AuthChecker.checkCode(source == AuthSource.ALIPAY ? authCallback.getAuth_code() : authCallback.getCode());
AuthToken authToken = this.getAccessToken(authCallback);
AuthUser user = this.getUserInfo(authToken);
return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build();
} catch (Exception e) {
log.error("Failed to login with oauth authorization.", e);
return this.responseError(e);
}
}
private AuthResponse responseError(Exception e) {
int errorCode = AuthResponseStatus.FAILURE.getCode();
if (e instanceof AuthException) {
errorCode = ((AuthException) e).getErrorCode();
}
return AuthResponse.builder().code(errorCode).msg(e.getMessage()).build();
}
/**
* 返回授权url,可自行跳转页面
* <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();
}
}
@@ -6,9 +6,11 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthDingTalkErrorCode; import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.GlobalAuthUtil; import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
@@ -16,10 +18,9 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 钉钉登录 * 钉钉登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthDingTalkRequest extends BaseAuthRequest { public class AuthDingTalkRequest extends AuthDefaultRequest {
public AuthDingTalkRequest(AuthConfig config) { public AuthDingTalkRequest(AuthConfig config) {
super(config, AuthSource.DINGTALK); super(config, AuthSource.DINGTALK);
@@ -33,41 +34,62 @@ public class AuthDingTalkRequest extends BaseAuthRequest {
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String code = authToken.getAccessCode(); String code = authToken.getAccessCode();
// 根据timestamp, appSecret计算签名值
String timestamp = System.currentTimeMillis() + "";
String urlEncodeSignature = GlobalAuthUtil.generateDingTalkSignature(config.getClientSecret(), timestamp);
JSONObject param = new JSONObject(); JSONObject param = new JSONObject();
param.put("tmp_auth_code", code); param.put("tmp_auth_code", code);
HttpResponse response = HttpRequest.post(UrlBuilder.getDingTalkUserInfoUrl(urlEncodeSignature, timestamp, config HttpResponse response = HttpRequest.post(userInfoUrl(authToken)).body(param.toJSONString()).execute();
.getClientId())).body(param.toJSONString()).execute(); JSONObject object = JSON.parseObject(response.body());
String userInfo = response.body(); if (object.getIntValue("errcode") != 0) {
JSONObject object = JSON.parseObject(userInfo); throw new AuthException(object.getString("errmsg"));
AuthDingTalkErrorCode errorCode = AuthDingTalkErrorCode.getErrorCode(object.getIntValue("errcode"));
if (AuthDingTalkErrorCode.EC0 != errorCode) {
throw new AuthException(errorCode.getDesc());
} }
object = object.getJSONObject("user_info"); object = object.getJSONObject("user_info");
AuthToken token = AuthToken.builder() AuthToken token = AuthToken.builder()
.openId(object.getString("openid")) .openId(object.getString("openid"))
.unionId(object.getString("unionid")) .unionId(object.getString("unionid"))
.build(); .build();
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("unionid")) .uuid(object.getString("unionid"))
.nickname(object.getString("nick")) .nickname(object.getString("nick"))
.username(object.getString("nick")) .username(object.getString("nick"))
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.source(AuthSource.DINGTALK) .source(source)
.token(token) .token(token)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
* *
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址 * @return 返回授权地址
* @since 1.9.3
*/ */
@Override @Override
public String authorize() { public String authorize(String state) {
return UrlBuilder.getDingTalkQrConnectUrl(config.getClientId(), config.getRedirectUri(), config.getState()); 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();
} }
} }
@@ -5,6 +5,7 @@ import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*; import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
@@ -14,10 +15,9 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 抖音登录 * 抖音登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.4.0
* @since 1.8
*/ */
public class AuthDouyinRequest extends BaseAuthRequest { public class AuthDouyinRequest extends AuthDefaultRequest {
public AuthDouyinRequest(AuthConfig config) { public AuthDouyinRequest(AuthConfig config) {
super(config, AuthSource.DOUYIN); super(config, AuthSource.DOUYIN);
@@ -25,65 +25,46 @@ public class AuthDouyinRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getDouyinAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); return this.getToken(accessTokenUrl(authCallback.getCode()));
return this.getToken(accessTokenUrl);
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
String openId = authToken.getOpenId(); JSONObject userInfoObject = JSONObject.parseObject(response.body());
String url = UrlBuilder.getDouyinUserInfoUrl(accessToken, openId); this.checkResponse(userInfoObject);
HttpResponse response = HttpRequest.get(url).execute();
JSONObject object = JSONObject.parseObject(response.body());
JSONObject userInfoObject = this.checkResponse(object);
return AuthUser.builder() return AuthUser.builder()
.uuid(userInfoObject.getString("union_id")) .uuid(userInfoObject.getString("union_id"))
.username(userInfoObject.getString("nickname")) .username(userInfoObject.getString("nickname"))
.nickname(userInfoObject.getString("nickname")) .nickname(userInfoObject.getString("nickname"))
.avatar(userInfoObject.getString("avatar")) .avatar(userInfoObject.getString("avatar"))
.remark(userInfoObject.getString("description")) .remark(userInfoObject.getString("description"))
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.token(authToken) .token(authToken)
.source(AuthSource.DOUYIN) .source(source)
.build(); .build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getDouyinAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState());
} }
@Override @Override
public AuthResponse refresh(AuthToken oldToken) { public AuthResponse refresh(AuthToken oldToken) {
String refreshTokenUrl = UrlBuilder.getDouyinRefreshUrl(config.getClientId(), oldToken.getRefreshToken());
return AuthResponse.builder() return AuthResponse.builder()
.code(ResponseStatus.SUCCESS.getCode()) .code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl)) .data(getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build(); .build();
} }
/** /**
* 检查响应内容是否正确 * 检查响应内容是否正确
* *
* @param object 请求响应内容 * @param object 请求响应内容
* @return 实际请求数据的json对象
*/ */
private JSONObject checkResponse(JSONObject object) { private void checkResponse(JSONObject object) {
String message = object.getString("message"); String message = object.getString("message");
JSONObject data = object.getJSONObject("data"); JSONObject data = object.getJSONObject("data");
int errorCode = data.getIntValue("error_code"); int errorCode = data.getIntValue("error_code");
if ("error".equals(message) || errorCode != 0) { if ("error".equals(message) || errorCode != 0) {
throw new AuthException(errorCode, data.getString("description")); throw new AuthException(errorCode, data.getString("description"));
} }
return data;
} }
/** /**
@@ -96,14 +77,76 @@ public class AuthDouyinRequest extends BaseAuthRequest {
HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
String accessTokenStr = response.body(); String accessTokenStr = response.body();
JSONObject object = JSONObject.parseObject(accessTokenStr); JSONObject object = JSONObject.parseObject(accessTokenStr);
this.checkResponse(object);
JSONObject accessTokenObject = this.checkResponse(object);
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(object.getString("access_token"))
.openId(accessTokenObject.getString("open_id")) .openId(object.getString("open_id"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(object.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token")) .refreshToken(object.getString("refresh_token"))
.scope(accessTokenObject.getString("scope")) .scope(object.getString("scope"))
.build(); .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,25 +1,23 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
/** /**
* Facebook登录 * Facebook登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.3.0
* @since 1.8
*/ */
public class AuthFacebookRequest extends BaseAuthRequest { public class AuthFacebookRequest extends AuthDefaultRequest {
public AuthFacebookRequest(AuthConfig config) { public AuthFacebookRequest(AuthConfig config) {
super(config, AuthSource.FACEBOOK); super(config, AuthSource.FACEBOOK);
@@ -27,31 +25,36 @@ public class AuthFacebookRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getFacebookAccessTokenUrl(config.getClientId(), config.getClientSecret(), HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
authCallback.getCode(), config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
if (accessTokenObject.containsKey("error")) {
throw new AuthException(accessTokenObject.getJSONObject("error").getString("message"));
}
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.tokenType(accessTokenObject.getString("token_type")) .tokenType(accessTokenObject.getString("token_type"))
.build(); .build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getFacebookUserInfoUrl(accessToken)).execute();
String userInfo = response.body(); String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo); JSONObject object = JSONObject.parseObject(userInfo);
if (object.containsKey("error")) { this.checkResponse(object);
throw new AuthException(object.getJSONObject("error").getString("message")); 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; String picture = null;
if (object.containsKey("picture")) { if (object.containsKey("picture")) {
JSONObject pictureObj = object.getJSONObject("picture"); JSONObject pictureObj = object.getJSONObject("picture");
@@ -60,26 +63,31 @@ public class AuthFacebookRequest extends BaseAuthRequest {
picture = pictureObj.getString("url"); picture = pictureObj.getString("url");
} }
} }
return AuthUser.builder() return picture;
.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();
} }
/** /**
* 返回认证url,可自行跳转页面 * 返回获取userInfo的url
* *
* @return 返回授权地址 * @param authToken 用户token
* @return 返回获取userInfo的url
*/ */
@Override @Override
public String authorize() { protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.getFacebookAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); 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,25 +1,22 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder;
/** /**
* Gitee登录 * Gitee登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthGiteeRequest extends BaseAuthRequest { public class AuthGiteeRequest extends AuthDefaultRequest {
public AuthGiteeRequest(AuthConfig config) { public AuthGiteeRequest(AuthConfig config) {
super(config, AuthSource.GITEE); super(config, AuthSource.GITEE);
@@ -27,45 +24,48 @@ public class AuthGiteeRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getGiteeAccessTokenUrl(config.getClientId(), config.getClientSecret(), HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
authCallback.getCode(), config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error")) { this.checkResponse(accessTokenObject);
throw new AuthException("Unable to get token from gitee using code [" + authCallback.getCode() + "]"); return AuthToken.builder()
} .accessToken(accessTokenObject.getString("access_token"))
return AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build(); .refreshToken(accessTokenObject.getString("refresh_token"))
.scope(accessTokenObject.getString("scope"))
.tokenType(accessTokenObject.getString("token_type"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getGiteeUserInfoUrl(accessToken)).execute();
String userInfo = response.body(); String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo); JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("id")) .uuid(object.getString("id"))
.username(object.getString("login")) .username(object.getString("login"))
.avatar(object.getString("avatar_url")) .avatar(object.getString("avatar_url"))
.blog(object.getString("blog")) .blog(object.getString("blog"))
.nickname(object.getString("name")) .nickname(object.getString("name"))
.company(object.getString("company")) .company(object.getString("company"))
.location(object.getString("address")) .location(object.getString("address"))
.email(object.getString("email")) .email(object.getString("email"))
.remark(object.getString("bio")) .remark(object.getString("bio"))
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.token(authToken) .token(authToken)
.source(AuthSource.GITEE) .source(source)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 检查响应内容是否正确
* *
* @return 返回授权地址 * @param object 请求响应内容
*/ */
@Override private void checkResponse(JSONObject object) {
public String authorize() { if (object.containsKey("error")) {
return UrlBuilder.getGiteeAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); throw new AuthException(object.getString("error_description"));
}
} }
} }
@@ -1,17 +1,15 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.GlobalAuthUtil; import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Map; import java.util.Map;
@@ -19,10 +17,9 @@ import java.util.Map;
* Github登录 * Github登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthGithubRequest extends BaseAuthRequest { public class AuthGithubRequest extends AuthDefaultRequest {
public AuthGithubRequest(AuthConfig config) { public AuthGithubRequest(AuthConfig config) {
super(config, AuthSource.GITHUB); super(config, AuthSource.GITHUB);
@@ -30,44 +27,39 @@ public class AuthGithubRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getGithubAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config.getRedirectUri()); HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
Map<String, String> res = GlobalAuthUtil.parseStringToMap(response.body()); Map<String, String> res = GlobalAuthUtil.parseStringToMap(response.body());
if (res.containsKey("error")) { if (res.containsKey("error")) {
throw new AuthException(res.get("error") + ":" + res.get("error_description")); 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 @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getGithubUserInfoUrl(accessToken)).execute(); JSONObject object = JSONObject.parseObject(response.body());
String userInfo = response.body(); if (object.containsKey("error")) {
JSONObject object = JSONObject.parseObject(userInfo); throw new AuthException(object.getString("error_description"));
}
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("id")) .uuid(object.getString("id"))
.username(object.getString("login")) .username(object.getString("login"))
.avatar(object.getString("avatar_url")) .avatar(object.getString("avatar_url"))
.blog(object.getString("blog")) .blog(object.getString("blog"))
.nickname(object.getString("name")) .nickname(object.getString("name"))
.company(object.getString("company")) .company(object.getString("company"))
.location(object.getString("location")) .location(object.getString("location"))
.email(object.getString("email")) .email(object.getString("email"))
.remark(object.getString("bio")) .remark(object.getString("bio"))
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.token(authToken) .token(authToken)
.source(AuthSource.GITHUB) .source(source)
.build(); .build();
} }
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getGithubAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState());
}
} }
@@ -5,21 +5,20 @@ import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
/** /**
* Google登录 * Google登录
* *
* @author yangkai.shen (https://xkcoding.com) * @author yangkai.shen (https://xkcoding.com)
* @version 1.3 * @since 1.3.0
* @since 1.3
*/ */
public class AuthGoogleRequest extends BaseAuthRequest { public class AuthGoogleRequest extends AuthDefaultRequest {
public AuthGoogleRequest(AuthConfig config) { public AuthGoogleRequest(AuthConfig config) {
super(config, AuthSource.GOOGLE); super(config, AuthSource.GOOGLE);
@@ -27,51 +26,76 @@ public class AuthGoogleRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getGoogleAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
if (accessTokenObject.containsKey("error") || accessTokenObject.containsKey("error_description")) {
throw new AuthException("get google access_token has error:[" + accessTokenObject.getString("error") + "], error_description:[" + accessTokenObject
.getString("error_description") + "]");
}
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.scope(accessTokenObject.getString("scope")) .scope(accessTokenObject.getString("scope"))
.tokenType(accessTokenObject.getString("token_type")) .tokenType(accessTokenObject.getString("token_type"))
.idToken(accessTokenObject.getString("id_token")) .idToken(accessTokenObject.getString("id_token"))
.build(); .build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getIdToken(); HttpResponse response = HttpRequest.post(userInfoUrl(authToken))
HttpResponse response = HttpRequest.get(UrlBuilder.getGoogleUserInfoUrl(accessToken)).execute(); .header("Authorization", "Bearer " + authToken.getAccessToken())
.execute();
String userInfo = response.body(); String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo); JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("sub")) .uuid(object.getString("sub"))
.username(object.getString("name")) .username(object.getString("email"))
.avatar(object.getString("picture")) .avatar(object.getString("picture"))
.nickname(object.getString("name")) .nickname(object.getString("name"))
.location(object.getString("locale")) .location(object.getString("locale"))
.email(object.getString("email")) .email(object.getString("email"))
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.token(authToken) .token(authToken)
.source(AuthSource.GOOGLE) .source(source)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
* *
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址 * @return 返回授权地址
* @since 1.9.3
*/ */
@Override @Override
public String authorize() { public String authorize(String state) {
return UrlBuilder.getGoogleAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); 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"));
}
} }
} }
@@ -4,8 +4,10 @@ import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*; import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.StringUtils;
@@ -16,10 +18,9 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 领英登录 * 领英登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.4.0
* @since 1.8
*/ */
public class AuthLinkedinRequest extends BaseAuthRequest { public class AuthLinkedinRequest extends AuthDefaultRequest {
public AuthLinkedinRequest(AuthConfig config) { public AuthLinkedinRequest(AuthConfig config) {
super(config, AuthSource.LINKEDIN); super(config, AuthSource.LINKEDIN);
@@ -27,24 +28,47 @@ public class AuthLinkedinRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getLinkedinAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config return this.getToken(accessTokenUrl(authCallback.getCode()));
.getRedirectUri());
return this.getToken(accessTokenUrl);
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(UrlBuilder.getLinkedinUserInfoUrl()) HttpResponse response = HttpRequest.get(userInfoUrl(authToken))
.header("Host", "api.linkedin.com") .header("Host", "api.linkedin.com")
.header("Connection", "Keep-Alive") .header("Connection", "Keep-Alive")
.header("Authorization", "Bearer " + accessToken) .header("Authorization", "Bearer " + accessToken)
.execute(); .execute();
JSONObject userInfoObject = JSONObject.parseObject(response.body()); JSONObject userInfoObject = JSONObject.parseObject(response.body());
this.checkResponse(userInfoObject); 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; String firstName, lastName;
// 获取firstName // 获取firstName
if (userInfoObject.containsKey("localizedFirstName")) { if (userInfoObject.containsKey("localizedFirstName")) {
@@ -58,60 +82,46 @@ public class AuthLinkedinRequest extends BaseAuthRequest {
} else { } else {
lastName = getUserName(userInfoObject, "lastName"); lastName = getUserName(userInfoObject, "lastName");
} }
String userName = firstName + " " + lastName; return firstName + " " + lastName;
}
// 获取用户头像 /**
* 获取用户的头像
*
* @param userInfoObject 用户json对象
* @return 用户的头像地址
*/
private String getAvatar(JSONObject userInfoObject) {
String avatar = null; String avatar = null;
JSONObject profilePictureObject = userInfoObject.getJSONObject("profilePicture"); JSONObject profilePictureObject = userInfoObject.getJSONObject("profilePicture");
if (profilePictureObject.containsKey("displayImage~")) { if (profilePictureObject.containsKey("displayImage~")) {
JSONArray displayImageElements = profilePictureObject.getJSONObject("displayImage~") JSONArray displayImageElements = profilePictureObject.getJSONObject("displayImage~")
.getJSONArray("elements"); .getJSONArray("elements");
if (null != displayImageElements && displayImageElements.size() > 0) { if (null != displayImageElements && displayImageElements.size() > 0) {
JSONObject largestImageObj = displayImageElements.getJSONObject(displayImageElements.size() - 1); JSONObject largestImageObj = displayImageElements.getJSONObject(displayImageElements.size() - 1);
avatar = largestImageObj.getJSONArray("identifiers").getJSONObject(0).getString("identifier"); avatar = largestImageObj.getJSONArray("identifiers").getJSONObject(0).getString("identifier");
} }
} }
return avatar;
// 获取用户邮箱地址
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();
} }
/** /**
* 返回认证url,可自行跳转页面 * 获取用户的email
* *
* @return 返回授权地址 * @param accessToken 用户授权后返回的token
* @return 用户的邮箱地址
*/ */
@Override
public String authorize() {
return UrlBuilder.getLinkedinAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState());
}
private String getUserEmail(String accessToken) { private String getUserEmail(String accessToken) {
String email = null; String email = null;
HttpResponse emailResponse = HttpRequest.get("https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))") HttpResponse emailResponse = HttpRequest.get("https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))")
.header("Host", "api.linkedin.com") .header("Host", "api.linkedin.com")
.header("Connection", "Keep-Alive") .header("Connection", "Keep-Alive")
.header("Authorization", "Bearer " + accessToken) .header("Authorization", "Bearer " + accessToken)
.execute(); .execute();
System.out.println(emailResponse.body());
JSONObject emailObj = JSONObject.parseObject(emailResponse.body()); JSONObject emailObj = JSONObject.parseObject(emailResponse.body());
if (emailObj.containsKey("elements")) { this.checkResponse(emailObj);
email = emailObj.getJSONArray("elements") Object obj = JSONPath.eval(emailObj, "$['elements'][0]['handle~']['emailAddress']");
.getJSONObject(0) return null == obj ? null : (String) obj;
.getJSONObject("handle~")
.getString("emailAddress");
}
return email;
} }
private String getUserName(JSONObject userInfoObject, String nameKey) { private String getUserName(JSONObject userInfoObject, String nameKey) {
@@ -125,20 +135,25 @@ public class AuthLinkedinRequest extends BaseAuthRequest {
@Override @Override
public AuthResponse refresh(AuthToken oldToken) { public AuthResponse refresh(AuthToken oldToken) {
if (StringUtils.isEmpty(oldToken.getRefreshToken())) { String refreshToken = oldToken.getRefreshToken();
throw new AuthException(ResponseStatus.UNSUPPORTED); if (StringUtils.isEmpty(refreshToken)) {
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
} }
String refreshTokenUrl = UrlBuilder.getLinkedinRefreshUrl(config.getClientId(), config.getClientSecret(), oldToken String refreshTokenUrl = refreshTokenUrl(refreshToken);
.getRefreshToken());
return AuthResponse.builder() return AuthResponse.builder()
.code(ResponseStatus.SUCCESS.getCode()) .code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl)) .data(this.getToken(refreshTokenUrl))
.build(); .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"));
} }
} }
@@ -150,18 +165,49 @@ public class AuthLinkedinRequest extends BaseAuthRequest {
*/ */
private AuthToken getToken(String accessTokenUrl) { private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.post(accessTokenUrl) HttpResponse response = HttpRequest.post(accessTokenUrl)
.header("Host", "www.linkedin.com") .header("Host", "www.linkedin.com")
.header("Content-Type", "application/x-www-form-urlencoded") .contentType("application/x-www-form-urlencoded")
.execute(); .execute();
String accessTokenStr = response.body(); String accessTokenStr = response.body();
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr); JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
this.checkResponse(accessTokenObject); this.checkResponse(accessTokenObject);
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token")) .refreshToken(accessTokenObject.getString("refresh_token"))
.build(); .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,8 +4,10 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*; import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
@@ -16,10 +18,10 @@ import java.text.MessageFormat;
* 小米登录 * 小米登录
* *
* @author yangkai.shen (https://xkcoding.com) * @author yangkai.shen (https://xkcoding.com)
* @version 1.5 * @since 1.5.0
* @since 1.5
*/ */
public class AuthMiRequest extends BaseAuthRequest { @Slf4j
public class AuthMiRequest extends AuthDefaultRequest {
private static final String PREFIX = "&&&START&&&"; private static final String PREFIX = "&&&START&&&";
public AuthMiRequest(AuthConfig config) { public AuthMiRequest(AuthConfig config) {
@@ -28,8 +30,7 @@ public class AuthMiRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getMiAccessTokenUrl(config.getClientId(), config.getClientSecret(), config.getRedirectUri(), authCallback.getCode()); return getToken(accessTokenUrl(authCallback.getCode()));
return getToken(accessTokenUrl);
} }
private AuthToken getToken(String accessTokenUrl) { private AuthToken getToken(String accessTokenUrl) {
@@ -42,22 +43,21 @@ public class AuthMiRequest extends BaseAuthRequest {
} }
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.scope(accessTokenObject.getString("scope")) .scope(accessTokenObject.getString("scope"))
.tokenType(accessTokenObject.getString("token_type")) .tokenType(accessTokenObject.getString("token_type"))
.refreshToken(accessTokenObject.getString("refresh_token")) .refreshToken(accessTokenObject.getString("refresh_token"))
.openId(accessTokenObject.getString("openId")) .openId(accessTokenObject.getString("openId"))
.macAlgorithm(accessTokenObject.getString("mac_algorithm")) .macAlgorithm(accessTokenObject.getString("mac_algorithm"))
.macKey(accessTokenObject.getString("mac_key")) .macKey(accessTokenObject.getString("mac_key"))
.build(); .build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
// 获取用户信息 // 获取用户信息
HttpResponse userResponse = HttpRequest.get(UrlBuilder.getMiUserInfoUrl(config.getClientId(), authToken.getAccessToken())) HttpResponse userResponse = doGetUserInfo(authToken);
.execute();
JSONObject userProfile = JSONObject.parseObject(userResponse.body()); JSONObject userProfile = JSONObject.parseObject(userResponse.body());
if ("error".equalsIgnoreCase(userProfile.getString("result"))) { if ("error".equalsIgnoreCase(userProfile.getString("result"))) {
@@ -67,40 +67,32 @@ public class AuthMiRequest extends BaseAuthRequest {
JSONObject user = userProfile.getJSONObject("data"); JSONObject user = userProfile.getJSONObject("data");
AuthUser authUser = AuthUser.builder() AuthUser authUser = AuthUser.builder()
.uuid(authToken.getOpenId()) .uuid(authToken.getOpenId())
.username(user.getString("miliaoNick")) .username(user.getString("miliaoNick"))
.nickname(user.getString("miliaoNick")) .nickname(user.getString("miliaoNick"))
.avatar(user.getString("miliaoIcon")) .avatar(user.getString("miliaoIcon"))
.email(user.getString("mail")) .email(user.getString("mail"))
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.token(authToken) .token(authToken)
.source(AuthSource.MI) .source(source)
.build(); .build();
// 获取用户邮箱手机号等信息 // 获取用户邮箱手机号等信息
String emailPhoneUrl = MessageFormat.format("{0}?clientId={1}&token={2}", "https://open.account.xiaomi.com/user/phoneAndEmail", config 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(); HttpResponse emailResponse = HttpRequest.get(emailPhoneUrl).execute();
JSONObject userEmailPhone = JSONObject.parseObject(emailResponse.body()); JSONObject userEmailPhone = JSONObject.parseObject(emailResponse.body());
if (!"error".equalsIgnoreCase(userEmailPhone.getString("result"))) { if (!"error".equalsIgnoreCase(userEmailPhone.getString("result"))) {
JSONObject emailPhone = userEmailPhone.getJSONObject("data"); JSONObject emailPhone = userEmailPhone.getJSONObject("data");
authUser.setEmail(emailPhone.getString("email")); authUser.setEmail(emailPhone.getString("email"));
} else {
log.warn("小米开发平台暂时不对外开放用户手机及邮箱信息的获取");
} }
return authUser; return authUser;
} }
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getMiAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState());
}
/** /**
* 刷新access token (续期) * 刷新access token (续期)
* *
@@ -109,9 +101,42 @@ public class AuthMiRequest extends BaseAuthRequest {
*/ */
@Override @Override
public AuthResponse refresh(AuthToken authToken) { public AuthResponse refresh(AuthToken authToken) {
String miRefreshUrl = UrlBuilder.getMiRefreshUrl(config.getClientId(), config.getClientSecret(), config.getRedirectUri(), authToken return AuthResponse.builder()
.getRefreshToken()); .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,35 +2,30 @@ package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*; import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
import java.util.HashMap; import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap;
import java.util.Map;
/** /**
* 微软登录 * 微软登录
* *
* @author yangkai.shen (https://xkcoding.com) * @author yangkai.shen (https://xkcoding.com)
* @version 1.5 * @since 1.5.0
* @since 1.5
*/ */
public class AuthMicrosoftRequest extends BaseAuthRequest { public class AuthMicrosoftRequest extends AuthDefaultRequest {
public AuthMicrosoftRequest(AuthConfig config) { public AuthMicrosoftRequest(AuthConfig config) {
super(config, AuthSource.MICROSOFT); super(config, AuthSource.MICROSOFT);
} }
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getMicrosoftAccessTokenUrl(config.getClientId(), config.getClientSecret(), config return getToken(accessTokenUrl(authCallback.getCode()));
.getRedirectUri(), authCallback.getCode());
return getToken(accessTokenUrl);
} }
/** /**
@@ -40,30 +35,33 @@ public class AuthMicrosoftRequest extends BaseAuthRequest {
* @return token对象 * @return token对象
*/ */
private AuthToken getToken(String accessTokenUrl) { 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) HttpResponse response = HttpRequest.post(accessTokenUrl)
.header("Host", "https://login.microsoftonline.com") .header("Host", "https://login.microsoftonline.com")
.header("Content-Type", "application/x-www-form-urlencoded") .contentType("application/x-www-form-urlencoded")
.form(paramMap) .form(parseQueryToMap(accessTokenUrl))
.execute(); .execute();
String accessTokenStr = response.body(); String accessTokenStr = response.body();
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr); JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
this.checkResponse(accessTokenObject); this.checkResponse(accessTokenObject);
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.scope(accessTokenObject.getString("scope")) .scope(accessTokenObject.getString("scope"))
.tokenType(accessTokenObject.getString("token_type")) .tokenType(accessTokenObject.getString("token_type"))
.refreshToken(accessTokenObject.getString("refresh_token")) .refreshToken(accessTokenObject.getString("refresh_token"))
.build(); .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"));
} }
} }
@@ -72,31 +70,20 @@ public class AuthMicrosoftRequest extends BaseAuthRequest {
String token = authToken.getAccessToken(); String token = authToken.getAccessToken();
String tokenType = authToken.getTokenType(); String tokenType = authToken.getTokenType();
String jwt = tokenType + " " + token; String jwt = tokenType + " " + token;
HttpResponse response = HttpRequest.get(UrlBuilder.getMicrosoftUserInfoUrl()) HttpResponse response = HttpRequest.get(userInfoUrl(authToken)).header("Authorization", jwt).execute();
.header("Authorization", jwt)
.execute();
String userInfo = response.body(); String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo); JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("id")) .uuid(object.getString("id"))
.username(object.getString("userPrincipalName")) .username(object.getString("userPrincipalName"))
.nickname(object.getString("displayName")) .nickname(object.getString("displayName"))
.location(object.getString("officeLocation")) .location(object.getString("officeLocation"))
.email(object.getString("mail")) .email(object.getString("mail"))
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.token(authToken) .token(authToken)
.source(AuthSource.MICROSOFT) .source(source)
.build(); .build();
}
/**
* 返回认证url,可自行跳转页面
*
* @return 返回授权地址
*/
@Override
public String authorize() {
return UrlBuilder.getMicrosoftAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState());
} }
/** /**
@@ -107,9 +94,75 @@ public class AuthMicrosoftRequest extends BaseAuthRequest {
*/ */
@Override @Override
public AuthResponse refresh(AuthToken authToken) { public AuthResponse refresh(AuthToken authToken) {
String refreshTokenUrl = UrlBuilder.getMicrosoftRefreshUrl(config.getClientId(), config.getClientSecret(), config return AuthResponse.builder()
.getRedirectUri(), authToken.getRefreshToken()); .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,25 +1,23 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
/** /**
* oschina登录 * oschina登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthOschinaRequest extends BaseAuthRequest { public class AuthOschinaRequest extends AuthDefaultRequest {
public AuthOschinaRequest(AuthConfig config) { public AuthOschinaRequest(AuthConfig config) {
super(config, AuthSource.OSCHINA); super(config, AuthSource.OSCHINA);
@@ -27,50 +25,76 @@ public class AuthOschinaRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getOschinaAccessTokenUrl(config.getClientId(), config.getClientSecret(), HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
authCallback.getCode(), config.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error")) { this.checkResponse(accessTokenObject);
throw new AuthException("Unable to get token from oschina using code [" + authCallback.getCode() + "]");
}
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token")) .refreshToken(accessTokenObject.getString("refresh_token"))
.uid(accessTokenObject.getString("uid")) .uid(accessTokenObject.getString("uid"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.build(); .build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getOschinaUserInfoUrl(accessToken)).execute();
JSONObject object = JSONObject.parseObject(response.body()); JSONObject object = JSONObject.parseObject(response.body());
if (object.containsKey("error")) { this.checkResponse(object);
throw new AuthException(object.getString("error_description"));
}
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("id")) .uuid(object.getString("id"))
.username(object.getString("name")) .username(object.getString("name"))
.nickname(object.getString("name")) .nickname(object.getString("name"))
.avatar(object.getString("avatar")) .avatar(object.getString("avatar"))
.blog(object.getString("url")) .blog(object.getString("url"))
.location(object.getString("location")) .location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("gender"))) .gender(AuthUserGender.getRealGender(object.getString("gender")))
.email(object.getString("email")) .email(object.getString("email"))
.token(authToken) .token(authToken)
.source(AuthSource.OSCHINA) .source(source)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 返回获取accessToken的url
* *
* @return 返回授权地址 * @param code 授权回调时带回的授权码
* @return 返回获取accessToken的url
*/ */
@Override @Override
public String authorize() { protected String accessTokenUrl(String code) {
return UrlBuilder.getOschinaAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); 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,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.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);
}
@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
*/
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"));
}
}
}
@@ -6,11 +6,9 @@ import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.*;
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.GlobalAuthUtil;
import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
@@ -22,36 +20,32 @@ import java.util.Map;
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @author yangkai.shen (https://xkcoding.com) * @author yangkai.shen (https://xkcoding.com)
* @version 1.0 * @since 1.1.0
* @since 1.8
*/ */
public class AuthQqRequest extends BaseAuthRequest { public class AuthQqRequest extends AuthDefaultRequest {
public AuthQqRequest(AuthConfig config) { public AuthQqRequest(AuthConfig config) {
super(config, AuthSource.QQ); super(config, AuthSource.QQ);
} }
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getQqAccessTokenUrl(config.getClientId(), config.getClientSecret(), HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
authCallback.getCode(), config.getRedirectUri()); return getAuthToken(response);
HttpResponse response = HttpRequest.get(accessTokenUrl).execute(); }
Map<String, String> accessTokenObject = GlobalAuthUtil.parseStringToMap(response.body());
if (!accessTokenObject.containsKey("access_token")) { @Override
throw new AuthException("Unable to get token from qq using code [" + authCallback.getCode() + "]"); public AuthResponse refresh(AuthToken authToken) {
} HttpResponse response = HttpRequest.get(refreshTokenUrl(authToken.getRefreshToken())).execute();
return AuthToken.builder() return AuthResponse.builder()
.accessToken(accessTokenObject.get("access_token")) .code(AuthResponseStatus.SUCCESS.getCode())
.expireIn(Integer.valueOf(accessTokenObject.get("expires_in"))) .data(getAuthToken(response))
.refreshToken(accessTokenObject.get("refresh_token")) .build();
.build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
String openId = this.getOpenId(authToken); String openId = this.getOpenId(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getQqUserInfoUrl(config.getClientId(), accessToken, openId)) HttpResponse response = doGetUserInfo(authToken);
.execute();
JSONObject object = JSONObject.parseObject(response.body()); JSONObject object = JSONObject.parseObject(response.body());
if (object.getIntValue("ret") != 0) { if (object.getIntValue("ret") != 0) {
throw new AuthException(object.getString("msg")); throw new AuthException(object.getString("msg"));
@@ -63,31 +57,29 @@ public class AuthQqRequest extends BaseAuthRequest {
String location = String.format("%s-%s", object.getString("province"), object.getString("city")); String location = String.format("%s-%s", object.getString("province"), object.getString("city"));
return AuthUser.builder() return AuthUser.builder()
.username(object.getString("nickname")) .username(object.getString("nickname"))
.nickname(object.getString("nickname")) .nickname(object.getString("nickname"))
.avatar(avatar) .avatar(avatar)
.location(location) .location(location)
.uuid(openId) .uuid(openId)
.gender(AuthUserGender.getRealGender(object.getString("gender"))) .gender(AuthUserGender.getRealGender(object.getString("gender")))
.token(authToken) .token(authToken)
.source(AuthSource.QQ) .source(source)
.build(); .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(), config.getState());
}
private String getOpenId(AuthToken authToken) { private String getOpenId(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = HttpRequest.get(UrlBuilder.fromBaseUrl("https://graph.qq.com/oauth2.0/me")
HttpResponse response = HttpRequest.get(UrlBuilder.getQqOpenidUrl("https://graph.qq.com/oauth2.0/me", accessToken, config.isUnionId())) .queryParam("access_token", authToken.getAccessToken())
.execute(); .queryParam("unionid", config.isUnionId() ? 1 : 0)
.build()).execute();
if (response.isOk()) { if (response.isOk()) {
String body = response.body(); String body = response.body();
String removePrefix = StrUtil.replace(body, "callback(", ""); String removePrefix = StrUtil.replace(body, "callback(", "");
@@ -106,4 +98,31 @@ public class AuthQqRequest extends BaseAuthRequest {
throw new AuthException("request error"); 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,112 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Objects;
import static me.zhyd.oauth.config.AuthSource.RENREN;
import static me.zhyd.oauth.model.AuthResponseStatus.SUCCESS;
/**
* 人人登录
*
* @author hongwei.peng (pengisgood(at)gmail(dot)com)
* @since 1.9.0
*/
public class AuthRenrenRequest extends AuthDefaultRequest {
public AuthRenrenRequest(AuthConfig config) {
super(config, RENREN);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
HttpResponse response = doGetUserInfo(authToken);
JSONObject userObj = JSONObject.parseObject(response.body()).getJSONObject("response");
return AuthUser.builder()
.uuid(userObj.getString("id"))
.avatar(getAvatarUrl(userObj))
.nickname(userObj.getString("name"))
.company(getCompany(userObj))
.gender(getGender(userObj))
.token(authToken)
.source(source)
.build();
}
@Override
public AuthResponse refresh(AuthToken authToken) {
return AuthResponse.builder()
.code(SUCCESS.getCode())
.data(getToken(this.refreshTokenUrl(authToken.getRefreshToken())))
.build();
}
private AuthToken getToken(String url) {
HttpResponse response = HttpRequest.post(url).execute();
JSONObject jsonObject = JSONObject.parseObject(response.body());
if (jsonObject.containsKey("error")) {
throw new AuthException("Failed to get token from Renren: " + jsonObject);
}
return AuthToken.builder()
.tokenType(jsonObject.getString("token_type"))
.expireIn(jsonObject.getIntValue("expires_in"))
.accessToken(jsonObject.getString("access_token"))
.refreshToken(jsonObject.getString("refresh_token"))
.openId(jsonObject.getJSONObject("user").getString("id"))
.build();
}
private String getAvatarUrl(JSONObject userObj) {
JSONArray jsonArray = userObj.getJSONArray("avatar");
if (Objects.isNull(jsonArray) || jsonArray.isEmpty()) {
return null;
}
return jsonArray.getJSONObject(0).getString("url");
}
private AuthUserGender getGender(JSONObject userObj) {
JSONObject basicInformation = userObj.getJSONObject("basicInformation");
if (Objects.isNull(basicInformation)) {
return AuthUserGender.UNKNOWN;
}
return AuthUserGender.getRealGender(basicInformation.getString("sex"));
}
private String getCompany(JSONObject userObj) {
JSONArray jsonArray = userObj.getJSONArray("work");
if (Objects.isNull(jsonArray) || jsonArray.isEmpty()) {
return null;
}
return jsonArray.getJSONObject(0).getString("name");
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("userId", authToken.getOpenId())
.build();
}
}
@@ -3,22 +3,36 @@ package me.zhyd.oauth.request;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse; import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthResponseStatus;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
/** /**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8 * @since 1.8
*/ */
public interface AuthRequest { public interface AuthRequest {
/** /**
* 返回认证url,可自行跳转页面 * 返回授权url,可自行跳转页面
* <p>
* 不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击。
* 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址,在回调方法中对{@code state}进行校验
* *
* @return 返回授权地址 * @return 返回授权地址
*/ */
@Deprecated
default String authorize() { 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);
} }
/** /**
@@ -28,7 +42,7 @@ public interface AuthRequest {
* @return 返回登录成功后的用户信息 * @return 返回登录成功后的用户信息
*/ */
default AuthResponse login(AuthCallback authCallback) { default AuthResponse login(AuthCallback authCallback) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED); throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
} }
/** /**
@@ -38,7 +52,7 @@ public interface AuthRequest {
* @return AuthResponse * @return AuthResponse
*/ */
default AuthResponse revoke(AuthToken authToken) { default AuthResponse revoke(AuthToken authToken) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED); throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
} }
/** /**
@@ -48,6 +62,6 @@ public interface AuthRequest {
* @return AuthResponse * @return AuthResponse
*/ */
default AuthResponse refresh(AuthToken authToken) { default AuthResponse refresh(AuthToken authToken) {
throw new AuthException(ResponseStatus.NOT_IMPLEMENTED); throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
} }
} }
@@ -0,0 +1,97 @@
package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.UrlBuilder;
import static me.zhyd.oauth.config.AuthSource.STACK_OVERFLOW;
import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap;
/**
* Stack Overflow登录
*
* @author hongwei.peng (pengisgood(at)gmail(dot)com)
* @since 1.9.0
*/
public class AuthStackOverflowRequest extends AuthDefaultRequest {
public AuthStackOverflowRequest(AuthConfig config) {
super(config, STACK_OVERFLOW);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = accessTokenUrl(authCallback.getCode());
HttpResponse response = HttpRequest.post(accessTokenUrl)
.contentType("application/x-www-form-urlencoded")
.form(parseQueryToMap(accessTokenUrl))
.execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String userInfoUrl = UrlBuilder.fromBaseUrl(this.source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("site", "stackoverflow")
.queryParam("key", this.config.getStackOverflowKey())
.build();
HttpResponse response = HttpRequest.get(userInfoUrl).execute();
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
JSONObject userObj = object.getJSONArray("items").getJSONObject(0);
return AuthUser.builder()
.uuid(userObj.getString("user_id"))
.avatar(userObj.getString("profile_image"))
.location(userObj.getString("location"))
.nickname(userObj.getString("display_name"))
.blog(userObj.getString("website_url"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
/**
* 返回带{@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,15 +1,14 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.GlobalAuthUtil; import me.zhyd.oauth.utils.GlobalAuthUtil;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
@@ -17,10 +16,9 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 淘宝登录 * 淘宝登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.1.0
* @since 1.8
*/ */
public class AuthTaobaoRequest extends BaseAuthRequest { public class AuthTaobaoRequest extends AuthDefaultRequest {
public AuthTaobaoRequest(AuthConfig config) { public AuthTaobaoRequest(AuthConfig config) {
super(config, AuthSource.TAOBAO); super(config, AuthSource.TAOBAO);
@@ -33,12 +31,10 @@ public class AuthTaobaoRequest extends BaseAuthRequest {
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessCode = authToken.getAccessCode(); HttpResponse response = doPostAuthorizationCode(authToken.getAccessCode());
HttpResponse response = HttpRequest.post(UrlBuilder.getTaobaoAccessTokenUrl(this.config.getClientId(), this.config
.getClientSecret(), accessCode, this.config.getRedirectUri())).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error")) { if (accessTokenObject.containsKey("error")) {
throw new AuthException(ResponseStatus.FAILURE + ":" + accessTokenObject.getString("error_description")); throw new AuthException(accessTokenObject.getString("error_description"));
} }
authToken.setAccessToken(accessTokenObject.getString("access_token")); authToken.setAccessToken(accessTokenObject.getString("access_token"));
authToken.setRefreshToken(accessTokenObject.getString("refresh_token")); authToken.setRefreshToken(accessTokenObject.getString("refresh_token"));
@@ -48,22 +44,30 @@ public class AuthTaobaoRequest extends BaseAuthRequest {
String nick = GlobalAuthUtil.urlDecode(accessTokenObject.getString("taobao_user_nick")); String nick = GlobalAuthUtil.urlDecode(accessTokenObject.getString("taobao_user_nick"));
return AuthUser.builder() return AuthUser.builder()
.uuid(accessTokenObject.getString("taobao_user_id")) .uuid(accessTokenObject.getString("taobao_user_id"))
.username(nick) .username(nick)
.nickname(nick) .nickname(nick)
.gender(AuthUserGender.UNKNOW) .gender(AuthUserGender.UNKNOWN)
.token(authToken) .token(authToken)
.source(AuthSource.TAOBAO) .source(source)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
* *
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址 * @return 返回授权地址
* @since 1.9.3
*/ */
@Override @Override
public String authorize() { public String authorize(String state) {
return UrlBuilder.getTaobaoAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); 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,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.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*;
/**
* Teambition授权登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.9.0
*/
public class AuthTeambitionRequest extends AuthDefaultRequest {
public AuthTeambitionRequest(AuthConfig config) {
super(config, AuthSource.TEAMBITION);
}
/**
* @param authCallback 回调返回的参数
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
HttpResponse response = HttpRequest.post(source.accessToken())
.form("client_id", config.getClientId())
.form("client_secret", config.getClientSecret())
.form("code", authCallback.getCode())
.form("grant_type", "code")
.execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpResponse response = HttpRequest.get(source.userInfo())
.header("Authorization", "OAuth2 " + accessToken)
.execute();
JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object);
authToken.setUid(object.getString("_id"));
return AuthUser.builder()
.uuid(object.getString("_id"))
.username(object.getString("name"))
.nickname(object.getString("name"))
.avatar(object.getString("avatarUrl"))
.blog(object.getString("website"))
.location(object.getString("location"))
.email(object.getString("email"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source)
.build();
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
String uid = oldToken.getUid();
String refreshToken = oldToken.getRefreshToken();
HttpResponse response = HttpRequest.post(source.refresh())
.form("_userId", uid)
.form("refresh_token", refreshToken)
.execute();
JSONObject refreshTokenObject = JSONObject.parseObject(response.body());
this.checkResponse(refreshTokenObject);
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(refreshTokenObject.getString("access_token"))
.refreshToken(refreshTokenObject.getString("refresh_token"))
.build())
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
if ((object.containsKey("message") && object.containsKey("name"))) {
throw new AuthException(object.getString("name") + ", " + object.getString("message"));
}
}
}
@@ -1,25 +1,23 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
/** /**
* 腾讯云登录 * 腾讯云登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthTencentCloudRequest extends BaseAuthRequest { public class AuthTencentCloudRequest extends AuthDefaultRequest {
public AuthTencentCloudRequest(AuthConfig config) { public AuthTencentCloudRequest(AuthConfig config) {
super(config, AuthSource.TENCENT_CLOUD); super(config, AuthSource.TENCENT_CLOUD);
@@ -27,51 +25,65 @@ public class AuthTencentCloudRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getTencentCloudAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.getIntValue("code") != 0) { this.checkResponse(accessTokenObject);
throw new AuthException("Unable to get token from tencent cloud using code [" + authCallback.getCode() + "]: " + accessTokenObject.get("msg"));
}
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token")) .refreshToken(accessTokenObject.getString("refresh_token"))
.build(); .build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken(); HttpResponse response = doGetUserInfo(authToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getTencentCloudUserInfoUrl(accessToken)).execute();
JSONObject object = JSONObject.parseObject(response.body()); JSONObject object = JSONObject.parseObject(response.body());
if (object.getIntValue("code") != 0) { this.checkResponse(object);
throw new AuthException(object.getString("msg"));
}
object = object.getJSONObject("data"); object = object.getJSONObject("data");
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("id")) .uuid(object.getString("id"))
.username(object.getString("name")) .username(object.getString("name"))
.avatar("https://dev.tencent.com/" + object.getString("avatar")) .avatar("https://dev.tencent.com/" + object.getString("avatar"))
.blog("https://dev.tencent.com/" + object.getString("path")) .blog("https://dev.tencent.com/" + object.getString("path"))
.nickname(object.getString("name")) .nickname(object.getString("name"))
.company(object.getString("company")) .company(object.getString("company"))
.location(object.getString("location")) .location(object.getString("location"))
.gender(AuthUserGender.getRealGender(object.getString("sex"))) .gender(AuthUserGender.getRealGender(object.getString("sex")))
.email(object.getString("email")) .email(object.getString("email"))
.remark(object.getString("slogan")) .remark(object.getString("slogan"))
.token(authToken) .token(authToken)
.source(AuthSource.TENCENT_CLOUD) .source(source)
.build(); .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 返回授权地址 * @return 返回授权地址
* @since 1.9.3
*/ */
@Override @Override
public String authorize() { public String authorize(String state) {
return UrlBuilder.getTencentCloudAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); 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,23 +1,24 @@
package me.zhyd.oauth.request; package me.zhyd.oauth.request;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthToutiaoErrorCode; import me.zhyd.oauth.enums.AuthToutiaoErrorCode;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*; 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 me.zhyd.oauth.utils.UrlBuilder;
/** /**
* 今日头条登录 * 今日头条登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.5 * @since 1.6.0-beta
* @since 1.5
*/ */
public class AuthToutiaoRequest extends BaseAuthRequest { public class AuthToutiaoRequest extends AuthDefaultRequest {
public AuthToutiaoRequest(AuthConfig config) { public AuthToutiaoRequest(AuthConfig config) {
super(config, AuthSource.TOUTIAO); super(config, AuthSource.TOUTIAO);
@@ -25,30 +26,25 @@ public class AuthToutiaoRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getToutiaoAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); HttpResponse response = doGetAuthorizationCode(authCallback.getCode());
HttpResponse response = HttpRequest.get(accessTokenUrl).execute();
JSONObject accessTokenObject = JSONObject.parseObject(response.body()); JSONObject accessTokenObject = JSONObject.parseObject(response.body());
if (accessTokenObject.containsKey("error_code")) { this.checkResponse(accessTokenObject);
throw new AuthException(AuthToutiaoErrorCode.getErrorCode(accessTokenObject.getIntValue("error_code")).getDesc());
}
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.openId(accessTokenObject.getString("open_id")) .openId(accessTokenObject.getString("open_id"))
.build(); .build();
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { 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()); JSONObject userProfile = JSONObject.parseObject(userResponse.body());
if (userProfile.containsKey("error_code")) { this.checkResponse(userProfile);
throw new AuthException(AuthToutiaoErrorCode.getErrorCode(userProfile.getIntValue("error_code")).getDesc());
}
JSONObject user = userProfile.getJSONObject("data"); JSONObject user = userProfile.getJSONObject("data");
@@ -56,24 +52,75 @@ public class AuthToutiaoRequest extends BaseAuthRequest {
String anonymousUserName = "匿名用户"; String anonymousUserName = "匿名用户";
return AuthUser.builder() return AuthUser.builder()
.uuid(user.getString("uid")) .uuid(user.getString("uid"))
.username(isAnonymousUser ? anonymousUserName : user.getString("screen_name")) .username(isAnonymousUser ? anonymousUserName : user.getString("screen_name"))
.nickname(isAnonymousUser ? anonymousUserName : user.getString("screen_name")) .nickname(isAnonymousUser ? anonymousUserName : user.getString("screen_name"))
.avatar(user.getString("avatar_url")) .avatar(user.getString("avatar_url"))
.remark(user.getString("description")) .remark(user.getString("description"))
.gender(AuthUserGender.getRealGender(user.getString("gender"))) .gender(AuthUserGender.getRealGender(user.getString("gender")))
.token(authToken) .token(authToken)
.source(AuthSource.TOUTIAO) .source(source)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
* *
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址 * @return 返回授权地址
* @since 1.9.3
*/ */
@Override @Override
public String authorize() { public String authorize(String state) {
return UrlBuilder.getToutiaoAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); 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());
}
} }
} }
@@ -5,6 +5,7 @@ import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.*; import me.zhyd.oauth.model.*;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
@@ -13,10 +14,9 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 微信登录 * 微信登录
* *
* @author yangkai.shen (https://xkcoding.com) * @author yangkai.shen (https://xkcoding.com)
* @version 1.0 * @since 1.1.0
* @since 1.8
*/ */
public class AuthWeChatRequest extends BaseAuthRequest { public class AuthWeChatRequest extends AuthDefaultRequest {
public AuthWeChatRequest(AuthConfig config) { public AuthWeChatRequest(AuthConfig config) {
super(config, AuthSource.WECHAT); super(config, AuthSource.WECHAT);
} }
@@ -29,50 +29,42 @@ public class AuthWeChatRequest extends BaseAuthRequest {
*/ */
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getWeChatAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); return this.getToken(accessTokenUrl(authCallback.getCode()));
return this.getToken(accessTokenUrl);
} }
@Override @Override
protected AuthUser getUserInfo(AuthToken authToken) { protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
String openId = authToken.getOpenId(); String openId = authToken.getOpenId();
HttpResponse response = HttpRequest.get(UrlBuilder.getWeChatUserInfoUrl(accessToken, openId)).execute(); HttpResponse response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response.body()); JSONObject object = JSONObject.parseObject(response.body());
this.checkResponse(object); this.checkResponse(object);
String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city")); 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();
}
/** if (object.containsKey("unionid")) {
* 返回认证url,可自行跳转页面 authToken.setUnionId(object.getString("unionid"));
* }
* @return 返回授权地址
*/ return AuthUser.builder()
@Override .username(object.getString("nickname"))
public String authorize() { .nickname(object.getString("nickname"))
return UrlBuilder.getWeChatAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); .avatar(object.getString("headimgurl"))
.location(location)
.uuid(openId)
.gender(AuthUserGender.getRealGender(object.getString("sex")))
.token(authToken)
.source(source)
.build();
} }
@Override @Override
public AuthResponse refresh(AuthToken oldToken) { public AuthResponse refresh(AuthToken oldToken) {
String refreshTokenUrl = UrlBuilder.getWeChatRefreshUrl(config.getClientId(), oldToken.getRefreshToken());
return AuthResponse.builder() return AuthResponse.builder()
.code(ResponseStatus.SUCCESS.getCode()) .code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl)) .data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build(); .build();
} }
/** /**
@@ -99,10 +91,74 @@ public class AuthWeChatRequest extends BaseAuthRequest {
this.checkResponse(accessTokenObject); this.checkResponse(accessTokenObject);
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token")) .refreshToken(accessTokenObject.getString("refresh_token"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.openId(accessTokenObject.getString("openid")) .openId(accessTokenObject.getString("openid"))
.build(); .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();
} }
} }
@@ -5,11 +5,11 @@ import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
import me.zhyd.oauth.utils.IpUtils; import me.zhyd.oauth.utils.IpUtils;
import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder; import me.zhyd.oauth.utils.UrlBuilder;
@@ -19,10 +19,9 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 微博登录 * 微博登录
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class AuthWeiboRequest extends BaseAuthRequest { public class AuthWeiboRequest extends AuthDefaultRequest {
public AuthWeiboRequest(AuthConfig config) { public AuthWeiboRequest(AuthConfig config) {
super(config, AuthSource.WEIBO); super(config, AuthSource.WEIBO);
@@ -30,20 +29,18 @@ public class AuthWeiboRequest extends BaseAuthRequest {
@Override @Override
protected AuthToken getAccessToken(AuthCallback authCallback) { protected AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = UrlBuilder.getWeiboAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
.getRedirectUri());
HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
String accessTokenStr = response.body(); String accessTokenStr = response.body();
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr); JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
if (accessTokenObject.containsKey("error")) { if (accessTokenObject.containsKey("error")) {
throw new AuthException("Unable to get token from weibo using code [" + authCallback.getCode() + "]:" + accessTokenObject.getString("error_description")); throw new AuthException(accessTokenObject.getString("error_description"));
} }
return AuthToken.builder() return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token")) .accessToken(accessTokenObject.getString("access_token"))
.uid(accessTokenObject.getString("uid")) .uid(accessTokenObject.getString("uid"))
.openId(accessTokenObject.getString("uid")) .openId(accessTokenObject.getString("uid"))
.expireIn(accessTokenObject.getIntValue("expires_in")) .expireIn(accessTokenObject.getIntValue("expires_in"))
.build(); .build();
} }
@Override @Override
@@ -51,37 +48,41 @@ public class AuthWeiboRequest extends BaseAuthRequest {
String accessToken = authToken.getAccessToken(); String accessToken = authToken.getAccessToken();
String uid = authToken.getUid(); String uid = authToken.getUid();
String oauthParam = String.format("uid=%s&access_token=%s", uid, accessToken); String oauthParam = String.format("uid=%s&access_token=%s", uid, accessToken);
HttpResponse response = HttpRequest.get(UrlBuilder.getWeiboUserInfoUrl(oauthParam)) HttpResponse response = HttpRequest.get(userInfoUrl(authToken))
.header("Authorization", "OAuth2 " + oauthParam) .header("Authorization", "OAuth2 " + oauthParam)
.header("API-RemoteIP", IpUtils.getIp()) .header("API-RemoteIP", IpUtils.getLocalIp())
.execute(); .execute();
String userInfo = response.body(); String userInfo = response.body();
JSONObject object = JSONObject.parseObject(userInfo); JSONObject object = JSONObject.parseObject(userInfo);
if (object.containsKey("error")) { if (object.containsKey("error")) {
throw new AuthException(object.getString("error")); throw new AuthException(object.getString("error"));
} }
return AuthUser.builder() return AuthUser.builder()
.uuid(object.getString("id")) .uuid(object.getString("id"))
.username(object.getString("name")) .username(object.getString("name"))
.avatar(object.getString("profile_image_url")) .avatar(object.getString("profile_image_url"))
.blog(StringUtils.isEmpty(object.getString("url")) ? "https://weibo.com/" + object.getString("profile_url") : object .blog(StringUtils.isEmpty(object.getString("url")) ? "https://weibo.com/" + object.getString("profile_url") : object
.getString("url")) .getString("url"))
.nickname(object.getString("screen_name")) .nickname(object.getString("screen_name"))
.location(object.getString("location")) .location(object.getString("location"))
.remark(object.getString("description")) .remark(object.getString("description"))
.gender(AuthUserGender.getRealGender(object.getString("gender"))) .gender(AuthUserGender.getRealGender(object.getString("gender")))
.token(authToken) .token(authToken)
.source(AuthSource.WEIBO) .source(source)
.build(); .build();
} }
/** /**
* 返回认证url,可自行跳转页面 * 返回获取userInfo的url
* *
* @return 返回授权地址 * @param authToken authToken
* @return 返回获取userInfo的url
*/ */
@Override @Override
public String authorize() { protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.getWeiboAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("uid", authToken.getUid())
.build();
} }
} }
@@ -1,66 +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.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;
/**
* @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 (!AuthChecker.isSupportedAuth(config, source)) {
throw new AuthException(ResponseStatus.PARAMETER_INCOMPLETE);
}
// 校验配置合法性
AuthChecker.checkConfig(config, source);
}
protected abstract AuthToken getAccessToken(AuthCallback authCallback);
protected abstract AuthUser getUserInfo(AuthToken authToken);
@Override
public AuthResponse login(AuthCallback authCallback) {
try {
AuthChecker.checkCode(source == AuthSource.ALIPAY ? authCallback.getAuth_code() : authCallback.getCode());
AuthChecker.checkState(authCallback.getState(), config.getState());
AuthToken authToken = this.getAccessToken(authCallback);
AuthUser user = this.getUserInfo(authToken);
return AuthResponse.builder().code(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();
}
@@ -3,14 +3,13 @@ package me.zhyd.oauth.utils;
import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.request.ResponseStatus; import me.zhyd.oauth.model.AuthResponseStatus;
/** /**
* 授权配置类的校验器 * 授权配置类的校验器
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.6.1-beta
* @since 1.8
*/ */
public class AuthChecker { public class AuthChecker {
@@ -20,12 +19,16 @@ public class AuthChecker {
* @param config config * @param config config
* @param source source * @param source source
* @return true or false * @return true or false
* @since 1.6.1-beta
*/ */
public static boolean isSupportedAuth(AuthConfig config, AuthSource source) { public static boolean isSupportedAuth(AuthConfig config, AuthSource source) {
boolean isSupported = StringUtils.isNotEmpty(config.getClientId()) && StringUtils.isNotEmpty(config.getClientSecret()) && StringUtils.isNotEmpty(config.getRedirectUri()); boolean isSupported = StringUtils.isNotEmpty(config.getClientId()) && StringUtils.isNotEmpty(config.getClientSecret()) && StringUtils.isNotEmpty(config.getRedirectUri());
if (isSupported && AuthSource.ALIPAY == source) { if (isSupported && AuthSource.ALIPAY == source) {
isSupported = StringUtils.isNotEmpty(config.getAlipayPublicKey()); isSupported = StringUtils.isNotEmpty(config.getAlipayPublicKey());
} }
if (isSupported && AuthSource.STACK_OVERFLOW == source) {
isSupported = StringUtils.isNotEmpty(config.getStackOverflowKey());
}
return isSupported; return isSupported;
} }
@@ -34,19 +37,20 @@ public class AuthChecker {
* *
* @param config config * @param config config
* @param source source * @param source source
* @since 1.6.1-beta
*/ */
public static void checkConfig(AuthConfig config, AuthSource source) { public static void checkConfig(AuthConfig config, AuthSource source) {
String redirectUri = config.getRedirectUri(); String redirectUri = config.getRedirectUri();
if (!GlobalAuthUtil.isHttpProtocol(redirectUri) && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) { if (!GlobalAuthUtil.isHttpProtocol(redirectUri) && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) {
throw new AuthException(ResponseStatus.ILLEGAL_REDIRECT_URI); throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI);
} }
// facebook的回调地址必须为https的链接 // facebook的回调地址必须为https的链接
if (AuthSource.FACEBOOK == source && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) { 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 // 支付宝在创建回调地址时,不允许使用localhost或者127.0.0.1
if (AuthSource.ALIPAY == source && GlobalAuthUtil.isLocalHost(redirectUri)) { if (AuthSource.ALIPAY == source && GlobalAuthUtil.isLocalHost(redirectUri)) {
throw new AuthException(ResponseStatus.ILLEGAL_REDIRECT_URI); throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI);
} }
} }
@@ -54,31 +58,11 @@ public class AuthChecker {
* 校验回调传回的code * 校验回调传回的code
* *
* @param code 回调时传回的code * @param code 回调时传回的code
* @since 1.8.0
*/ */
public static void checkCode(String code) { public static void checkCode(String code) {
if (StringUtils.isEmpty(code)) { if (StringUtils.isEmpty(code)) {
throw new AuthException(ResponseStatus.ILLEGAL_CODE); throw new AuthException(AuthResponseStatus.ILLEGAL_CODE);
}
}
/**
* 校验state的合法性防止被CSRF
*
* @param newState 新的state,一般为回调时传回的state(可能被篡改)
* @param originalState 原始的state,发起授权时向第三方平台传递的state
*/
public static void checkState(String newState, String originalState) {
// 如果原始state为空,表示当前平台未使用state
if (StringUtils.isEmpty(originalState)) {
return;
}
// 如果授权之前使用了state,但是回调时未返回state,则表示当前请求为非法的请求,可能正在被CSRF攻击
if (StringUtils.isEmpty(newState)) {
throw new AuthException(ResponseStatus.ILLEGAL_REQUEST);
}
// 如果授权前后的state不一致,则表示当前请求为非法的请求,新的state可能为伪造
if (!newState.equals(originalState)) {
throw new AuthException(ResponseStatus.ILLEGAL_REQUEST);
} }
} }
} }
@@ -1,165 +0,0 @@
package me.zhyd.oauth.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.request.ResponseStatus;
import java.nio.charset.Charset;
import java.util.concurrent.ConcurrentHashMap;
/**
* state工具,负责创建、获取和删除state
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/
@Slf4j
public class AuthState {
/**
* 空字符串
*/
private static final String EMPTY_STR = "";
/**
* state存储器
*/
private static ConcurrentHashMap<String, String> stateBucket = new ConcurrentHashMap<>();
/**
* 生成随机的state
*
* @param source oauth平台
* @return state
*/
public static String create(String source) {
return create(source, RandomUtil.randomString(4));
}
/**
* 创建state
*
* @param source oauth平台
* @param body 希望加密到state的消息体
* @return state
*/
public static String create(String source, Object body) {
return create(source, JSON.toJSONString(body));
}
/**
* 创建state
*
* @param source oauth平台
* @param body 希望加密到state的消息体
* @return state
*/
public static String create(String source, String body) {
String currentIp = getCurrentIp();
String simpleKey = ((source + currentIp));
String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
log.debug("Create the state: ip={}, platform={}, simpleKey={}, key={}, body={}", currentIp, source, simpleKey, key, body);
if (stateBucket.containsKey(key)) {
log.debug("Get from bucket: {}", stateBucket.get(key));
return stateBucket.get(key);
}
String simpleState = source + "_" + currentIp + "_" + body;
String state = Base64.encode(simpleState.getBytes(Charset.forName("UTF-8")));
log.debug("Create a new state: {}", state, simpleState);
stateBucket.put(key, state);
return state;
}
/**
* 获取state
*
* @param source oauth平台
* @return state
*/
public static String get(String source) {
String currentIp = getCurrentIp();
String simpleKey = ((source + currentIp));
String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
log.debug("Get state by the key[{}], current ip[{}]", key, currentIp);
return stateBucket.get(key);
}
/**
* 获取state中保存的body内容
*
* @param source oauth平台
* @param state 加密后的state
* @param clazz body的实际类型
* @param <T> 需要转换的具体的class类型
* @return state
*/
public static <T> T getBody(String source, String state, Class<T> clazz) {
if (StringUtils.isEmpty(state) || null == clazz) {
return null;
}
log.debug("Get body from the state[{}] of the {} and convert it to {}", state, source, clazz.toString());
String currentIp = getCurrentIp();
String decodedState = Base64.decodeStr(state);
log.debug("The decoded state is [{}]", decodedState);
if (!decodedState.startsWith(source)) {
return null;
}
String noneSourceState = decodedState.substring(source.length() + 1);
if (!noneSourceState.startsWith(currentIp)) {
// ip不相同,可能为非法的请求
throw new AuthException(ResponseStatus.ILLEGAL_REQUEST);
}
String body = noneSourceState.substring(currentIp.length() + 1);
log.debug("body is [{}]", body);
if (clazz == String.class) {
return (T) body;
}
if (clazz == Integer.class) {
return (T) Integer.valueOf(Integer.parseInt(body));
}
if (clazz == Long.class) {
return (T) Long.valueOf(Long.parseLong(body));
}
if (clazz == Short.class) {
return (T) Short.valueOf(Short.parseShort(body));
}
if (clazz == Double.class) {
return (T) Double.valueOf(Double.parseDouble(body));
}
if (clazz == Float.class) {
return (T) Float.valueOf(Float.parseFloat(body));
}
if (clazz == Boolean.class) {
return (T) Boolean.valueOf(Boolean.parseBoolean(body));
}
if (clazz == Byte.class) {
return (T) Byte.valueOf(Byte.parseByte(body));
}
return JSON.parseObject(body, clazz);
}
/**
* 登录成功后,清除state
*
* @param source oauth平台
*/
public static void delete(String source) {
String currentIp = getCurrentIp();
String simpleKey = ((source + currentIp));
String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
log.debug("Delete used state[{}] by the key[{}], current ip[{}]", stateBucket.get(key), key, currentIp);
stateBucket.remove(key);
}
private static String getCurrentIp() {
String currentIp = IpUtils.getIp();
return StringUtils.isEmpty(currentIp) ? EMPTY_STR : currentIp;
}
}
@@ -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; package me.zhyd.oauth.utils;
import cn.hutool.core.codec.Base64; 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 me.zhyd.oauth.exception.AuthException;
import javax.crypto.Mac; import javax.crypto.Mac;
@@ -12,16 +15,13 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.*;
import java.util.HashMap;
import java.util.Map;
/** /**
* 全局的工具类 * 全局的工具类
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class GlobalAuthUtil { public class GlobalAuthUtil {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8; private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
@@ -44,7 +44,7 @@ public class GlobalAuthUtil {
} }
} }
private static String urlEncode(String value) { public static String urlEncode(String value) {
if (value == null) { if (value == null) {
return ""; return "";
} }
@@ -82,6 +82,26 @@ public class GlobalAuthUtil {
return res; 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) { public static boolean isHttpProtocol(String url) {
if (StringUtils.isEmpty(url)) { if (StringUtils.isEmpty(url)) {
return false; return false;
@@ -7,8 +7,7 @@ import java.net.UnknownHostException;
* 获取IP的工具类 * 获取IP的工具类
* *
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.0
*/ */
public class IpUtils { public class IpUtils {
@@ -17,7 +16,7 @@ public class IpUtils {
* *
* @return ip * @return ip
*/ */
public static String getIp() { public static String getLocalIp() {
try { try {
return InetAddress.getLocalHost().getHostAddress(); return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
@@ -25,4 +24,4 @@ public class IpUtils {
return null; return null;
} }
} }
} }
@@ -1,9 +1,11 @@
package me.zhyd.oauth.utils; package me.zhyd.oauth.utils;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;
/** /**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0 * @since 1.0.0
* @since 1.8
*/ */
public class StringUtils { public class StringUtils {
@@ -14,4 +16,24 @@ public class StringUtils {
public static boolean isNotEmpty(String str) { public static boolean isNotEmpty(String str) {
return !isEmpty(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 -812
View File
@@ -1,844 +1,78 @@
package me.zhyd.oauth.utils; 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) * @author yangkai.shen (https://xkcoding.com)
* @version 1.0 * @since 1.9.0
* @since 1.0
*/ */
@Setter
public class UrlBuilder { public class UrlBuilder {
private static final String GITHUB_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}"; private final Map<String, Object> params = new LinkedHashMap<>(7);
private static final String GITHUB_USER_INFO_PATTERN = "{0}?access_token={1}"; private String baseUrl;
private static final String GITHUB_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&state={3}";
private static final String GOOGLE_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&scope=openid%20email%20profile&redirect_uri={2}&state={3}"; private UrlBuilder() {
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 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}&state={3}";
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}&state={3}";
private static final String DING_TALK_QRCONNECT_PATTERN = "{0}?appid={1}&response_type=code&scope=snsapi_login&redirect_uri={2}&state={3}";
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&state={3}";
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}&state={3}";
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&state={3}";
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&state={3}";
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}&state={3}";
private static final String ALIPAY_AUTHORIZE_PATTERN = "{0}?app_id={1}&scope=auth_user&redirect_uri={2}&state={3}";
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={2}";
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=1%203%204%206&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";
/**
* 获取state,如果为空, 则默认去当前日期的时间戳
*
* @param state state
*/
private static Object getState(String state) {
return StringUtils.isEmpty(state) ? String.valueOf(System.currentTimeMillis()) : state;
} }
/** /**
* 获取githubtoken的接口地址 * @param baseUrl 基础路径
* * @return the new {@code UrlBuilder}
* @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) { public static UrlBuilder fromBaseUrl(String baseUrl) {
return MessageFormat.format(GITHUB_ACCESS_TOKEN_PATTERN, AuthSource.GITHUB.accessToken(), clientId, clientSecret, code, redirectUri); UrlBuilder builder = new UrlBuilder();
builder.setBaseUrl(baseUrl);
return builder;
} }
/** /**
* 获取github用户详情的接口地址 * 添加参数
* *
* @param token github 应用的token * @param key 参数名称
* @return full url * @param value 参数值
* @return this UrlBuilder
*/ */
public static String getGithubUserInfoUrl(String token) { public UrlBuilder queryParam(String key, Object value) {
return MessageFormat.format(GITHUB_USER_INFO_PATTERN, AuthSource.GITHUB.userInfo(), token); Assert.notBlank(key, "参数名不能为空");
String valueAsString = (value != null ? value.toString() : null);
this.params.put(key, valueAsString);
return this;
} }
/** /**
* 获取github授权地址 * 构造url
* *
* @param clientId github 应用的Client ID * @return url
* @param redirectUrl github 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/ */
public static String getGithubAuthorizeUrl(String clientId, String redirectUrl, String state) { public String build() {
return MessageFormat.format(GITHUB_AUTHORIZE_PATTERN, AuthSource.GITHUB.authorize(), clientId, redirectUrl, getState(state)); return this.build(false);
} }
/** /**
* 获取weibo token的接口地址 * 构造url
* *
* @param clientId weibo 应用的App Key * @param encode 转码
* @param clientSecret weibo 应用的App Secret * @return url
* @param code weibo 授权前的code,用来换token
* @param redirectUri 待跳转的页面
* @return full url
*/ */
public static String getWeiboAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { public String build(boolean encode) {
return MessageFormat.format(WEIBO_ACCESS_TOKEN_PATTERN, AuthSource.WEIBO.accessToken(), clientId, clientSecret, code, redirectUri); if (MapUtil.isEmpty(this.params)) {
} return this.baseUrl;
}
/** String baseUrl = StringUtils.appendIfNotContain(this.baseUrl, "?", "&");
* 获取weibo用户详情的接口地址 String paramString = GlobalAuthUtil.parseMapToString(this.params, encode);
* return baseUrl + paramString;
* @param token weibo 应用的token
* @return full 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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getWeiboAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(WEIBO_AUTHORIZE_PATTERN, AuthSource.WEIBO.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return json
*/
public static String getGiteeAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(GITEE_AUTHORIZE_PATTERN, AuthSource.GITEE.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取钉钉登录二维码的地址
*
* @param clientId 钉钉 应用的App Id
* @param redirectUrl 钉钉 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getDingTalkQrConnectUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(DING_TALK_QRCONNECT_PATTERN, AuthSource.DINGTALK.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取钉钉用户信息的地址
*
* @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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return json
*/
public static String getBaiduAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(BAIDU_AUTHORIZE_PATTERN, AuthSource.BAIDU.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取收回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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getCsdnAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(CSDN_AUTHORIZE_PATTERN, AuthSource.CSDN.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getCodingAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(CODING_AUTHORIZE_PATTERN, AuthSource.CODING.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取腾讯云开发者平台 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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getTencentCloudAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(TENCENT_AUTHORIZE_PATTERN, AuthSource.TENCENT_CLOUD.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getOschinaAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(OSCHINA_AUTHORIZE_PATTERN, AuthSource.OSCHINA.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getQqAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(QQ_AUTHORIZE_PATTERN, AuthSource.QQ.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取qq授权地址
*
* @param url 获取qqopenid的api接口地址
* @param token qq 应用授权的token
* @param unionid 是否需要获取unionid,默认为false。注:获取unionid需要单独发送邮件申请权限,请个人视情况而定。参考链接:http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D
* @return full url
*/
public static String getQqOpenidUrl(String url, String token, boolean unionid) {
return MessageFormat.format(QQ_OPENID_PATTERN, url, token, unionid ? 1 : 0);
}
/**
* 获取alipay授权地址
*
* @param clientId alipay 应用的Client ID
* @param redirectUrl alipay 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getAlipayAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(ALIPAY_AUTHORIZE_PATTERN, AuthSource.ALIPAY.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取微信 授权地址
*
* @param clientId 微信 应用的appid
* @param redirectUrl 微信 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getWeChatAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(WECHAT_AUTHORIZE_PATTERN, AuthSource.WECHAT.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取微信 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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getTaobaoAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(TAOBAO_AUTHORIZE_PATTERN, AuthSource.TAOBAO.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取Google授权地址
*
* @param clientId google 应用的Client ID
* @param redirectUrl google 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getGoogleAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(GOOGLE_AUTHORIZE_PATTERN, AuthSource.GOOGLE.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getFacebookAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(FACEBOOK_AUTHORIZE_PATTERN, AuthSource.FACEBOOK.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getDouyinAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(DOUYIN_AUTHORIZE_PATTERN, AuthSource.DOUYIN.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取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 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getLinkedinAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(LINKEDIN_AUTHORIZE_PATTERN, AuthSource.LINKEDIN.authorize(), clientId, redirectUrl, state);
}
/**
* 获取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 微软 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getMicrosoftAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(MICROSOFT_AUTHORIZE_PATTERN, AuthSource.MICROSOFT.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取微软 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 小米 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getMiAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(MI_AUTHORIZE_PATTERN, AuthSource.MI.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取小米 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 今日头条 应用授权成功后的回调地址
* @param state 随机字符串,用于保持会话状态,防止CSRF攻击
* @return full url
*/
public static String getToutiaoAuthorizeUrl(String clientId, String redirectUrl, String state) {
return MessageFormat.format(TOUTIAO_AUTHORIZE_PATTERN, AuthSource.TOUTIAO.authorize(), clientId, redirectUrl, getState(state));
}
/**
* 获取今日头条 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);
} }
} }
@@ -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);
}
}
+134 -116
View File
@@ -8,274 +8,292 @@ import org.junit.Test;
/** /**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.8
*/ */
public class AuthRequestTest { public class AuthRequestTest {
@Test @Test
public void giteeTest() { public void giteeTest() {
AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder() AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback()); authRequest.login(new AuthCallback());
} }
@Test @Test
public void githubTest() { public void githubTest() {
AuthRequest authRequest = new AuthGithubRequest(AuthConfig.builder() AuthRequest authRequest = new AuthGithubRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback()); authRequest.login(new AuthCallback());
} }
@Test @Test
public void weiboTest() { public void weiboTest() {
AuthRequest authRequest = new AuthWeiboRequest(AuthConfig.builder() AuthRequest authRequest = new AuthWeiboRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.build()); .build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback()); authRequest.login(new AuthCallback());
} }
@Test @Test
public void dingdingTest() { public void dingdingTest() {
AuthRequest authRequest = new AuthDingTalkRequest(AuthConfig.builder() AuthRequest authRequest = new AuthDingTalkRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback()); authRequest.login(new AuthCallback());
} }
@Test @Test
public void baiduTest() { public void baiduTest() {
AuthRequest authRequest = new AuthBaiduRequest(AuthConfig.builder() AuthRequest authRequest = new AuthBaiduRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback()); authRequest.login(new AuthCallback());
} }
@Test @Test
public void codingTest() { public void codingTest() {
AuthRequest authRequest = new AuthCodingRequest(AuthConfig.builder() AuthRequest authRequest = new AuthCodingRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback()); authRequest.login(new AuthCallback());
} }
@Test @Test
public void tencentCloudTest() { public void tencentCloudTest() {
AuthRequest authRequest = new AuthTencentCloudRequest(AuthConfig.builder() AuthRequest authRequest = new AuthTencentCloudRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback()); authRequest.login(new AuthCallback());
} }
@Test @Test
public void oschinaTest() { public void oschinaTest() {
AuthRequest authRequest = new AuthOschinaRequest(AuthConfig.builder() AuthRequest authRequest = new AuthOschinaRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(new AuthCallback()); authRequest.login(new AuthCallback());
} }
@Test @Test
public void alipayTest() { public void alipayTest() {
AuthRequest authRequest = new AuthAlipayRequest(AuthConfig.builder() AuthRequest authRequest = new AuthAlipayRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.alipayPublicKey("publicKey") .alipayPublicKey("publicKey")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void qqTest() { public void qqTest() {
AuthRequest authRequest = new AuthQqRequest(AuthConfig.builder() AuthRequest authRequest = new AuthQqRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void wechatTest() { public void wechatTest() {
AuthRequest authRequest = new AuthWeChatRequest(AuthConfig.builder() AuthRequest authRequest = new AuthWeChatRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void taobaoTest() { public void taobaoTest() {
AuthRequest authRequest = new AuthTaobaoRequest(AuthConfig.builder() AuthRequest authRequest = new AuthTaobaoRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void googleTest() { public void googleTest() {
AuthRequest authRequest = new AuthGoogleRequest(AuthConfig.builder() AuthRequest authRequest = new AuthGoogleRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void facebookTest() { public void facebookTest() {
AuthRequest authRequest = new AuthFacebookRequest(AuthConfig.builder() AuthRequest authRequest = new AuthFacebookRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void douyinTest() { public void douyinTest() {
AuthRequest authRequest = new AuthDouyinRequest(AuthConfig.builder() AuthRequest authRequest = new AuthDouyinRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void linkedinTest() { public void linkedinTest() {
AuthRequest authRequest = new AuthLinkedinRequest(AuthConfig.builder() AuthRequest authRequest = new AuthLinkedinRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void microsoftTest() { public void microsoftTest() {
AuthRequest authRequest = new AuthMicrosoftRequest(AuthConfig.builder() AuthRequest authRequest = new AuthMicrosoftRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void miTest() { public void miTest() {
AuthRequest authRequest = new AuthMiRequest(AuthConfig.builder() AuthRequest authRequest = new AuthMiRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); AuthResponse login = authRequest.login(new AuthCallback());
} }
@Test @Test
public void toutiaoTest() { public void toutiaoTest() {
AuthRequest authRequest = new AuthToutiaoRequest(AuthConfig.builder() AuthRequest authRequest = new AuthToutiaoRequest(AuthConfig.builder()
.clientId("clientId") .clientId("clientId")
.clientSecret("clientSecret") .clientSecret("clientSecret")
.redirectUri("redirectUri") .redirectUri("redirectUri")
.state("state") .build());
.build());
// 返回授权页面,可自行跳转 // 返回授权页面,可自行跳转
String url = authRequest.authorize(); authRequest.authorize("state");
// 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参 // 授权登录后会返回code(auth_code(仅限支付宝))、state1.8.0版本后,可以用AuthCallback类作为回调接口的入参
// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
AuthResponse login = authRequest.login(new AuthCallback()); 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 {
AuthStateCache.cache("key", "value");
Assert.assertEquals(AuthStateCache.get("key"), "value");
TimeUnit.MILLISECONDS.sleep(4);
Assert.assertEquals(AuthStateCache.get("key"), "value");
}
@Test
public void cache2() throws InterruptedException {
AuthStateCache.cache("key", "value", 10);
Assert.assertEquals(AuthStateCache.get("key"), "value");
// 没过期
TimeUnit.MILLISECONDS.sleep(5);
Assert.assertEquals(AuthStateCache.get("key"), "value");
// 过期
TimeUnit.MILLISECONDS.sleep(6);
Assert.assertNull(AuthStateCache.get("key"));
}
}
@@ -1,231 +0,0 @@
package me.zhyd.oauth.utils;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import me.zhyd.oauth.config.AuthConfig;
import org.junit.Assert;
import org.junit.Test;
import java.util.*;
public class AuthStateTest {
/**
* step1 生成state: 预期创建一个新的state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
*
* step2 重复生成state: 预期从bucket中返回一个可用的state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
*
* step3 获取state: 预期获取上面生成的state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
*
* step4 删除state: 预期删除掉上面创建的state...
*
* step5 重新获取state: 预期返回null...
* null
*/
@Test
public void test() {
String source = "github";
System.out.println("\nstep1 生成state: 预期创建一个新的state...");
String state = AuthState.create(source);
System.out.println(state);
System.out.println("\nstep2 重复生成state: 预期从bucket中返回一个可用的state...");
String recreateState = AuthState.create(source);
System.out.println(recreateState);
Assert.assertEquals(state, recreateState);
System.out.println("\nstep3 获取state: 预期获取上面生成的state...");
String stateByBucket = AuthState.get(source);
System.out.println(stateByBucket);
Assert.assertEquals(state, stateByBucket);
System.out.println("\nstep4 删除state: 预期删除掉上面创建的state...");
AuthState.delete(source);
System.out.println("\nstep5 重新获取state: 预期返回null...");
String deletedState = AuthState.get(source);
System.out.println(deletedState);
Assert.assertNull(deletedState);
}
/**
* 通过随机字符串生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9wdnAy
*
* 通过传入自定义的字符串生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV/ov5nmmK/kuIDkuKrlrZfnrKbkuLI=
*
* 通过传入数字生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV8xMTE=
*
* 通过传入日期生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV8xNTQ2MzE1OTMyMDAw
*
* 通过传入map生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV97InVzZXJUb2tlbiI6Inh4eHh4IiwidXNlcklkIjoxfQ==
*
* 通过传入List生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9bInh4eHgiLCJ4eHh4eHh4eCJd
*
* 通过传入实体类生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV97ImNsaWVudElkIjoieHh4eHgiLCJjbGllbnRTZWNyZXQiOiJ4eHh4eCIsInVuaW9uSWQiOmZhbHNlfQ==
*/
@Test
public void create() {
String source = "github";
System.out.println("\n通过随机字符串生成state...");
String state = AuthState.create(source);
System.out.println(state);
AuthState.delete(source);
System.out.println("\n通过传入自定义的字符串生成state...");
String stringBody = "这是一个字符串";
String stringState = AuthState.create(source, stringBody);
System.out.println(stringState);
AuthState.delete(source);
System.out.println("\n通过传入数字生成state...");
Integer numberBody = 111;
String numberState = AuthState.create(source, numberBody);
System.out.println(numberState);
AuthState.delete(source);
System.out.println("\n通过传入日期生成state...");
Date dateBody = DateUtil.parse("2019-01-01 12:12:12", DatePattern.NORM_DATETIME_PATTERN);
String dateState = AuthState.create(source, dateBody);
System.out.println(dateState);
AuthState.delete(source);
System.out.println("\n通过传入map生成state...");
Map<String, Object> mapBody = new HashMap<>();
mapBody.put("userId", 1);
mapBody.put("userToken", "xxxxx");
String mapState = AuthState.create(source, mapBody);
System.out.println(mapState);
AuthState.delete(source);
System.out.println("\n通过传入List生成state...");
List<String> listBody = new ArrayList<>();
listBody.add("xxxx");
listBody.add("xxxxxxxx");
String listState = AuthState.create(source, listBody);
System.out.println(listState);
AuthState.delete(source);
System.out.println("\n通过传入实体类生成state...");
AuthConfig entityBody = AuthConfig.builder()
.clientId("xxxxx")
.clientSecret("xxxxx")
.build();
String entityState = AuthState.create(source, entityBody);
System.out.println(entityState);
AuthState.delete(source);
}
/**
* 通过随机字符串生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9kaWNn
* dicg
*
* 通过传入自定义的字符串生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV/ov5nmmK/kuIDkuKrlrZfnrKbkuLI=
* 这是一个字符串
*
* 通过传入数字生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV8xMTE=
* 111
*
* 通过传入日期生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV8xNTQ2MzE1OTMyMDAw
* Tue Jan 01 12:12:12 CST 2019
*
* 通过传入map生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV97InVzZXJUb2tlbiI6Inh4eHh4IiwidXNlcklkIjoxfQ==
* {userToken=xxxxx, userId=1}
*
* 通过传入List生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV9bInh4eHgiLCJ4eHh4eHh4eCJd
* [xxxx, xxxxxxxx]
*
* 通过传入实体类生成state...
* Z2l0aHViXzE5Mi4xNjguMTkuMV97ImNsaWVudElkIjoieHh4eHgiLCJjbGllbnRTZWNyZXQiOiJ4eHh4eCIsInVuaW9uSWQiOmZhbHNlfQ==
* me.zhyd.oauth.config.AuthConfig@725bef66
*/
@Test
public void getBody() {
String source = "github";
System.out.println("\n通过随机字符串生成state...");
String state = AuthState.create(source);
System.out.println(state);
String body = AuthState.getBody(source, state, String.class);
System.out.println(body);
AuthState.delete(source);
System.out.println("\n通过传入自定义的字符串生成state...");
String stringBody = "这是一个字符串";
String stringState = AuthState.create(source, stringBody);
System.out.println(stringState);
stringBody = AuthState.getBody(source, stringState, String.class);
System.out.println(stringBody);
AuthState.delete(source);
System.out.println("\n通过传入数字生成state...");
Integer numberBody = 111;
String numberState = AuthState.create(source, numberBody);
System.out.println(numberState);
numberBody = AuthState.getBody(source, numberState, Integer.class);
System.out.println(numberBody);
AuthState.delete(source);
System.out.println("\n通过传入日期生成state...");
Date dateBody = DateUtil.parse("2019-01-01 12:12:12", DatePattern.NORM_DATETIME_PATTERN);
String dateState = AuthState.create(source, dateBody);
System.out.println(dateState);
dateBody = AuthState.getBody(source, dateState, Date.class);
System.out.println(dateBody);
AuthState.delete(source);
System.out.println("\n通过传入map生成state...");
Map<String, Object> mapBody = new HashMap<>();
mapBody.put("userId", 1);
mapBody.put("userToken", "xxxxx");
String mapState = AuthState.create(source, mapBody);
System.out.println(mapState);
mapBody = AuthState.getBody(source, mapState, Map.class);
System.out.println(mapBody);
AuthState.delete(source);
System.out.println("\n通过传入List生成state...");
List<String> listBody = new ArrayList<>();
listBody.add("xxxx");
listBody.add("xxxxxxxx");
String listState = AuthState.create(source, listBody);
System.out.println(listState);
listBody = AuthState.getBody(source, listState, List.class);
System.out.println(listBody);
AuthState.delete(source);
System.out.println("\n通过传入实体类生成state...");
AuthConfig entityBody = AuthConfig.builder()
.clientId("xxxxx")
.clientSecret("xxxxx")
.build();
String entityState = AuthState.create(source, entityBody);
System.out.println(entityState);
entityBody = AuthState.getBody(source, entityState, AuthConfig.class);
System.out.println(entityBody);
AuthState.delete(source);
}
@Test
public void getErrorStateBody() {
String source = "github";
String state = "1111111111111111111111111111111";
String body = AuthState.getBody(source, state, String.class);
System.out.println(body);
AuthState.delete(source);
}
}
@@ -0,0 +1,87 @@
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;
/**
* 其他测试方法
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
*/
public class CustomTest {
/**
* 1000000: 23135ms
* 100000: 3016ms
* 10000: 328ms
* 1000: 26ms
*/
@Test
public void test() {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
callMethod();
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
/**
* 1000000: 19058ms
* 100000: 2772ms
* 10000: 323ms
* 1000: 29ms
*/
@Test
public void test2() {
long end = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
callMethod2();
}
long end2 = System.currentTimeMillis();
System.out.println((end2 - end) + "ms");
}
public String callMethod() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// for (StackTraceElement stackTraceElement : stackTrace) {
// System.out.println(stackTraceElement.getMethodName());
// }
return stackTrace[2].getMethodName();
}
public String callMethod2() {
StackTraceElement[] stackTrace = (new Throwable()).getStackTrace();
// for (StackTraceElement stackTraceElement : stackTrace) {
// System.out.println(stackTraceElement.getMethodName());
// }
return stackTrace[2].getMethodName();
}
@Test
public void jsonpath() {
List<Map<String, Map<String, Object>>> list = new ArrayList<>();
Map<String, Map<String, Object>> map = new HashMap<>();
Map<String, Object> node = new HashMap<>();
node.put("emailAddress", "xxxx");
map.put("handle~", node);
list.add(map);
Map<String, Object> master = new HashMap<>();
// master.put("elements", list);
JSONObject emailObj = JSONObject.parseObject(JSON.toJSONString(master));
Object object = JSONPath.eval(emailObj, "$['elements'][0]['handle~']['emailAddress']");
System.out.println(object);
}
}
@@ -0,0 +1,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);
}
}
+83 -16
View File
@@ -1,26 +1,87 @@
### 2019/07/15 ### 2019/07/30 [v1.9.3](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.3)
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()`方法
### 2019/07/22 [v1.9.2](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.2)
1. 合并github上[xkcoding](https://github.com/xkcoding) 的[pr#26](https://github.com/zhangyd-c/JustAuth/pull/26)AuthConfig类添加lombok注解,方便 [justauth-spring-boot-starter](https://github.com/xkcoding/justauth-spring-boot-starter) 直接使用
### 2019/07/22 [v1.9.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.1)
1. 增加`stackoverflow`参数校验
2. 解决`Pinterest`获取用户失败的问题
3. 添加注释
### 2019/07/19 [v1.9.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.0)
1. 合并github上[@dyc12ii](https://github.com/dyc12ii) 的[pr#25](https://github.com/zhangyd-c/JustAuth/pull/25),升级fastjson版本至1.2.58,避免安全漏洞
2. `AuthUserGender`枚举类挪到`enums`包下
3. 删除`AuthBaiduErrorCode``AuthDingTalkErrorCode`枚举类
4. 优化百度授权流程,增加refresh token的方法
5. 优化`AuthConfig``AuthResponse`类,去掉不必要的lombonk注解,减少编译后的代码量
6. 使用lombok注解优化枚举类
7. `AuthQqRequest`增加refresh方法
8. 修复google登录无法获取用户信息的问题
9. 优化代码
### 2019/07/18
1. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#19](https://github.com/zhangyd-c/JustAuth/pull/19),集成人人
2. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#20](https://github.com/zhangyd-c/JustAuth/pull/20),集成Pinterest
3. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#21](https://github.com/zhangyd-c/JustAuth/pull/21),集成StackOverflow
4. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#23](https://github.com/zhangyd-c/JustAuth/pull/23),重构代码、新增编辑器规范,规范PR代码风格
### 2019/07/17
1. 优化代码
2. 集成Teambition登录
### 2019/07/16
1. 重构UrlBuilder类
2. 将CSDN相关的类置为`Deprecated`,后续可能会删除,也可能一直保留。毕竟CSDN的openAPI已经不对外开放了。
3. `BaseAuthRequest` 改名为 `AuthDefaultRequest`
4. `ResponseStatus` 改名为 `AuthResponseStatus` 并且移动到 `me.zhyd.oauth.model`
5. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#18](https://github.com/zhangyd-c/JustAuth/pull/18),修复小米回调错误问题 同时 支持微信获取unionId
### 2019/07/15 [v1.8.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.8.1)
1. 新增 `AuthState` 类,内置默认的state生成规则和校验规则 1. 新增 `AuthState` 类,内置默认的state生成规则和校验规则
### 2019/07/12 ### 2019/07/12
1. 合并[Braavos96](https://github.com/Braavos96)提交的[PR#16](https://github.com/zhangyd-c/JustAuth/pull/16) 1. 合并[Braavos96](https://github.com/Braavos96)提交的[PR#16](https://github.com/zhangyd-c/JustAuth/pull/16)
### 2019/06/28 ### 2019/06/28 [v1.8.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.8.0)
1. 修复百度登录获取不到token失效时间的问题 1. 修复百度登录获取不到token失效时间的问题
2. 增加state参数校验,预防CSRF。**强烈建议启用state** 2. 增加state参数校验,预防CSRF。**强烈建议启用state**
### 2019/06/27 ### 2019/06/27
1.改login方法的参数为AuthCallback,封装回调返回的参数 1.复百度登录获取不到token失效时间的问题
2. 支持state参数 2. 增加state参数校验,预防CSRF。**强烈建议启用state**
3. 增加code和state参数校验 3. 修改login方法的参数为AuthCallback,封装回调返回的参数
4. 支持state参数
5. 增加code和state参数校验
### 2019/06/25 由于state安全问题,1.8.0以前的版本都有隐藏的CSRF漏洞问题,所以强烈建议正在使用JustAuth的朋友升级到1.8.0版本!
### 2019/06/25 [v1.7.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.7.1)
qq授权登录时,需要获取`openId`作为`uuid`,在`1.6.1-beta``1.7.0`版本中,引入了`unionId`这一属性。获取`unionid`需要单独向qq团队**发送邮件**申请权限,鉴于这一申请权限的步骤比较麻烦(需要填写的内容比较多),所以在`AuthConfig`中增加了一个`unionId`属性,当为**true**时才会获取unionid,当为false时只获取openId。如果你需要该功能, 则在自行申请了相关权限后,将该属性置为true即可。关于unionId的参考链接:[UnionID介绍](http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D) qq授权登录时,需要获取`openId`作为`uuid`,在`1.6.1-beta``1.7.0`版本中,引入了`unionId`这一属性。获取`unionid`需要单独向qq团队**发送邮件**申请权限,鉴于这一申请权限的步骤比较麻烦(需要填写的内容比较多),所以在`AuthConfig`中增加了一个`unionId`属性,当为**true**时才会获取unionid,当为false时只获取openId。如果你需要该功能, 则在自行申请了相关权限后,将该属性置为true即可。关于unionId的参考链接:[UnionID介绍](http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D)
### 2019/06/19 ### 2019/06/19 [v1.7.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.7.0)
1. 合并[xkcoding](https://github.com/xkcoding)提交的[PR](https://github.com/zhangyd-c/JustAuth/pull/14),重构了部分代码,jar包由原来的`130+kb`优化到现在的`110+kb` 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) 解决抖音登录失败问题 2. 合并[skqing](https://gitee.com/skqing)提交的[PR](https://gitee.com/yadong.zhang/JustAuth/pulls/3) 解决抖音登录失败问题
### 2019/06/18 ### 2019/06/18 [v1.6.1-beta](https://gitee.com/yadong.zhang/JustAuth/releases/v1.6.1-beta)
1. 解决Issue [#IY2HW](https://gitee.com/yadong.zhang/JustAuth/issues/IY2HW) 1. 解决Issue [#IY2HW](https://gitee.com/yadong.zhang/JustAuth/issues/IY2HW)
2. 解决Issue [#IY2OH](https://gitee.com/yadong.zhang/JustAuth/issues/IY2OH) 2. 解决Issue [#IY2OH](https://gitee.com/yadong.zhang/JustAuth/issues/IY2OH)
3. 解决Issue [#IY2FV](https://gitee.com/yadong.zhang/JustAuth/issues/IY2FV) 3. 解决Issue [#IY2FV](https://gitee.com/yadong.zhang/JustAuth/issues/IY2FV)
@@ -28,15 +89,15 @@ qq授权登录时,需要获取`openId`作为`uuid`,在`1.6.1-beta`和`1.7.0`
5. 解决Issue [#IY1QR](https://gitee.com/yadong.zhang/JustAuth/issues/IY1QR) 增加对Config属性的校验功能,主要校验redirect uri的合法性 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 6. 合并[skqing](https://gitee.com/skqing)提交的[PR](https://gitee.com/yadong.zhang/JustAuth/pulls/2),解决一些BUG
### 2019/06/06 ### 2019/06/06 [v1.6.0-beta](https://gitee.com/yadong.zhang/JustAuth/releases/v1.6.0-beta)
1. 增加今日头条的授权登陆 1. 增加今日头条的授权登陆
2. 发布1.6.0-beta版本,今日头条开发者暂时不能认证, 所以无法做测试,等测试通过后,正式发布release版本 2. 发布1.6.0-beta版本,今日头条开发者暂时不能认证, 所以无法做测试,等测试通过后,正式发布release版本
### 2019/05/28 ### 2019/05/28 [v1.5.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.5.0)
1. 增加小米账号和微软的授权登陆 1. 增加小米账号和微软的授权登陆
2. 发布1.5.0版本 2. 发布1.5.0版本
### 2019/05/26 ### 2019/05/26 [v1.4.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.4.0)
1. 增加抖音和Linkedin的授权登陆 1. 增加抖音和Linkedin的授权登陆
2. 修改部分图片命名 2. 修改部分图片命名
3. 优化部分代码 3. 优化部分代码
@@ -44,22 +105,22 @@ qq授权登录时,需要获取`openId`作为`uuid`,在`1.6.1-beta`和`1.7.0`
5. 修复支付宝登陆时用户名为空的问题 5. 修复支付宝登陆时用户名为空的问题
### 2019/05/24 ### 2019/05/24 [v1.3.3](https://gitee.com/yadong.zhang/JustAuth/releases/v1.3.3)
1. 修复一些问题 1. 修复一些问题
2. 升级api,在AuthUser中增加`uuid`属性,可以通过`uuid` + `source`唯一确定一个用户,此举解决了用户身份归属的问题。 2. 升级api,在AuthUser中增加`uuid`属性,可以通过`uuid` + `source`唯一确定一个用户,此举解决了用户身份归属的问题。
3. 发布1.3.3版本的jar包到公开仓库(1.3.2忘记发布了,( ╯□╰ )) 3. 发布1.3.3版本的jar包到公开仓库(1.3.2忘记发布了,( ╯□╰ ))
4. 重要:经咨询官方客服得知,CSDN的授权开放平台已经下线,如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了 4. 重要:经咨询官方客服得知,CSDN的授权开放平台已经下线,如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了
### 2019/05/23 ### 2019/05/23 [v1.3.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.3.1)
1. 修复QQ登录的问题 1. 修复QQ登录的问题
2. 发布1.3.1版本的jar包到公开仓库 2. 发布1.3.1版本的jar包到公开仓库
### 2019/05/21 ### 2019/05/21 [v1.3.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.3.0)
1. 新增google授权登录 1. 新增google授权登录
2. 新增facebook授权登录 2. 新增facebook授权登录
3. 发布1.3.0版本的jar包到公开仓库 3. 发布1.3.0版本的jar包到公开仓库
### 2019/05/18 ### 2019/05/18 [v1.1.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.1.0) | [v1.2.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.2.0)
1. 发布1.1.0版本的jar包到公开仓库(支持qq和微信登录) 1. 发布1.1.0版本的jar包到公开仓库(支持qq和微信登录)
2. 支持淘宝登录 2. 支持淘宝登录
3. 修改`AuthUser.java`类中的`accessToken`属性,由原本的~~accessToken (String)~~改为`token (AuthToken)` 3. 修改`AuthUser.java`类中的`accessToken`属性,由原本的~~accessToken (String)~~改为`token (AuthToken)`
@@ -68,4 +129,10 @@ qq授权登录时,需要获取`openId`作为`uuid`,在`1.6.1-beta`和`1.7.0`
### 2019/05/17 ### 2019/05/17
1. 增加qq和微信的授权登录 1. 增加qq和微信的授权登录
2. 修改getAccessToken方法的返回值 2. 修改getAccessToken方法的返回值
### 2019/03/27 [v1.0.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.0.1)
集成 支付宝授权登录
### 2019/03/25 [v1.0.0](https://gitee.com/yadong.zhang/JustAuth/releases/v1.0.0)
史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉和百度、Coding、腾讯云开发者平台和OSChina登录。 Login, so easy!