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

Compare commits

...

101 Commits

Author SHA1 Message Date
yadong.zhang 694bbf1b01 📝 编写文档 2024-12-14 17:32:48 +08:00
yadong.zhang bbf63c6056 Merge branch 'dev' 2024-12-14 17:14:04 +08:00
yadong.zhang 44d77f3d6a Merge branch 'dev' of github.com:justauth/JustAuth into dev 2024-12-14 17:13:06 +08:00
yadong.zhang 2b6e419943 📝 编写文档 2024-12-14 17:12:00 +08:00
yadong.zhang 56bfe2803a Merge pull request #223 from hurentian/dev
feat: 支持QQ小程序
2024-12-14 17:09:49 +08:00
yadong.zhang 89d107f6da Merge pull request #222 from Yeaury/fix-doc-link
fix: Fix documentation link in README
2024-12-14 17:07:21 +08:00
yadong.zhang cdcf9a5e8d 🔥 支持支付宝证书模式登录 2024-12-14 17:02:33 +08:00
yadong.zhang 354f00660b 💩 AppleId 登录的 POM 依赖改为 provided 2024-12-14 14:31:50 +08:00
hurentian 4b07aecfcf feat: 支持QQ小程序 2024-10-17 09:52:33 +08:00
31445 9862f487b4 fix: correct documentation link in README 2024-10-13 11:57:27 +08:00
yadong.zhang 9153e97398 merge pr 2024-09-02 00:45:40 +08:00
yadong.zhang 64e1098519 💩 添加新版钉钉扫码登录能力 2024-09-02 00:24:05 +08:00
yadong.zhang e9135436f9 💩 修复企业微信扫码登录 code 的传值 2024-09-01 18:19:05 +08:00
yadong.zhang 20731ea77e 💩 添加新版华为登录能力,原AuthHuaweiRequest会在后面版本被弃用,如有使用,请切换到AuthHuaweiV3Request 2024-09-01 18:15:18 +08:00
yadong.zhang 587d2f4072 !42 修改爱发电平台的域名为https://afdian.com
Merge pull request !42 from handy/handy-0827
2024-08-27 03:44:47 +00:00
handy 602a48b3ce 修改爱发电平台的域名为https://afdian.com 2024-08-27 10:51:00 +08:00
yadong.zhang 91167bcbf7 🔥 支持微信小程序 2024-08-17 20:16:14 +08:00
yadong.zhang 8f376129d4 🔥 支持微信小程序 2024-08-17 20:11:51 +08:00
yadong.zhang 6859b8a949 🔥 Amazon PKCE 中的 code_verifier 基于 state 缓存 2024-08-04 23:28:55 +08:00
yadong.zhang b01704ff78 💩 解决单测失败的问题 2024-08-04 15:51:38 +08:00
yadong.zhang 0731ebd130 Merge remote-tracking branch 'origin/dev' into dev 2024-08-04 15:38:56 +08:00
yadong.zhang 5eb8160a42 💩 添加新版企业微信扫码登录能力(PS. 新版企业微信登录,除了支持使用移动端企业微信扫码登录之外,还支持通过企业微信桌面端进行快速登录。) 2024-08-04 15:38:40 +08:00
yadong.zhang 415c703539 💩 修复 twitter 平台在 Java11 环境下登录失败的问题。[#174](https://github.com/justauth/JustAuth/issues/174) 2024-08-04 15:33:49 +08:00
yadong.zhang 5d521e3ea4 💩 添加新版企业微信扫码登录能力(PS. 新版企业微信登录,除了支持使用移动端企业微信扫码登录之外,还支持通过企业微信桌面端进行快速登录。) 2024-08-04 15:33:37 +08:00
yadong.zhang 0eafb96a43 💩 improve: 优化 AuthCallback 获取 code 的方式 2024-08-04 14:45:30 +08:00
yadong.zhang 6d8b3b5f1d 💩 getAccessTokengetUserInfo两个方法从AuthDefaultRequest提升至AuthRequest中,方便直接使用。[Github Issue#194](https://github.com/justauth/JustAuth/issues/194) 2024-08-03 18:27:36 +08:00
yadong.zhang 15b1a974f3 💩 补全抖音平台缺失字段补全。Github#188 2024-08-03 17:57:41 +08:00
yadong.zhang 74b5b4b3bc Merge branch 'dev' of github.com:justauth/JustAuth into dev
g the commit.
2024-08-03 16:35:53 +08:00
yadong.zhang 6cc414997d Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java
2024-08-03 16:34:13 +08:00
yadong.zhang 7c6f236733 Merge branch 'master' of github.com:justauth/JustAuth 2024-08-03 16:31:02 +08:00
yadong.zhang bf5a6d1b17 Merge branch 'master' of github.com:justauth/JustAuth into dev 2024-08-03 16:29:12 +08:00
yadong.zhang 9db9fbdcc8 Merge pull request #211 from justauth/dependabot/maven/org.bouncycastle-bcpkix-jdk18on-1.78
⬆️ Bump org.bouncycastle:bcpkix-jdk18on from 1.77 to 1.78
2024-08-03 16:28:39 +08:00
yadong.zhang 0acd67e704 ⬆️ 切分 v1.16.7 版本 2024-08-03 12:15:03 +08:00
dependabot[bot] 992f4a67ac ⬆️ Bump org.bouncycastle:bcpkix-jdk18on from 1.77 to 1.78
Bumps [org.bouncycastle:bcpkix-jdk18on](https://github.com/bcgit/bc-java) from 1.77 to 1.78.
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcpkix-jdk18on
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-03 04:08:01 +00:00
yadong.zhang 2ee2483aee Merge pull request #192 from G-XD/feat_apple
feat: sign with apple
2024-08-03 12:07:24 +08:00
yadong.zhang a1c1ab93bd !41 集成Figma平台
Merge pull request !41 from xiangqian/fork
2024-07-22 15:27:26 +00:00
xiangqian eaafd49890 feat: 集成figma平台 2024-07-22 21:40:07 +08:00
yadong.zhang 74be7d4c8b Merge pull request #198 from GitHubzhaolei/master
change:变更Google 端点地址
2024-06-23 23:06:03 +08:00
“zhaolei” d62bb6eecf change:[google oauth] api addr 2024-03-14 16:53:02 +08:00
yadong.zhang b2b007aa4d !39 补充单侧
Merge pull request !39 from handy/dev
2024-03-02 04:20:16 +00:00
handy 431adb447f 补充单侧 2024-02-02 10:12:21 +08:00
yadong.zhang 78a318e799 !38 AuthRequest 响应携带泛型,避免二次解析
Merge pull request !38 from handy/handy
2024-02-01 15:42:17 +00:00
G-XD 988d3cd293 fix: fix user info 2024-01-19 11:49:35 +08:00
G-XD 72e4a1d846 feat: support sign with apple 2024-01-19 01:42:31 +08:00
handy 65bb994012 AuthRequest 响应携带泛型,避免二次解析 2024-01-04 12:51:50 +08:00
yadong.zhang 6b758e0e1e 💡 修改注释 2023-12-03 21:50:50 +08:00
yadong.zhang 02bd057963 🔀 合并 PR#166 2023-12-03 21:05:53 +08:00
yadong.zhang 4d2a110fea Merge pull request #166 from christxlx/master
fix: bug 58-63 scope在微软最新的文档中要求必须用空格隔开,form构建时候空格会被强制转换为%20,所以此处重新覆盖
2023-12-03 21:04:10 +08:00
yadong.zhang 2ef8298278 Merge branch 'master' into dev 2023-12-03 21:01:16 +08:00
yadong.zhang 80ce57bce4 📝 编写文档 2023-12-03 21:00:45 +08:00
yadong.zhang 2fed3e5ba2 💩 修改 facebook 平台的 scope 2023-12-03 20:58:49 +08:00
yadong.zhang 1d38ce2835 💩 微软平台适配 AzureAD(目前改名为 Microsoft Entra ID)登录认证 2023-12-03 20:18:39 +08:00
yadong.zhang 14dd2b6179 💩 修复微信公众平台 scope 为 snsapi_base 登录报错的问题 [181](https://github.com/justauth/JustAuth/issues/181) 2023-12-03 20:17:47 +08:00
yadong.zhang d6ba3bba10 💩 修复 twitter 平台在 Java11 环境下登录失败的问题。[#174](https://github.com/justauth/JustAuth/issues/174) 2023-12-03 16:55:55 +08:00
yadong.zhang fe328c7375 💩 调整单元测试 2023-08-06 18:17:53 +08:00
yadong.zhang 102521a979 Merge branch 'dev' 2023-08-06 18:15:11 +08:00
yadong.zhang 924f0f552b 🔥 更新脚本 2023-08-06 18:14:42 +08:00
yadong.zhang 7167a74af2 💩 调整单元测试 2023-08-06 18:14:04 +08:00
yadong.zhang c0ac81df08 Merge remote-tracking branch 'origin/master' 2023-08-06 18:10:09 +08:00
yadong.zhang 56cdd20600 Merge branch 'master' of gitee.com:yadong.zhang/JustAuth into dev 2023-08-06 18:07:45 +08:00
yadong.zhang 87d8c1d164 Merge branch 'master' into dev 2023-08-06 17:58:24 +08:00
yadong.zhang d1f820514f ⬆️ 1.16.6 2023-08-06 17:57:47 +08:00
yadong.zhang 6d192e94ac Merge branch 'dev' of gitee.com:yadong.zhang/JustAuth into dev 2023-08-06 17:50:07 +08:00
yadong.zhang 02bec576de Merge branch 'dev'
# Conflicts:
#	README.md
2023-04-11 22:48:07 +08:00
yadong.zhang 0d599d75f0 替换 justauth.wiki 域名,该域名暂时不可用,请使用 justauth.cn 2023-04-11 22:46:53 +08:00
yadong.zhang 5171fb8148 替换 justauth.wiki 域名,该域名暂时不可用,请使用 justauth.cn 2023-04-11 22:45:58 +08:00
yadong.zhang 0025748ba7 替换 justauth.wiki 域名,该域名暂时不可用,请使用 justauth.cn 2023-04-11 22:42:47 +08:00
yadong.zhang 87cf45b675 替换 justauth.wiki 域名,该域名暂时不可用,请使用 justauth.cn 2023-04-11 22:41:18 +08:00
yadong.zhang bd1192f17f Merge branch 'dev'
# Conflicts:
#	README.md
2023-04-11 22:39:57 +08:00
yadong.zhang a7cb4aefc0 替换 justauth.wiki 域名,该域名暂时不可用,请使用 justauth.cn 2023-04-11 22:38:04 +08:00
yadong.zhang 7ffda8737c 替换 justauth.wiki 域名,该域名暂时不可用,请使用 justauth.cn 2023-04-11 22:34:18 +08:00
yadong.zhang 90a9d56fd0 替换 justauth.wiki 域名,该域名暂时不可用
Signed-off-by: yadong.zhang <yadong.zhang0415@gmail.com>
2023-04-11 14:25:54 +00:00
yadong.zhang c53c763540 !36 新增afdian授权示例图
Merge pull request !36 from ヽ米 饭/handy
2023-03-25 09:41:31 +00:00
韩帅 dc18304eee feat: 新增afdian授权示例图 2023-03-24 16:50:37 +08:00
yadong.zhang a558379680 !35 新增爱发电平台
Merge pull request !35 from ヽ米 饭/handy
2023-03-21 10:55:56 +00:00
韩帅 142846b2fa 新增爱发电平台 2023-03-21 17:42:46 +08:00
Christ 80b59fc13c fix: bug 58-63 scope在微软最新的文档中要求必须用空格隔开,form构建时候空格会被强制转换为%20,所以此处重新覆盖 2023-03-21 14:41:30 +08:00
yadong.zhang 765127e847 📝 更新文档 2023-02-26 18:09:08 +08:00
yadong.zhang 11707e6903 🔥 微信公众平台支持返回快照数据 2023-02-26 17:54:58 +08:00
yadong.zhang 3564c846f4 Merge branch 'master' of gitee.com:yadong.zhang/JustAuth into dev 2023-02-26 16:47:46 +08:00
yadong.zhang 0bc4c806bf ⬆️ Merge branch 'dev' 2023-02-26 16:47:27 +08:00
yadong.zhang 35c754f327 Merge branch 'master' of gitee.com:yadong.zhang/JustAuth 2023-02-26 16:32:19 +08:00
yadong.zhang 45ebe26f1b ⬆️ Merge branch 'dev' 2023-02-26 16:32:16 +08:00
yadong.zhang 30f4a29f48 Merge branch 'dev' of gitee.com:yadong.zhang/JustAuth 2023-02-26 16:27:49 +08:00
yadong.zhang ca8e194c4e Merge pull request #159 from cheng521521/master
添加飞书单元测试
2023-02-26 16:19:58 +08:00
yadong.zhang 81ea4b1620 Merge pull request #155 from marquis-chen/dev
企业微信网页登录--获取用户敏感信息
2023-02-26 16:19:28 +08:00
yadong.zhang cc74d4a5c8 !33 【轻量级 PR】:update pom.xml.
Merge pull request !33 from test/N/A
2023-02-24 12:15:56 +00:00
test e80304601f update pom.xml.
升级fastjson版本到1.2.83,1.2.83版本之前存在代码执行漏洞风险 ,CVE-2022-25845	

Signed-off-by: test <531648642@qq.com>
2023-02-24 11:54:47 +00:00
yadong.zhang afddf269e1 !31 修复企业微信的 redirect_uri 没有 urlEncode 问题
Merge pull request !31 from AlexChan/dev
2022-10-23 12:06:06 +00:00
alexchan bac494a7fc 修复企业微信的 redirect_uri 没有 urlEncode 问题 2022-10-21 16:22:13 +08:00
chengpengxiang 7a9d602499 test:添加飞书单元测试 2022-10-03 13:17:28 +08:00
marquis chen f3a8cf675c 企业微信网页登录增加AgentId参数,对重定向地址UrlEncode,获取用户敏感信息 2022-09-05 16:42:51 +08:00
yadong.zhang 571466f079 Merge pull request #149 from justauth/dependabot/maven/com.alibaba-fastjson-1.2.83
⬆️ Bump fastjson from 1.2.78 to 1.2.83
2022-08-07 22:59:14 +08:00
dependabot[bot] e1b912bdf6 ⬆️ Bump fastjson from 1.2.78 to 1.2.83
Bumps [fastjson](https://github.com/alibaba/fastjson) from 1.2.78 to 1.2.83.
- [Release notes](https://github.com/alibaba/fastjson/releases)
- [Commits](https://github.com/alibaba/fastjson/compare/1.2.78...1.2.83)

---
updated-dependencies:
- dependency-name: com.alibaba:fastjson
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-17 02:05:50 +00:00
yadong.zhang 02674049d0 📝 更新说明 2021-10-18 10:03:14 +08:00
yadong.zhang f1714726f5 Merge branch 'carldea/master' into dev 2021-10-18 09:55:40 +08:00
yadong.zhang a3108caccf 🐛 修复 alipay 登录失败的 BUG 2021-10-16 19:11:24 +08:00
yadong.zhang e134dd06b0 ⬆️ 升级alipay-sdk-version的依赖版本 2021-10-16 19:10:50 +08:00
yadong.zhang 172ba7be55 💩 修改用户logo的尺寸 2021-10-16 19:10:24 +08:00
Carl Dea f9e3053ecc Using TCK Tested JDK builds of OpenJDK
The AdoptOpenJDK has been discontinued since July 2021. When using Zulu you get all the latest updated (TCK Tested) builds for all versions of OpenJDK included archived major fixed versions.
2021-09-27 14:37:10 -04:00
Carl Dea 99c9ba2854 Using TCK Tested JDK builds of OpenJDK
The AdoptOpenJDK has been discontinued since July 2021. When using Zulu you get all the latest updated (TCK Tested) builds for all versions of OpenJDK included archived major fixed versions.
2021-09-27 14:36:33 -04:00
91 changed files with 1999 additions and 271 deletions
+1 -1
View File
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://justauth.wiki/sponsor.html']
custom: ['https://www.justauth.cn/sponsor.html']
+2 -2
View File
@@ -9,8 +9,8 @@ assignees: ''
## Pre-submission checklist:
- [ ] I have searched the relevant information in the existing list of Issues.
- [ ] I have searched the developer documentation for that information: https://justauth.wiki
- [ ] I have read the relevant Q&A: https://justauth.wiki
- [ ] I have searched the developer documentation for that information: https://www.justauth.cn
- [ ] I have read the relevant Q&A: https://www.justauth.cn
## Issue description
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
distribution: 'zulu'
- name: Cache m2 package
uses: actions/cache@v2
with:
@@ -36,7 +36,7 @@ jobs:
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
distribution: 'zulu'
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
distribution: 'zulu'
- name: Cache m2 package
uses: actions/cache@v2
with:
+56 -6
View File
@@ -1,3 +1,53 @@
## 1.16.7
### 2024/12/14
- 新增
- 添加`微信小程序`登录能力,对接文档:[点击查看](https://justauth.cn/guide/oauth/wechat_mini_program/)。
- 添加`支付宝证书模式`登录能力(原支持的公钥登录模式依然可用),对接文档:[点击查看](https://justauth.cn/guide/oauth/alipay_cert)。
- 添加`appleid`社交登录能力,对接文档:[点击查看](https://justauth.cn/guide/oauth/appleid/)。 [Github#192](https://github.com/justauth/JustAuth/pull/192)
- 添加`QQ小程序`社交登录能力。 [Github#223](https://github.com/justauth/JustAuth/pull/223)
- 添加`figma`社交登录能力。 [Gitee#41](https://gitee.com/yadong.zhang/JustAuth/pulls/41)
- 添加新版`企业微信扫码`登录能力,对接文档:[点击查看](https://justauth.cn/guide/oauth/wechat_enterprise_qrcode_v2/)。 [Github Issue#165](https://github.com/justauth/JustAuth/issues/165)
- 添加新版`钉钉扫码`登录能力,对接文档:[点击查看](https://justauth.cn/guide/oauth/dingtalk_v2/)。 [Gitee Issue#I73FZL](https://gitee.com/yadong.zhang/JustAuth/issues/I73FZL)
- 添加新版`华为`登录能力,对接文档:[点击查看](https://justauth.cn/guide/oauth/huawei_v3/),原`AuthHuaweiRequest`会在后面版本被弃用,如有使用,请切换到`AuthHuaweiV3Request`
- 优化
- 修复文档错误。[Github #222](https://github.com/justauth/JustAuth/pull/222)
- 更新 Google 端点地址。[Github #198](https://github.com/justauth/JustAuth/pull/198)
- Amazon PKCE 中的 `code_verifier` 基于 `state` 缓存
- `AuthRequest`响应时携带泛型,避免二次解析。[Gitee#38](https://gitee.com/yadong.zhang/JustAuth/pulls/38)
- 优化业务调用方式:`getAccessToken``getUserInfo`两个方法从`AuthDefaultRequest`提升至`AuthRequest`中,部分场景下可以减少一次网络请求。[Github Issue#194](https://github.com/justauth/JustAuth/issues/194)
- ***注意:如果有基于 JustAuth 规范自定义实现的三方平台 Request([自定义第三方平台的OAuth](https://justauth.cn/features/customize-the-oauth/)),需要注意`getAccessToken``getUserInfo`接口的访问级别是否正确!!!***
- 其他
- 补充单侧,[Gitee#39](https://gitee.com/yadong.zhang/JustAuth/pulls/39)
## 1.16.6
### 2023/12/03
- 优化
- 微信公众平台支持返回快照标识(快照标识为 true 时,标识当前获取到的微信用户信息都是虚拟的)
- 企业微信网页登录--获取用户敏感信。 [Github #155](https://github.com/justauth/JustAuth/pull/155)
- 添加飞书单元测试。 [Github #159](https://github.com/justauth/JustAuth/pull/159)
- 升级fastjson版本到1.2.831.2.83版本之前存在代码执行漏洞风险 CVE-2022-25845。[Gitee PR #31](https://gitee.com/yadong.zhang/JustAuth/pulls/31)
- 新增
- 添加微软中国(世纪华联)第三方登录,新增微软方式登录的redirectUri校验。[Gitee PR #33](https://gitee.com/yadong.zhang/JustAuth/pulls/33)
- 新增爱发电平台 [Gitee #35](https://gitee.com/yadong.zhang/JustAuth/pulls/35)
- 微软平台适配 AzureAD(目前改名为 Microsoft Entra ID)登录认证
- Fixed
- 修复 twitter 平台在 Java11 环境下登录失败的问题。[#174](https://github.com/justauth/JustAuth/issues/174)
- 修复 Facebook 平台无法登录的问题(facebook 平台 API 进行了升级)
- 修复微信公众平台 scope 为 snsapi_base 登录报错的问题 [181](https://github.com/justauth/JustAuth/issues/181)
## 1.16.5
### 2021/10/18
- 合并 PR [Github #138](https://github.com/justauth/JustAuth/pull/138)
- 升级alipay-sdk-version的依赖版本
- 修改用户logo的尺寸
- 修复 alipay 登录失败的 BUG [Gitee Issue #I4E4ML](https://gitee.com/yadong.zhang/JustAuth/issues/I4E4ML)
## 1.16.4
### 2021/9/22
@@ -151,7 +201,7 @@
- 新增 [微信企业版登录](oauth/wechatEnterprise.md)文档
- 新增 [Facebook 登录](oauth/facebook.md)文档
- 完善 [JustAuth 使用者](users.md)文档
- 替换“帮助文档”域名,由[https://docs.justauth.whnb.wang](https://docs.justauth.whnb.wang)迁移到[https://justauth.wiki](https://justauth.wiki)
- 替换“帮助文档”域名,由[https://docs.justauth.whnb.wang](https://docs.justauth.whnb.wang)迁移到[https://www.justauth.cn](https://www.justauth.cn)
- 新增
- 增加阿里云授权登录中刷新授权token的接口,by “QQ群用户需求”
- AuthConfig 增加忽略校验 state 的参数,详情参考:[Github#Issue#83](https://github.com/justauth/JustAuth/issues/83)
@@ -204,7 +254,7 @@ new AuthGoogleRequest(AuthConfig.builder()
- 修复
- 解决 Twitter 授权失败的BUG
- 文档
- 完善 [https://justauth.wiki](https://justauth.wiki/) 的404引导页内容
- 完善 [https://www.justauth.cn](https://www.justauth.cn/) 的404引导页内容
- 增加名词解释: `uuid`
- 补充 [Q&A](Q&A.md)
- 新增 [参考文档](references.md),包含 OAuth 授权和第三方平台的API文档等内容
@@ -284,9 +334,9 @@ System.setProperty("proxyHost", "127.0.0.1");
- 新增
- 增加微信、QQ、支付宝、微博授权登录的帮助文档
- 合并[PR#57](https://github.com/justauth/JustAuth/pull/57),增加微信公众号登录 by [@xkcoding](https://github.com/xkcoding)
- [帮助文档](https://justauth.wiki)中增加自定义的404页面
- [帮助文档](https://justauth.wiki)中增加Gittalk插件
- [帮助文档](https://justauth.wiki)中增加Java代码高亮的插件
- [帮助文档](https://www.justauth.cn)中增加自定义的404页面
- [帮助文档](https://www.justauth.cn)中增加Gittalk插件
- [帮助文档](https://www.justauth.cn)中增加Java代码高亮的插件
- 增加`AuthUserGender#getWechatRealGender`方法,兼容获取微信平台的用户性别
- 修改
- 修复抖音登录取值取错层级的问题([issue#I15SIG@Gitee](https://gitee.com/yadong.zhang/JustAuth/issues/I15SIG)
@@ -295,7 +345,7 @@ System.setProperty("proxyHost", "127.0.0.1");
- `AuthResponseStatus`枚举类中增加`ILLEGAL_STATUS`、`REQUIRED_REFRESH_TOKEN`两个枚举值
- `AuthSource`接口中增加`getName`方法,用来对外提供实际`source`的字符串值
- `AuthWeiboRequest`微博授权登录中实现`revoke`方法,支持手动回收授权
- [帮助文档](https://justauth.wiki)中修复[腾讯云登录]链接错误的问题
- [帮助文档](https://www.justauth.cn)中修复[腾讯云登录]链接错误的问题
- 升级
- 升级相关依赖:lombok@v1.18.10hutool@5.0.5fastjson@1.2.62alipay@4.8.10.ALL[PR#11@Gitee](https://gitee.com/yadong.zhang/JustAuth/pulls/11)
+5 -5
View File
@@ -1,5 +1,5 @@
<p align="center">
<a href="https://justauth.wiki"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/Justauth.png" width="400"></a>
<a href="https://www.justauth.cn"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/Justauth.png" width="400"></a>
</p>
<p align="center">
<strong>Login, so easy.</strong>
@@ -17,7 +17,7 @@
<a target="_blank" href="https://www.oracle.com/technetwork/java/javase/downloads/index.html">
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" ></img>
</a>
<a target="_blank" href="https://justauth.wiki" title="参考文档">
<a target="_blank" href="https://www.justauth.cn" title="参考文档">
<img src="https://img.shields.io/badge/Docs-latest-blueviolet.svg" ></img>
</a>
<a href="https://codecov.io/gh/justauth/JustAuth">
@@ -43,7 +43,7 @@
`JustAuth`, as you see, It is just a Java library of third-party authorized login, It's smaller and easier to use. JustAuth is the best third-party login tool written in JAVA.
Source Code[gitee](https://gitee.com/yadong.zhang/JustAuth) | [github](https://github.com/zhangyd-c/JustAuth)
Docs[Reference Doc](https://justauth.wiki)
Docs[Reference Doc](https://www.justauth.cn)
## Features
@@ -180,11 +180,11 @@ I look forward to your joining us.
## Contributors
[contributors](https://justauth.wiki/contributors.html)
[contributors](https://www.justauth.cn/contributors.html)
## Change Logs
[CHANGELOGS](https://justauth.wiki/update.html)
[CHANGELOGS](https://www.justauth.cn/update.html)
## Recommend
+14 -14
View File
@@ -1,5 +1,5 @@
<p align="center">
<a href="https://justauth.wiki"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/Justauth.png" width="400"></a>
<a href="https://www.justauth.cn"><img src="https://gitee.com/yadong.zhang/static/raw/master/JustAuth/Justauth.png" width="400"></a>
</p>
<p align="center">
<strong>Login, so easy.</strong>
@@ -17,7 +17,7 @@
<a target="_blank" href="https://www.oracle.com/technetwork/java/javase/downloads/index.html">
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" ></img>
</a>
<a target="_blank" href="https://justauth.wiki" title="参考文档">
<a target="_blank" href="https://www.justauth.cn" title="参考文档">
<img src="https://img.shields.io/badge/Docs-latest-blueviolet.svg" ></img>
</a>
<a href="https://codecov.io/gh/justauth/JustAuth">
@@ -40,13 +40,13 @@
QQ 群:230017570
微信群:justauth (备注`justauth`或者`ja`
帮助文档:[justauth.wiki](https://justauth.wiki)
帮助文档:[www.justauth.cn](https://www.justauth.cn)
## 什么是 JustAuth
JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具类库**,它可以让我们脱离繁琐的第三方登录 SDK,让登录变得**So easy!**
JustAuth 集成了诸如:Github、Gitee、支付宝、新浪微博、微信、Google、Facebook、Twitter、StackOverflow等国内外数十家第三方平台。更多请参考<a href="https://justauth.wiki" target="_blank">已集成的平台</a>
JustAuth 集成了诸如:Github、Gitee、支付宝、新浪微博、微信、Google、Facebook、Twitter、StackOverflow等国内外数十家第三方平台。更多请参考<a href="https://www.justauth.cn" target="_blank">已集成的平台</a>
## 有哪些特点?
@@ -55,12 +55,12 @@ JustAuth 集成了诸如:Github、Gitee、支付宝、新浪微博、微信、
## 有哪些功能?
- 集成国内外数十家第三方平台,实现快速接入。<a href="https://justauth.wiki/quickstart/how-to-use.html" target="_blank">参考文档</a>
- 自定义 State 缓存,支持各种分布式缓存组件。<a href="https://justauth.wiki/features/customize-the-state-cache.html" target="_blank">参考文档</a>
- 自定义 OAuth 平台,更容易适配自有的 OAuth 服务。<a href="https://justauth.wiki/features/customize-the-oauth.html" target="_blank">参考文档</a>
- 自定义 Http 实现,选择权完全交给开发者,不会单独依赖某一具体实现。<a href="https://justauth.wiki/quickstart/how-to-use.html#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F" target="_blank">参考文档</a>
- 自定义 Scope,支持更完善的授权体系。<a href="https://justauth.wiki/features/customize-scopes.html" target="_blank">参考文档</a>
- 更多...<a href="https://justauth.wiki" target="_blank">参考文档</a>
- 集成国内外数十家第三方平台,实现快速接入。<a href="https://www.justauth.cn/guide/" target="_blank">参考文档</a>
- 自定义 State 缓存,支持各种分布式缓存组件。<a href="https://www.justauth.cn/features/using-state/" target="_blank">参考文档</a>
- 自定义 OAuth 平台,更容易适配自有的 OAuth 服务。<a href="https://www.justauth.cn/features/customize-the-oauth/" target="_blank">参考文档</a>
- 自定义 Http 实现,选择权完全交给开发者,不会单独依赖某一具体实现。<a href="https://www.justauth.cn/guide/quickstart/how-to-use/#%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4" target="_blank">参考文档</a>
- 自定义 Scope,支持更完善的授权体系。<a href="https://www.justauth.cn/features/customize-scopes/#%E5%85%B3%E4%BA%8E-scope" target="_blank">参考文档</a>
- 更多...<a href="https://www.justauth.cn" target="_blank">参考文档</a>
## 快速开始
@@ -214,7 +214,7 @@ AuthRequest authRequest = AuthRequestBuilder.builder()
感谢以下赞助商的支持:
[我要赞助](https://justauth.wiki/sponsor.html)
[我要赞助](https://www.justauth.cn/sponsor.html)
## JustAuth 的用户
有很多公司、组织和个人把 JustAuth 用于学习、研究、生产环境和商业产品中,包括(但不限于):
@@ -234,7 +234,7 @@ AuthRequest authRequest = AuthRequestBuilder.builder()
- `mica` SpringBoot 微服务高效开发工具集: [https://github.com/lets-mica/mica](https://github.com/lets-mica/mica)
- `sureness` 面向restful api的高性能认证鉴权框架:[sureness](https://github.com/usthe/sureness)
更多推荐,请参考:[JustAuth - 开源推荐](https://justauth.wiki)
更多推荐,请参考:[JustAuth - 开源推荐](https://www.justauth.cn)
## 鸣谢
@@ -246,8 +246,8 @@ AuthRequest authRequest = AuthRequestBuilder.builder()
## 其他
- [CONTRIBUTORS](https://justauth.wiki/contributors.html)
- [CHANGELOGS](https://justauth.wiki/update.html)
- [CONTRIBUTORS](https://www.justauth.cn/contributors.html)
- [CHANGELOGS](https://www.justauth.cn/update.html)
- [PLAN](https://gitee.com/yadong.zhang/JustAuth/issues/IUGRK)
## 贡献者列表
+1 -1
View File
@@ -1 +1 @@
git pull origin dev && git pull github dev && git pull cc dev
git pull origin dev && git pull github dev
+1
View File
@@ -0,0 +1 @@
git pull origin master && git pull github master
+1 -1
View File
@@ -1 +1 @@
git push origin dev && git push github dev && git push cc dev
git push origin dev && git push github dev
+1 -1
View File
@@ -1 +1 @@
git push origin master && git push github master && git push cc master
git push origin master && git push github master
+1 -1
View File
@@ -1 +1 @@
1.16.4
1.16.7
+1 -1
View File
@@ -16,7 +16,7 @@
</head>
<body>
<script>
window.location.href = "https://justauth.wiki";
window.location.href = "https://www.justauth.cn";
</script>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

+4
View File
@@ -96,6 +96,10 @@ _注:非全部平台,部分平台可能不存在图例_
![授权Stack Overflow](https://images.gitee.com/uploads/images/2019/0718/192639_cc301ba7_784199.png "授权Stack Overflow")
#### 授权afdian
![授权afdian](https://img.fastmirror.net/s/2023/03/24/641d63a8c19b5.png "授权afDian")
#### 授权Twitter
![授权Twitter]( "授权Twitter")
+3
View File
@@ -18,6 +18,9 @@ case "$1" in
'ppd')
bin/pull-dev.sh
;;
'pp')
bin/pull.sh
;;
'pd')
bin/push-dev.sh
;;
+36 -4
View File
@@ -6,7 +6,7 @@
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.16.4</version>
<version>1.16.7</version>
<name>JustAuth</name>
<url>https://gitee.com/yadong.zhang/JustAuth</url>
@@ -58,11 +58,13 @@
<maven-gpg-version>1.6</maven-gpg-version>
<maven.test.skip>false</maven.test.skip>
<simple-http.version>1.0.5</simple-http.version>
<lombok-version>1.18.20</lombok-version>
<lombok-version>1.18.30</lombok-version>
<junit-version>4.13.2</junit-version>
<fastjson-version>1.2.78</fastjson-version>
<alipay-sdk-version>4.16.38.ALL</alipay-sdk-version>
<fastjson-version>1.2.83</fastjson-version>
<alipay-sdk-version>4.39.165.ALL</alipay-sdk-version>
<jacoco-version>0.8.2</jacoco-version>
<jwt.version>0.12.3</jwt.version>
<bcpkix-jdk18on.version>1.78</bcpkix-jdk18on.version>
</properties>
<dependencies>
@@ -93,6 +95,36 @@
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay-sdk-version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jwt.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jwt.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jwt.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${bcpkix-jdk18on.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -181,4 +181,75 @@ public class AuthConfig {
public String getAuthServerId() {
return StringUtils.isEmpty(authServerId) ? "default" : authServerId;
}
/**
* Microsoft Entra ID(原微软 AAD)中的租户 ID
*/
private String tenantId;
/**
* 苹果开发者账号中的密钥标识符
* @see <a href="https://developer.apple.com/help/account/configure-app-capabilities/create-a-sign-in-with-apple-private-key/">create-a-sign-in-with-apple-private-key</a>
*/
private String kid;
/**
* 苹果开发者账号中的团队ID
* @see <a href="https://developer.apple.com/help/glossary/team-id/">team id</a>
*/
private String teamId;
/**
* 新版企业微信 Web 登录时的参数,
*
* 登录类型。ServiceApp:服务商登录;CorpApp:企业自建/代开发应用登录。
* @see <a href="https://developer.work.weixin.qq.com/document/path/98152">https://developer.work.weixin.qq.com/document/path/98152</a>
* @since 1.16.7
*/
private String loginType = "CorpApp";
/**
* 企业微信平台的语言编码
*
* @since 1.16.7
*/
private String lang = "zh";
/**
* 钉钉平台参数:控制输出特定类型的组织列表,org_type=management 表示只输出有管理权限的组织。
*
* scope包含corpid时该参数存在意义。
*
* @see <a href="https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug">https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug</a>
* @since 1.16.7
*/
private String dingTalkOrgType;
/**
* 钉钉平台参数:用于指定用户需要选择的组织。
*
* scope包含corpid时该参数存在意义。传入的corpId需要是当前用户所在的组织。
*
* @see <a href="https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug">https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug</a>
* @since 1.16.7
*/
private String dingTalkCorpId;
/**
* 钉钉平台参数:true表示专属帐号登录,展示组织代码输入页。
*
* @see <a href="https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug">https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug</a>
* @since 1.16.7
*/
private boolean dingTalkExclusiveLogin;
/**
* 钉钉平台参数:开启了专属帐号功能的组织corpId。
*
* scope包含corpid时该参数存在意义。传入的corpId需要是当前用户所在的组织。
*
* @see <a href="https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug">https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug</a>
* @since 1.16.7
*/
private String dingTalkExclusiveCorpId;
}
@@ -112,6 +112,30 @@ public enum AuthDefaultSource implements AuthSource {
return AuthDingTalkRequest.class;
}
},
/**
* 新版钉钉扫码登录
*/
DINGTALK_V2 {
@Override
public String authorize() {
return "https://login.dingtalk.com/oauth2/challenge.htm";
}
@Override
public String accessToken() {
return "https://api.dingtalk.com/v1.0/oauth2/userAccessToken";
}
@Override
public String userInfo() {
return "https://api.dingtalk.com/v1.0/contact/users/me";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthDingTalkV2Request.class;
}
},
/**
* 钉钉账号登录
*/
@@ -382,6 +406,7 @@ public enum AuthDefaultSource implements AuthSource {
},
/**
* Google
* 端点地址:https://accounts.google.com/.well-known/openid-configuration
*/
GOOGLE {
@Override
@@ -391,12 +416,12 @@ public enum AuthDefaultSource implements AuthSource {
@Override
public String accessToken() {
return "https://www.googleapis.com/oauth2/v4/token";
return "https://oauth2.googleapis.com/token";
}
@Override
public String userInfo() {
return "https://www.googleapis.com/oauth2/v3/userinfo";
return "https://openidconnect.googleapis.com/v1/userinfo";
}
@Override
@@ -410,17 +435,17 @@ public enum AuthDefaultSource implements AuthSource {
FACEBOOK {
@Override
public String authorize() {
return "https://www.facebook.com/v10.0/dialog/oauth";
return "https://www.facebook.com/v18.0/dialog/oauth";
}
@Override
public String accessToken() {
return "https://graph.facebook.com/v10.0/oauth/access_token";
return "https://graph.facebook.com/v18.0/oauth/access_token";
}
@Override
public String userInfo() {
return "https://graph.facebook.com/v10.0/me";
return "https://graph.facebook.com/v18.0/me";
}
@Override
@@ -492,12 +517,12 @@ public enum AuthDefaultSource implements AuthSource {
MICROSOFT {
@Override
public String authorize() {
return "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
return "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize";
}
@Override
public String accessToken() {
return "https://login.microsoftonline.com/common/oauth2/v2.0/token";
return "https://login.microsoftonline.com/%s/oauth2/v2.0/token";
}
@Override
@@ -507,7 +532,7 @@ public enum AuthDefaultSource implements AuthSource {
@Override
public String refresh() {
return "https://login.microsoftonline.com/common/oauth2/v2.0/token";
return "https://login.microsoftonline.com/%s/oauth2/v2.0/token";
}
@Override
@@ -521,12 +546,12 @@ public enum AuthDefaultSource implements AuthSource {
MICROSOFT_CN {
@Override
public String authorize() {
return "https://login.partner.microsoftonline.cn/common/oauth2/v2.0/authorize";
return "https://login.partner.microsoftonline.cn/%s/oauth2/v2.0/authorize";
}
@Override
public String accessToken() {
return "https://login.partner.microsoftonline.cn/common/oauth2/v2.0/token";
return "https://login.partner.microsoftonline.cn/%s/oauth2/v2.0/token";
}
@Override
@@ -536,7 +561,7 @@ public enum AuthDefaultSource implements AuthSource {
@Override
public String refresh() {
return "https://login.partner.microsoftonline.cn/common/oauth2/v2.0/token";
return "https://login.partner.microsoftonline.cn/%s/oauth2/v2.0/token";
}
@Override
@@ -708,8 +733,11 @@ public enum AuthDefaultSource implements AuthSource {
/**
* 华为
*
* 当前方式未来可能被废弃,建议使用 {@link this#HUAWEI_V3}
*
* @since 1.10.0
*/
@Deprecated
HUAWEI {
@Override
public String authorize() {
@@ -737,6 +765,38 @@ public enum AuthDefaultSource implements AuthSource {
}
},
/**
* 华为最新版本的 API
*
* @since 1.16.7
*/
HUAWEI_V3 {
@Override
public String authorize() {
return "https://oauth-login.cloud.huawei.com/oauth2/v3/authorize";
}
@Override
public String accessToken() {
return "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
}
@Override
public String userInfo() {
return "https://account.cloud.huawei.com/rest.php";
}
@Override
public String refresh() {
return "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthHuaweiV3Request.class;
}
},
/**
* 企业微信二维码登录
*
@@ -763,6 +823,32 @@ public enum AuthDefaultSource implements AuthSource {
return AuthWeChatEnterpriseQrcodeRequest.class;
}
},
/**
* 新版企业微信 Web 登录(扫码),参考 <a href="https://developer.work.weixin.qq.com/document/path/98152">https://developer.work.weixin.qq.com/document/path/98152</a>
*
* @since 1.16.7
*/
WECHAT_ENTERPRISE_V2 {
@Override
public String authorize() {
return "https://login.work.weixin.qq.com/wwlogin/sso/login";
}
@Override
public String accessToken() {
return "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
}
@Override
public String userInfo() {
return "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthWeChatEnterpriseQrcodeV2Request.class;
}
},
/**
* 企业微信二维码第三方登录
*/
@@ -1272,6 +1358,141 @@ public enum AuthDefaultSource implements AuthSource {
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthProginnRequest.class;
}
},
/**
* 爱发电 <a href="https://afdian.net/">爱发电</a>
*/
AFDIAN {
@Override
public String authorize() {
return "https://afdian.net/oauth2/authorize";
}
@Override
public String accessToken() {
return "https://afdian.net/api/oauth2/access_token";
}
@Override
public String userInfo() {
return "";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthProginnRequest.class;
}
},
APPLE {
@Override
public String authorize() {
return "https://appleid.apple.com/auth/authorize";
}
/**
* @see <a href="https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens">generate_and_validate_tokens</a>
*/
@Override
public String accessToken() {
return "https://appleid.apple.com/auth/token";
}
@Override
public String userInfo() {
return "";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthAppleRequest.class;
}
},
FIGMA{
@Override
public String authorize() {
return "https://www.figma.com/oauth";
}
@Override
public String accessToken() {
return "https://www.figma.com/api/oauth/token";
}
@Override
public String userInfo() {
return "https://api.figma.com/v1/me";
}
@Override
public String refresh() {
return "https://www.figma.com/api/oauth/refresh";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthFigmaRequest.class;
}
},
/**
* 微信小程序授权登录
* @since yudaocode
*/
WECHAT_MINI_PROGRAM {
@Override
public String authorize() {
// 参见 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 文档
throw new UnsupportedOperationException("不支持获取授权 url,请使用小程序内置函数 wx.login() 登录获取 code");
}
@Override
public String accessToken() {
// 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档
// 获取 openid, unionId , session_key 等字段
return "https://api.weixin.qq.com/sns/jscode2session";
}
@Override
public String userInfo() {
// 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档
throw new UnsupportedOperationException("不支持获取用户信息 url,请使用小程序内置函数 wx.getUserProfile() 获取用户信息");
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthWechatMiniProgramRequest.class;
}
},
/**
* QQ小程序授权登录
*/
QQ_MINI_PROGRAM {
@Override
public String authorize() {
// 参见 https://q.qq.com/wiki/develop/miniprogram/frame/open_ability/open_userinfo.html 文档
throw new UnsupportedOperationException("不支持获取授权 url,请使用小程序内置函数 qq.login() 登录获取 code");
}
@Override
public String accessToken() {
// 参见 https://q.qq.com/wiki/develop/miniprogram/server/open_port/port_login.html 文档
// 获取 openid, unionId , session_key 等字段
return "https://api.q.qq.com/sns/jscode2session";
}
@Override
public String userInfo() {
// 参见 https://q.qq.com/wiki/develop/miniprogram/API/open_port/port_userinfo.html 文档
throw new UnsupportedOperationException("不支持获取用户信息 url,请使用小程序内置函数 qq.getUserInfo() 获取用户信息");
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return null;
}
}
}
@@ -29,6 +29,11 @@ public enum AuthResponseStatus {
ILLEGAL_STATUS(5009, "Illegal state"),
REQUIRED_REFRESH_TOKEN(5010, "The refresh token is required; it must not be null"),
ILLEGAL_TOKEN(5011, "Invalid token"),
ILLEGAL_KID(5012, "Invalid key identifier(kid)"),
ILLEGAL_TEAM_ID(5013, "Invalid team id"),
ILLEGAL_CLIENT_ID(5014, "Invalid client id"),
ILLEGAL_CLIENT_SECRET(5015, "Invalid client secret"),
ILLEGAL_WECHAT_AGENT_ID(5016, "Illegal wechat agent id"),
;
private final int code;
@@ -0,0 +1,19 @@
package me.zhyd.oauth.enums.scope;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @see <a href="https://developer.apple.com/documentation/sign_in_with_apple/clientconfigi/3230955-scope/">scope</a>
*/
@Getter
@AllArgsConstructor
public enum AuthAppleScope implements AuthScope {
EMAIL("email", "用户邮箱", true),
NAME("name", "用户名", true),
;
private final String scope;
private final String description;
private final boolean isDefault;
}
@@ -0,0 +1,33 @@
package me.zhyd.oauth.enums.scope;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 钉钉平台 OAuth 授权范围
*
* https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.16.7
*/
@Getter
@AllArgsConstructor
public enum AuthDingTalkScope implements AuthScope {
/**
* 无需申请 默认开启
*/
openid("openid", "授权后可获得用户userid", true),
/**
* 无需申请 默认开启
*/
corpid("corpid", "授权后可获得登录过程中用户选择的组织id", false)
;
private final String scope;
private final String description;
private final boolean isDefault;
}
@@ -17,18 +17,19 @@ public enum AuthFacebookScope implements AuthScope {
/**
* {@code scope} 含义,以{@code description} 为准
*/
EMAIL("email", "获取用户的邮箱", true),
USER_AGE_RANGE("user_age_range", "允许应用程序访问用户的年龄范围", true),
USER_BIRTHDAY("user_birthday", "获取用户的生日", true),
USER_FRIENDS("user_friends", "获取用户的好友列表", true),
USER_GENDER("user_gender", "获取用户的性别", true),
USER_HOMETOWN("user_hometown", "获取用户的家乡信息", true),
USER_LIKES("user_likes", "获取用户的喜欢列表", true),
PUBLIC_PROFILE("public_profile", "权限允许应用读取用户默认的公开资料", true),
EMAIL("email", "获取用户的邮箱", false),
USER_AGE_RANGE("user_age_range", "允许应用程序访问用户的年龄范围", false),
USER_BIRTHDAY("user_birthday", "获取用户的生日", false),
USER_FRIENDS("user_friends", "获取用户的好友列表", false),
USER_GENDER("user_gender", "获取用户的性别", false),
USER_HOMETOWN("user_hometown", "获取用户的家乡信息", false),
USER_LIKES("user_likes", "获取用户的喜欢列表", false),
USER_LINK("user_link", "获取用户的个人链接", true),
USER_LOCATION("user_location", "获取用户的位置信息", true),
USER_PHOTOS("user_photos", "获取用户的相册信息", true),
USER_POSTS("user_posts", "获取用户发布的内容", true),
USER_VIDEOS("user_videos", "获取用户上传的视频信息", true),
USER_LOCATION("user_location", "获取用户的位置信息", false),
USER_PHOTOS("user_photos", "获取用户的相册信息", false),
USER_POSTS("user_posts", "获取用户发布的内容", false),
USER_VIDEOS("user_videos", "获取用户上传的视频信息", false),
GROUPS_ACCESS_MEMBER_INFO("groups_access_member_info", "获取公开的群组成员信息", false),
PUBLISH_TO_GROUPS("publish_to_groups", "授权您的应用程序代表某人将内容发布到组中,前提是他们已经授予您的应用程序访问权限", false),
;
@@ -0,0 +1,29 @@
package me.zhyd.oauth.enums.scope;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Figma OAuth 授权范围
* <a href="https://www.figma.com/developers/api#authentication-scopes">...</a>
*
* @author xiangqian
* @since 1.16.6
*/
@Getter
@AllArgsConstructor
public enum AuthFigmaScope implements AuthScope {
FILE_CONTENT("files:read", "Read files, projects, users, versions, comments, components & styles, and webhooks", true),
VARIABLES("file_variables:read,file_variables:write", "Read and write to variables in Figma file. Note: this is only available to members in Enterprise organizations", false),
COMMENTS("file_comments:write", "Post and delete comments and comment reactions in files", false),
DEV_RESOURCES("file_dev_resources:read,file_dev_resources:write", "Read and write to dev resources in files", false),
LIBRARY_ANALYTICS("library_analytics:read", "Read your design system analytics", false),
WEBHOOKS("webhooks:write", "Create and manage webhooks", false);
private final String scope;
private final String description;
private final boolean isDefault;
}
@@ -6,12 +6,15 @@ import lombok.Getter;
/**
* 华为平台 OAuth 授权范围
*
* 当前方式未来可能被废弃,建议使用 {@link AuthHuaweiV3Scope}
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.0.0
*/
@Getter
@AllArgsConstructor
@Deprecated
public enum AuthHuaweiScope implements AuthScope {
/**
@@ -0,0 +1,51 @@
package me.zhyd.oauth.enums.scope;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 华为平台 V3 版本 OAuth 授权范围
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.16.7
*/
@Getter
@AllArgsConstructor
public enum AuthHuaweiV3Scope implements AuthScope {
/**
* {@code scope} 含义,以{@code description} 为准
*/
OPENID("openid", "基础scopev3必选", true),
/**
* {@code scope} 含义,以{@code description} 为准
*/
BASE_PROFILE("https://www.huawei.com/auth/account/base.profile", "获取用户的基本信息", true),
MOBILE_NUMBER("https://www.huawei.com/auth/account/mobile.number", "获取用户的手机号", false),
ACCOUNTLIST("https://www.huawei.com/auth/account/accountlist", "获取用户的账单列表", false),
/**
* 以下两个 scope 不需要经过华为评估和验证
*/
SCOPE_DRIVE_FILE("https://www.huawei.com/auth/drive.file", "只允许访问由应用程序创建或打开的文件", false),
SCOPE_DRIVE_APPDATA("https://www.huawei.com/auth/drive.appdata", "只允许访问由应用程序创建或打开的文件", false),
/**
* 以下四个 scope 使用前需要向drivekit@huawei.com提交申请
* <p>
* 参考:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides-V5/server-dev-0000001050039664-V5#ZH-CN_TOPIC_0000001050039664__section1618418855716
*/
SCOPE_DRIVE("https://www.huawei.com/auth/drive", "只允许访问由应用程序创建或打开的文件", false),
SCOPE_DRIVE_READONLY("https://www.huawei.com/auth/drive.readonly", "只允许访问由应用程序创建或打开的文件", false),
SCOPE_DRIVE_METADATA("https://www.huawei.com/auth/drive.metadata", "只允许访问由应用程序创建或打开的文件", false),
SCOPE_DRIVE_METADATA_READONLY("https://www.huawei.com/auth/drive.metadata.readonly", "只允许访问由应用程序创建或打开的文件", false),
;
;
private final String scope;
private final String description;
private final boolean isDefault;
}
@@ -5,6 +5,7 @@ import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import me.zhyd.oauth.utils.StringUtils;
import java.io.Serializable;
@@ -57,4 +58,19 @@ public class AuthCallback implements Serializable {
*/
private String oauth_verifier;
/**
* 苹果仅在用户首次授权应用程序时返回此值。如果您的应用程序已经获得了用户的授权,那么苹果将不会再次返回此值
* @see <a href="https://developer.apple.com/documentation/sign_in_with_apple/useri">user info</a>
*/
private String user;
/**
* 苹果错误信息,仅在用户取消授权时返回此值
* @see <a href="https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms">error response</a>
*/
private String error;
public String getCode() {
return StringUtils.isEmpty(code) ? auth_code : code;
}
}
@@ -44,6 +44,12 @@ public class AuthToken implements Serializable {
* @since 1.10.0
*/
private String code;
/**
* 微信公众号 - 网页授权的登录时可用
*
* 微信针对网页授权登录,增加了一个快照页的逻辑,快照页获取到的微信用户的 uid oid 和头像昵称都是虚拟的信息
*/
private boolean snapshotUser;
/**
* Twitter附带属性
@@ -56,4 +62,15 @@ public class AuthToken implements Serializable {
private String screenName;
private Boolean oauthCallbackConfirmed;
/**
* Apple附带属性
*/
private String username;
/**
* 新版钉钉附带属性
*
* @since 1.16.7
*/
private String corpId;
}
@@ -72,4 +72,11 @@ public class AuthUser implements Serializable {
*/
private JSONObject rawUserInfo;
/**
* 微信公众号 - 网页授权的登录时可用
*
* 微信针对网页授权登录,增加了一个快照页的逻辑,快照页获取到的微信用户的 uid oid 和头像昵称都是虚拟的信息
*/
private boolean snapshotUser;
}
@@ -34,12 +34,12 @@ public abstract class AbstractAuthDingtalkRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder().accessCode(authCallback.getCode()).build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String code = authToken.getAccessCode();
JSONObject param = new JSONObject();
param.put("tmp_auth_code", code);
@@ -16,6 +16,7 @@ import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.AuthScopeUtils;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Map;
@@ -38,7 +39,7 @@ public abstract class AbstractAuthMicrosoftRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return getToken(accessTokenUrl(authCallback.getCode()));
}
@@ -79,7 +80,7 @@ public abstract class AbstractAuthMicrosoftRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String token = authToken.getAccessToken();
String tokenType = authToken.getTokenType();
String jwt = tokenType + " " + token;
@@ -110,8 +111,8 @@ public abstract class AbstractAuthMicrosoftRequest extends AuthDefaultRequest {
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
return AuthResponse.builder()
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(authToken.getRefreshToken())))
.build();
@@ -126,9 +127,16 @@ public abstract class AbstractAuthMicrosoftRequest extends AuthDefaultRequest {
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(super.authorize(state))
// 兼容 Microsoft Entra ID 登录(原微软 AAD
// @since 1.16.6
String tenantId = StringUtils.isEmpty(config.getTenantId()) ? "common" : config.getTenantId();
return UrlBuilder.fromBaseUrl(String.format(source.authorize(), tenantId))
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(state))
.queryParam("response_mode", "query")
.queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthMicrosoftScope.values())))
.queryParam("scope", this.getScopes(" ", false, AuthScopeUtils.getDefaultScopes(AuthMicrosoftScope.values())))
.build();
}
@@ -140,12 +148,13 @@ public abstract class AbstractAuthMicrosoftRequest extends AuthDefaultRequest {
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
String tenantId = StringUtils.isEmpty(config.getTenantId()) ? "common" : config.getTenantId();
return UrlBuilder.fromBaseUrl(String.format(source.accessToken(), tenantId))
.queryParam("code", code)
.queryParam("client_id", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("grant_type", "authorization_code")
.queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthMicrosoftScope.values())))
.queryParam("scope", this.getScopes(" ", false, AuthScopeUtils.getDefaultScopes(AuthMicrosoftScope.values())))
.queryParam("redirect_uri", config.getRedirectUri())
.build();
}
@@ -169,12 +178,13 @@ public abstract class AbstractAuthMicrosoftRequest extends AuthDefaultRequest {
*/
@Override
protected String refreshTokenUrl(String refreshToken) {
return UrlBuilder.fromBaseUrl(source.refresh())
String tenantId = StringUtils.isEmpty(config.getTenantId()) ? "common" : config.getTenantId();
return UrlBuilder.fromBaseUrl(String.format(source.refresh(), tenantId))
.queryParam("client_id", config.getClientId())
.queryParam("client_secret", config.getClientSecret())
.queryParam("refresh_token", refreshToken)
.queryParam("grant_type", "refresh_token")
.queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthMicrosoftScope.values())))
.queryParam("scope", this.getScopes(" ", false, AuthScopeUtils.getDefaultScopes(AuthMicrosoftScope.values())))
.queryParam("redirect_uri", config.getRedirectUri())
.build();
}
@@ -3,7 +3,6 @@ package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
@@ -12,6 +11,7 @@ import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
@@ -34,7 +34,7 @@ public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultReq
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doGetAuthorizationCode(accessTokenUrl(null));
JSONObject object = this.checkResponse(response);
@@ -47,7 +47,7 @@ public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultReq
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject object = this.checkResponse(response);
@@ -56,8 +56,8 @@ public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultReq
throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM, source);
}
String userId = object.getString("UserId");
String userDetailResponse = getUserDetail(authToken.getAccessToken(), userId);
JSONObject userDetail = this.checkResponse(userDetailResponse);
String userTicket = object.getString("user_ticket");
JSONObject userDetail = getUserDetail(authToken.getAccessToken(), userId, userTicket);
return AuthUser.builder()
.rawUserInfo(userDetail)
@@ -123,14 +123,31 @@ public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultReq
*
* @param accessToken accessToken
* @param userId 企业内用户id
* @param userTicket 成员票据,用于获取用户信息或敏感信息
* @return 用户详情
*/
private String getUserDetail(String accessToken, String userId) {
String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
private JSONObject getUserDetail(String accessToken, String userId, String userTicket) {
// 用户基础信息
String userInfoUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
.queryParam("access_token", accessToken)
.queryParam("userid", userId)
.build();
return new HttpUtils(config.getHttpConfig()).get(userDetailUrl).getBody();
String userInfoResponse = new HttpUtils(config.getHttpConfig()).get(userInfoUrl).getBody();
JSONObject userInfo = checkResponse(userInfoResponse);
// 用户敏感信息
if (StringUtils.isNotEmpty(userTicket)) {
String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail")
.queryParam("access_token", accessToken)
.build();
JSONObject param = new JSONObject();
param.put("user_ticket", userTicket);
String userDetailResponse = new HttpUtils(config.getHttpConfig()).post(userDetailUrl, param.toJSONString()).getBody();
JSONObject userDetail = checkResponse(userDetailResponse);
userInfo.putAll(userDetail);
}
return userInfo;
}
}
@@ -0,0 +1,73 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.HashMap;
import java.util.Map;
/**
* 爱发电
*
* @author handy
*/
public class AuthAfDianRequest extends AuthDefaultRequest {
public AuthAfDianRequest(AuthConfig config) {
super(config, AuthDefaultSource.AFDIAN);
}
public AuthAfDianRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.AFDIAN, authStateCache);
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> params = new HashMap<>();
params.put("grant_type", "authorization_code");
params.put("client_id", config.getClientId());
params.put("client_secret", config.getClientSecret());
params.put("code", authCallback.getCode());
params.put("redirect_uri", config.getRedirectUri());
String response = new HttpUtils(config.getHttpConfig()).post(AuthDefaultSource.AFDIAN.accessToken(), params, false).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
String userId = accessTokenObject.getJSONObject("data").getString("user_id");
return AuthToken.builder().userId(userId).build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
return AuthUser.builder()
.uuid(authToken.getUserId())
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source.toString())
.build();
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("scope", "basic")
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(state))
.build();
}
}
@@ -0,0 +1,152 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.alipay.api.request.AlipayUserInfoShareRequest;
import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.alipay.api.response.AlipayUserInfoShareResponse;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import static me.zhyd.oauth.config.AuthDefaultSource.ALIPAY;
/**
* 支付宝证书模式登录
*
* @since 1.16.7
*/
public class AuthAlipayCertRequest extends AuthDefaultRequest {
private final AlipayClient alipayClient;
public AuthAlipayCertRequest(AuthConfig config, AlipayConfig alipayConfig) {
super(config, ALIPAY);
try {
this.alipayClient = new DefaultAlipayClient(alipayConfig);
} catch (AlipayApiException e) {
throw new AuthException(e);
}
}
@Override
protected void checkCode(AuthCallback authCallback) {
if (StringUtils.isEmpty(authCallback.getAuth_code())) {
throw new AuthException(AuthResponseStatus.ILLEGAL_CODE, source);
}
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setGrantType("authorization_code");
request.setCode(authCallback.getAuth_code());
AlipaySystemOauthTokenResponse response;
try {
response = this.alipayClient.certificateExecute(request);
} catch (Exception e) {
throw new AuthException(e);
}
if (!response.isSuccess()) {
throw new AuthException(response.getSubMsg());
}
return AuthToken.builder()
.accessToken(response.getAccessToken())
.uid(response.getUserId())
.expireIn(Integer.parseInt(response.getExpiresIn()))
.refreshToken(response.getRefreshToken())
.build();
}
/**
* 刷新access token (续期)
*
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
@Override
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setGrantType("refresh_token");
request.setRefreshToken(authToken.getRefreshToken());
AlipaySystemOauthTokenResponse response = null;
try {
response = this.alipayClient.certificateExecute(request);
} catch (Exception e) {
throw new AuthException(e);
}
if (!response.isSuccess()) {
throw new AuthException(response.getSubMsg());
}
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(response.getAccessToken())
.uid(response.getUserId())
.expireIn(Integer.parseInt(response.getExpiresIn()))
.refreshToken(response.getRefreshToken())
.build())
.build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
AlipayUserInfoShareRequest request = new AlipayUserInfoShareRequest();
AlipayUserInfoShareResponse response = null;
try {
response = this.alipayClient.certificateExecute(request, accessToken);
} catch (AlipayApiException e) {
throw new AuthException(e.getErrMsg(), e);
}
if (!response.isSuccess()) {
throw new AuthException(response.getSubMsg());
}
String province = response.getProvince(), city = response.getCity();
String location = String.format("%s %s", StringUtils.isEmpty(province) ? "" : province, StringUtils.isEmpty(city) ? "" : city);
return AuthUser.builder()
.rawUserInfo(JSONObject.parseObject(JSONObject.toJSONString(response)))
.uuid(response.getOpenId())
.username(StringUtils.isEmpty(response.getUserName()) ? response.getNickName() : response.getUserName())
.nickname(response.getNickName())
.avatar(response.getAvatar())
.location(location)
.gender(AuthUserGender.getRealGender(response.getGender()))
.token(authToken)
.source(source.toString())
.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("app_id", config.getClientId())
.queryParam("scope", "auth_user")
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(state))
.build();
}
}
@@ -26,7 +26,7 @@ import me.zhyd.oauth.utils.UrlBuilder;
import java.net.InetSocketAddress;
/**
* 支付宝登录
* 支付宝公钥模式登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.0.1
@@ -41,6 +41,8 @@ public class AuthAlipayRequest extends AuthDefaultRequest {
private final AlipayClient alipayClient;
private static final String GATEWAY = "https://openapi.alipay.com/gateway.do";
/**
* @see AuthAlipayRequest#AuthAlipayRequest(me.zhyd.oauth.config.AuthConfig, java.lang.String)
* @deprecated 请使用带有"alipayPublicKey"参数的构造方法
@@ -78,8 +80,8 @@ public class AuthAlipayRequest extends AuthDefaultRequest {
public AuthAlipayRequest(AuthConfig config, String alipayPublicKey) {
super(config, AuthDefaultSource.ALIPAY);
this.alipayPublicKey = determineAlipayPublicKey(alipayPublicKey, config);
this.alipayClient = new DefaultAlipayClient(AuthDefaultSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(), "json", "UTF-8", alipayPublicKey, "RSA2");
check(config);
this.alipayClient = new DefaultAlipayClient(GATEWAY, config.getClientId(), config.getClientSecret(), "json", "UTF-8", this.alipayPublicKey, "RSA2");
}
/**
@@ -92,16 +94,16 @@ public class AuthAlipayRequest extends AuthDefaultRequest {
public AuthAlipayRequest(AuthConfig config, String alipayPublicKey, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.ALIPAY, authStateCache);
this.alipayPublicKey = determineAlipayPublicKey(alipayPublicKey, config);
check(config);
if (config.getHttpConfig() != null && config.getHttpConfig().getProxy() != null
&& config.getHttpConfig().getProxy().address() instanceof InetSocketAddress) {
InetSocketAddress address = (InetSocketAddress) config.getHttpConfig().getProxy().address();
this.alipayClient = new DefaultAlipayClient(AuthDefaultSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(),
"json", "UTF-8", alipayPublicKey, "RSA2", address.getHostName(), address.getPort());
this.alipayClient = new DefaultAlipayClient(GATEWAY, config.getClientId(), config.getClientSecret(),
"json", "UTF-8", this.alipayPublicKey, "RSA2", address.getHostName(), address.getPort());
} else {
this.alipayClient = new DefaultAlipayClient(AuthDefaultSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(),
"json", "UTF-8", alipayPublicKey, "RSA2");
this.alipayClient = new DefaultAlipayClient(GATEWAY, config.getClientId(), config.getClientSecret(),
"json", "UTF-8", this.alipayPublicKey, "RSA2");
}
check(config);
}
/**
@@ -114,9 +116,9 @@ public class AuthAlipayRequest extends AuthDefaultRequest {
public AuthAlipayRequest(AuthConfig config, String alipayPublicKey, AuthStateCache authStateCache, String proxyHost, Integer proxyPort) {
super(config, AuthDefaultSource.ALIPAY, authStateCache);
this.alipayPublicKey = determineAlipayPublicKey(alipayPublicKey, config);
this.alipayClient = new DefaultAlipayClient(AuthDefaultSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(),
"json", "UTF-8", alipayPublicKey, "RSA2", proxyHost, proxyPort);
check(config);
this.alipayClient = new DefaultAlipayClient(GATEWAY, config.getClientId(), config.getClientSecret(),
"json", "UTF-8", this.alipayPublicKey, "RSA2", proxyHost, proxyPort);
}
private String determineAlipayPublicKey(String alipayPublicKey, AuthConfig config) {
@@ -145,7 +147,7 @@ public class AuthAlipayRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setGrantType("authorization_code");
request.setCode(authCallback.getAuth_code());
@@ -173,7 +175,7 @@ public class AuthAlipayRequest extends AuthDefaultRequest {
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setGrantType("refresh_token");
request.setRefreshToken(authToken.getRefreshToken());
@@ -186,7 +188,7 @@ public class AuthAlipayRequest extends AuthDefaultRequest {
if (!response.isSuccess()) {
throw new AuthException(response.getSubMsg());
}
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(response.getAccessToken())
@@ -198,7 +200,7 @@ public class AuthAlipayRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
AlipayUserInfoShareRequest request = new AlipayUserInfoShareRequest();
AlipayUserInfoShareResponse response = null;
@@ -26,7 +26,7 @@ public class AuthAliyunRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
return AuthToken.builder()
@@ -39,7 +39,7 @@ public class AuthAliyunRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userInfo = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(userInfo);
return AuthUser.builder()
@@ -50,15 +50,16 @@ public class AuthAmazonRequest extends AuthDefaultRequest {
*/
@Override
public String authorize(String state) {
String realState = getRealState(state);
UrlBuilder builder = UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("client_id", config.getClientId())
.queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthAmazonScope.values())))
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("response_type", "code")
.queryParam("state", getRealState(state));
.queryParam("state", realState);
if (config.isPkce()) {
String cacheKey = this.source.getName().concat(":code_verifier:").concat(config.getClientId());
String cacheKey = this.source.getName().concat(":code_verifier:").concat(realState);
String codeVerifier = PkceUtil.generateCodeVerifier();
String codeChallengeMethod = "S256";
String codeChallenge = PkceUtil.generateCodeChallenge(codeChallengeMethod, codeVerifier);
@@ -77,7 +78,7 @@ public class AuthAmazonRequest extends AuthDefaultRequest {
* @return access token
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> form = new HashMap<>(9);
form.put("grant_type", "authorization_code");
form.put("code", authCallback.getCode());
@@ -86,7 +87,7 @@ public class AuthAmazonRequest extends AuthDefaultRequest {
form.put("client_secret", config.getClientSecret());
if (config.isPkce()) {
String cacheKey = this.source.getName().concat(":code_verifier:").concat(config.getClientId());
String cacheKey = this.source.getName().concat(":code_verifier:").concat(authCallback.getState());
String codeVerifier = this.authStateCache.get(cacheKey);
form.put("code_verifier", codeVerifier);
}
@@ -94,13 +95,13 @@ public class AuthAmazonRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken authToken) {
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
Map<String, String> form = new HashMap<>(7);
form.put("grant_type", "refresh_token");
form.put("refresh_token", authToken.getRefreshToken());
form.put("client_id", config.getClientId());
form.put("client_secret", config.getClientSecret());
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(form, this.source.refresh()))
.build();
@@ -140,7 +141,7 @@ public class AuthAmazonRequest extends AuthDefaultRequest {
* @return AuthUser
*/
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
this.checkToken(accessToken);
@@ -0,0 +1,156 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.security.AbstractJwk;
import lombok.Data;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.scope.AuthAppleScope;
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.AuthScopeUtils;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import java.io.IOException;
import java.io.StringReader;
import java.security.PrivateKey;
import java.util.Base64;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class AuthAppleRequest extends AuthDefaultRequest {
private static final String AUD = "https://appleid.apple.com";
private volatile PrivateKey privateKey;
public AuthAppleRequest(AuthConfig config) {
super(config, AuthDefaultSource.APPLE);
}
public AuthAppleRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.APPLE, authStateCache);
}
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(super.authorize(state))
.queryParam("response_mode", "form_post")
.queryParam("scope", this.getScopes(" ", false, AuthScopeUtils.getDefaultScopes(AuthAppleScope.values())))
.build();
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
if (!StringUtils.isEmpty(authCallback.getError())) {
throw new AuthException(authCallback.getError());
}
this.config.setClientSecret(this.getToken());
// if failed will throw AuthException
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
// https://developer.apple.com/documentation/sign_in_with_apple/tokenresponse
AuthToken.AuthTokenBuilder builder = AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.tokenType(accessTokenObject.getString("token_type"))
.idToken(accessTokenObject.getString("id_token"));
if (!StringUtils.isEmpty(authCallback.getUser())) {
try {
AppleUserInfo userInfo = JSONObject.parseObject(authCallback.getUser(), AppleUserInfo.class);
builder.username(userInfo.getName().getFirstName() + " " + userInfo.getName().getLastName());
} catch (Exception ignored) {
}
}
return builder.build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
Base64.Decoder urlDecoder = Base64.getUrlDecoder();
String[] idToken = authToken.getIdToken().split("\\.");
String payload = new String(urlDecoder.decode(idToken[1]));
JSONObject object = JSONObject.parseObject(payload);
// https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple#3383773
return AuthUser.builder()
.rawUserInfo(object)
.uuid(object.getString("sub"))
.email(object.getString("email"))
.username(authToken.getUsername())
.token(authToken)
.source(source.toString())
.build();
}
@Override
protected void checkConfig(AuthConfig config) {
super.checkConfig(config);
if (StringUtils.isEmpty(config.getClientId())) {
throw new AuthException(AuthResponseStatus.ILLEGAL_CLIENT_ID, source);
}
if (StringUtils.isEmpty(config.getClientSecret())) {
throw new AuthException(AuthResponseStatus.ILLEGAL_CLIENT_SECRET, source);
}
if (StringUtils.isEmpty(config.getKid())) {
throw new AuthException(AuthResponseStatus.ILLEGAL_KID, source);
}
if (StringUtils.isEmpty(config.getTeamId())) {
throw new AuthException(AuthResponseStatus.ILLEGAL_TEAM_ID, source);
}
}
/**
* 获取token
* @see <a href="https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret">creating-a-client-secret</a>
* @return jwt token
*/
private String getToken() {
return Jwts.builder().header().add(AbstractJwk.KID.getId(), this.config.getKid()).and()
.issuer(this.config.getTeamId())
.subject(this.config.getClientId())
.audience().add(AUD).and()
.expiration(new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(3)))
.issuedAt(new Date())
.signWith(getPrivateKey())
.compact();
}
private PrivateKey getPrivateKey() {
if (this.privateKey == null) {
synchronized (this) {
if (this.privateKey == null) {
try (PEMParser pemParser = new PEMParser(new StringReader(this.config.getClientSecret()))) {
JcaPEMKeyConverter pemKeyConverter = new JcaPEMKeyConverter();
PrivateKeyInfo keyInfo = (PrivateKeyInfo) pemParser.readObject();
this.privateKey = pemKeyConverter.getPrivateKey(keyInfo);
} catch (IOException e) {
throw new AuthException("Failed to get apple private key", e);
}
}
}
}
return this.privateKey;
}
@Data
static class AppleUserInfo {
private AppleUsername name;
private String email;
}
@Data
static class AppleUsername {
private String firstName;
private String lastName;
}
}
@@ -34,7 +34,7 @@ public class AuthBaiduRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
return getAuthToken(response);
}
@@ -48,7 +48,7 @@ public class AuthBaiduRequest extends AuthDefaultRequest {
* @return AuthUser
*/
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userInfo = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
@@ -81,7 +81,7 @@ public class AuthBaiduRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken authToken) {
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
String refreshUrl = UrlBuilder.fromBaseUrl(this.source.refresh())
.queryParam("grant_type", "refresh_token")
.queryParam("refresh_token", authToken.getRefreshToken())
@@ -89,7 +89,7 @@ public class AuthBaiduRequest extends AuthDefaultRequest {
.queryParam("client_secret", this.config.getClientSecret())
.build();
String response = new HttpUtils(config.getHttpConfig()).get(refreshUrl).getBody();
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getAuthToken(response))
.build();
@@ -30,7 +30,7 @@ public class AuthCodingRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doGetAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
@@ -42,7 +42,7 @@ public class AuthCodingRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
@@ -28,7 +28,7 @@ public class AuthCsdnRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
@@ -36,7 +36,7 @@ public class AuthCsdnRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
@@ -40,28 +40,9 @@ public abstract class AuthDefaultRequest implements AuthRequest {
throw new AuthException(AuthResponseStatus.PARAMETER_INCOMPLETE, source);
}
// 校验配置合法性
AuthChecker.checkConfig(config, source);
this.checkConfig(config);
}
/**
* 获取access token
*
* @param authCallback 授权成功后的回调参数
* @return token
* @see AuthDefaultRequest#authorize()
* @see AuthDefaultRequest#authorize(String)
*/
protected abstract AuthToken getAccessToken(AuthCallback authCallback);
/**
* 使用token换取用户信息
*
* @param authToken token信息
* @return 用户信息
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
*/
protected abstract AuthUser getUserInfo(AuthToken authToken);
/**
* 统一的登录入口。当通过{@link AuthDefaultRequest#authorize(String)}授权成功后,会跳转到调用方的相关回调方法中
* 方法的入参可以使用{@code AuthCallback}{@code AuthCallback}类中封装好了OAuth2授权回调所需要的参数
@@ -70,7 +51,7 @@ public abstract class AuthDefaultRequest implements AuthRequest {
* @return AuthResponse
*/
@Override
public AuthResponse login(AuthCallback authCallback) {
public AuthResponse<AuthUser> login(AuthCallback authCallback) {
try {
checkCode(authCallback);
if (!config.isIgnoreCheckState()) {
@@ -79,7 +60,7 @@ public abstract class AuthDefaultRequest implements AuthRequest {
AuthToken authToken = this.getAccessToken(authCallback);
AuthUser user = this.getUserInfo(authToken);
return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build();
return AuthResponse.<AuthUser>builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build();
} catch (Exception e) {
Log.error("Failed to login with oauth authorization.", e);
return this.responseError(e);
@@ -96,7 +77,7 @@ public abstract class AuthDefaultRequest implements AuthRequest {
* @param e 具体的异常
* @return AuthResponse
*/
AuthResponse responseError(Exception e) {
AuthResponse<AuthUser> responseError(Exception e) {
int errorCode = AuthResponseStatus.FAILURE.getCode();
String errorMsg = e.getMessage();
if (e instanceof AuthException) {
@@ -106,7 +87,7 @@ public abstract class AuthDefaultRequest implements AuthRequest {
errorMsg = authException.getErrorMsg();
}
}
return AuthResponse.builder().code(errorCode).msg(errorMsg).build();
return AuthResponse.<AuthUser>builder().code(errorCode).msg(errorMsg).build();
}
/**
@@ -295,4 +276,8 @@ public abstract class AuthDefaultRequest implements AuthRequest {
return encode ? UrlUtil.urlEncode(scopeStr) : scopeStr;
}
protected void checkConfig(AuthConfig config) {
AuthChecker.checkConfig(config, source);
}
}
@@ -0,0 +1,108 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import com.xkcoding.http.support.HttpHeader;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.scope.AuthDingTalkScope;
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.AuthScopeUtils;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.HashMap;
import java.util.Map;
/**
* 新版钉钉二维码登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.16.7
*/
public class AuthDingTalkV2Request extends AuthDefaultRequest {
public AuthDingTalkV2Request(AuthConfig config) {
super(config, AuthDefaultSource.DINGTALK_V2);
}
public AuthDingTalkV2Request(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.DINGTALK_V2, authStateCache);
}
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("response_type", "code")
.queryParam("client_id", config.getClientId())
.queryParam("scope", this.getScopes(",", true, AuthScopeUtils.getDefaultScopes(AuthDingTalkScope.values())))
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("prompt", "consent")
.queryParam("org_type", config.getDingTalkOrgType())
.queryParam("corpId", config.getDingTalkCorpId())
.queryParam("exclusiveLogin", config.isDingTalkExclusiveLogin())
.queryParam("exclusiveCorpId", config.getDingTalkExclusiveCorpId())
.queryParam("state", getRealState(state))
.build();
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> params = new HashMap<>();
params.put("grantType", "authorization_code");
params.put("clientId", config.getClientId());
params.put("clientSecret", config.getClientSecret());
params.put("code", authCallback.getCode());
String response = new HttpUtils(config.getHttpConfig()).post(this.source.accessToken(), JSONObject.toJSONString(params)).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
if (!accessTokenObject.containsKey("accessToken")) {
throw new AuthException(JSONObject.toJSONString(response), source);
}
return AuthToken.builder()
.accessToken(accessTokenObject.getString("accessToken"))
.refreshToken(accessTokenObject.getString("refreshToken"))
.expireIn(accessTokenObject.getIntValue("expireIn"))
.corpId(accessTokenObject.getString("corpId"))
.build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
HttpHeader header = new HttpHeader();
header.add("x-acs-dingtalk-access-token", authToken.getAccessToken());
String response = new HttpUtils(config.getHttpConfig()).get(this.source.userInfo(), null, header, false).getBody();
JSONObject object = JSONObject.parseObject(response);
authToken.setOpenId(object.getString("openId"));
authToken.setUnionId(object.getString("unionId"));
return AuthUser.builder()
.rawUserInfo(object)
.uuid(object.getString("unionId"))
.username(object.getString("nick"))
.nickname(object.getString("nick"))
.avatar(object.getString("avatarUrl"))
.snapshotUser(object.getBooleanValue("visitor"))
.token(authToken)
.source(source.toString())
.build();
}
/**
* 返回获取accessToken的url
*
* @param code 授权码
* @return 返回获取accessToken的url
*/
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("code", code)
.queryParam("clientId", config.getClientId())
.queryParam("clientSecret", config.getClientSecret())
.queryParam("grantType", "authorization_code")
.build();
}
}
@@ -34,16 +34,17 @@ public class AuthDouyinRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject userInfoObject = JSONObject.parseObject(response);
this.checkResponse(userInfoObject);
JSONObject object = userInfoObject.getJSONObject("data");
authToken.setUnionId(object.getString("union_id"));
return AuthUser.builder()
.rawUserInfo(object)
.uuid(object.getString("union_id"))
@@ -59,8 +60,8 @@ public class AuthDouyinRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
return AuthResponse.builder()
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build();
@@ -96,6 +97,7 @@ public class AuthDouyinRequest extends AuthDefaultRequest {
.openId(dataObj.getString("open_id"))
.expireIn(dataObj.getIntValue("expires_in"))
.refreshToken(dataObj.getString("refresh_token"))
.refreshTokenExpireIn(dataObj.getIntValue("refresh_expires_in"))
.scope(dataObj.getString("scope"))
.build();
}
@@ -44,7 +44,7 @@ public class AuthElemeRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> form = new HashMap<>(7);
form.put("client_id", config.getClientId());
form.put("redirect_uri", config.getRedirectUri());
@@ -66,7 +66,7 @@ public class AuthElemeRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
Map<String, Object> parameters = new HashMap<>(4);
// 获取商户账号信息的API接口名称
String action = "eleme.user.getUser";
@@ -117,7 +117,7 @@ public class AuthElemeRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
Map<String, String> form = new HashMap<>(4);
form.put("refresh_token", oldToken.getRefreshToken());
form.put("grant_type", "refresh_token");
@@ -129,7 +129,7 @@ public class AuthElemeRequest extends AuthDefaultRequest {
this.checkResponse(object);
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(object.getString("access_token"))
@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.enums.scope.AuthFacebookScope;
import me.zhyd.oauth.exception.AuthException;
@@ -11,6 +12,7 @@ import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.AuthScopeUtils;
import me.zhyd.oauth.utils.GlobalAuthUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
@@ -30,7 +32,7 @@ public class AuthFacebookRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
@@ -42,7 +44,7 @@ public class AuthFacebookRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userInfo = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
@@ -87,6 +89,16 @@ public class AuthFacebookRequest extends AuthDefaultRequest {
.build();
}
@Override
protected void checkConfig(AuthConfig config) {
super.checkConfig(config);
// facebook的回调地址必须为https的链接
if (AuthDefaultSource.FACEBOOK == source && !GlobalAuthUtils.isHttpsProtocol(config.getRedirectUri())) {
// Facebook's redirect uri must use the HTTPS protocol
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI, source);
}
}
/**
* 检查响应内容是否正确
*
@@ -66,7 +66,7 @@ public class AuthFeishuRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
JSONObject requestObject = new JSONObject();
requestObject.put("app_access_token", this.getAppAccessToken());
requestObject.put("grant_type", "authorization_code");
@@ -76,7 +76,7 @@ public class AuthFeishuRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
String response = new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
.add("Content-Type", "application/json")
@@ -98,12 +98,12 @@ public class AuthFeishuRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken authToken) {
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
JSONObject requestObject = new JSONObject();
requestObject.put("app_access_token", this.getAppAccessToken());
requestObject.put("grant_type", "refresh_token");
requestObject.put("refresh_token", authToken.getRefreshToken());
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(requestObject, this.source.refresh()))
.build();
@@ -0,0 +1,118 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import com.xkcoding.http.support.HttpHeader;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.scope.AuthFigmaScope;
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.*;
/**
* Figma登录
* @author xiangqian
* @since 1.16.6
*/
public class AuthFigmaRequest extends AuthDefaultRequest {
public AuthFigmaRequest(AuthConfig config) {
super(config, AuthDefaultSource.FIGMA);
}
public AuthFigmaRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.FIGMA, authStateCache);
}
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(super.authorize(state))
.queryParam("scope", this.getScopes(",", true, AuthScopeUtils.getDefaultScopes(AuthFigmaScope.values())))
.build();
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
HttpHeader header = new HttpHeader()
.add("content-type", "application/x-www-form-urlencoded")
.add("Authorization", "Basic " + Base64Utils.encode(config.getClientId().concat(":").concat(config.getClientSecret())));
String response = new HttpUtils(config.getHttpConfig()).post(super.accessTokenUrl(authCallback.getCode()), null, header, true).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
return AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
.refreshToken(accessTokenObject.getString("refresh_token"))
.scope(accessTokenObject.getString("scope"))
.userId(accessTokenObject.getString("user_id"))
.expireIn(accessTokenObject.getIntValue("expires_in"))
.build();
}
@Override
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
HttpHeader header = new HttpHeader().add("content-type", "application/x-www-form-urlencoded");
String response = new HttpUtils(config.getHttpConfig()).post(this.refreshTokenUrl(authToken.getRefreshToken()), null, header, false).getBody();
JSONObject dataObj = JSONObject.parseObject(response);
this.checkResponse(dataObj);
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(dataObj.getString("access_token"))
.openId(dataObj.getString("open_id"))
.expireIn(dataObj.getIntValue("expires_in"))
.refreshToken(dataObj.getString("refresh_token"))
.scope(dataObj.getString("scope"))
.build())
.build();
}
@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)
.build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
HttpHeader header = new HttpHeader().add("Authorization", "Bearer " + authToken.getAccessToken());
String response = new HttpUtils(config.getHttpConfig()).get(super.userInfoUrl(authToken), null, header, false).getBody();
JSONObject dataObj = JSONObject.parseObject(response);
this.checkResponse(dataObj);
return AuthUser.builder()
.rawUserInfo(dataObj)
.uuid(dataObj.getString("id"))
.username(dataObj.getString("handle"))
.avatar(dataObj.getString("img_url"))
.email(dataObj.getString("email"))
.token(authToken)
.source(source.toString())
.build();
}
/**
* 校验响应结果
*
* @param object 接口返回的结果
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("error")) {
throw new AuthException(object.getString("error") + ":" + object.getString("message"));
}
}
}
@@ -30,7 +30,7 @@ public class AuthGiteeRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
@@ -44,7 +44,7 @@ public class AuthGiteeRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userInfo = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
@@ -35,7 +35,7 @@ public class AuthGithubRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
Map<String, String> res = GlobalAuthUtils.parseStringToMap(response);
@@ -49,7 +49,7 @@ public class AuthGithubRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
HttpHeader header = new HttpHeader();
header.add("Authorization", "token " + authToken.getAccessToken());
String response = new HttpUtils(config.getHttpConfig()).get(UrlBuilder.fromBaseUrl(source.userInfo()).build(), null, header, false).getBody();
@@ -30,7 +30,7 @@ public class AuthGitlabRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject object = JSONObject.parseObject(response);
@@ -46,7 +46,7 @@ public class AuthGitlabRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response);
@@ -33,7 +33,7 @@ public class AuthGoogleRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
@@ -47,7 +47,7 @@ public class AuthGoogleRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
HttpHeader httpHeader = new HttpHeader();
httpHeader.add("Authorization", "Bearer " + authToken.getAccessToken());
String userInfo = new HttpUtils(config.getHttpConfig()).post(userInfoUrl(authToken), null, httpHeader).getBody();
@@ -23,10 +23,13 @@ import static me.zhyd.oauth.enums.AuthResponseStatus.SUCCESS;
/**
* 华为授权登录
*
* 当前方式未来可能被废弃,建议使用 {@link AuthHuaweiV3Request}
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.10.0
*/
@Deprecated
public class AuthHuaweiRequest extends AuthDefaultRequest {
public AuthHuaweiRequest(AuthConfig config) {
@@ -46,7 +49,7 @@ public class AuthHuaweiRequest extends AuthDefaultRequest {
* @see AuthDefaultRequest#authorize(String)
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> form = new HashMap<>(8);
form.put("grant_type", "authorization_code");
form.put("code", authCallback.getAuthorization_code());
@@ -66,12 +69,14 @@ public class AuthHuaweiRequest extends AuthDefaultRequest {
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
*/
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
Map<String, String> form = new HashMap<>(7);
form.put("nsp_ts", System.currentTimeMillis() + "");
form.put("access_token", authToken.getAccessToken());
form.put("nsp_fmt", "JS");
form.put("nsp_svc", "OpenUP.User.getInfo");
form.put("open_id", "OPENID");
// form.put("nsp_svc", "OpenUP.User.getInfo");
form.put("nsp_svc", "huawei.oauth2.user.getTokenInfo");
String response = new HttpUtils(config.getHttpConfig()).post(source.userInfo(), form, false).getBody();
JSONObject object = JSONObject.parseObject(response);
@@ -99,7 +104,7 @@ public class AuthHuaweiRequest extends AuthDefaultRequest {
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
Map<String, String> form = new HashMap<>(7);
form.put("client_id", config.getClientId());
form.put("client_secret", config.getClientSecret());
@@ -107,7 +112,7 @@ public class AuthHuaweiRequest extends AuthDefaultRequest {
form.put("grant_type", "refresh_token");
String response = new HttpUtils(config.getHttpConfig()).post(source.refresh(), form, false).getBody();
return AuthResponse.builder().code(SUCCESS.getCode()).data(getAuthToken(response)).build();
return AuthResponse.<AuthToken>builder().code(SUCCESS.getCode()).data(getAuthToken(response)).build();
}
private AuthToken getAuthToken(String response) {
@@ -0,0 +1,196 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import com.xkcoding.http.constants.Constants;
import com.xkcoding.http.support.HttpHeader;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.enums.scope.AuthHuaweiV3Scope;
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.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static me.zhyd.oauth.enums.AuthResponseStatus.SUCCESS;
/**
* 华为授权登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @since 1.16.7
*/
public class AuthHuaweiV3Request extends AuthDefaultRequest {
public AuthHuaweiV3Request(AuthConfig config) {
super(config, AuthDefaultSource.HUAWEI_V3);
}
public AuthHuaweiV3Request(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.HUAWEI_V3, authStateCache);
}
/**
* 获取access token
*
* @param authCallback 授权成功后的回调参数
* @return token
* @see AuthDefaultRequest#authorize()
* @see AuthDefaultRequest#authorize(String)
*/
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> form = new HashMap<>(8);
form.put("grant_type", "authorization_code");
form.put("code", authCallback.getCode());
form.put("client_id", config.getClientId());
form.put("client_secret", config.getClientSecret());
form.put("redirect_uri", config.getRedirectUri());
if (config.isPkce()) {
String cacheKey = this.source.getName().concat(":code_verifier:").concat(authCallback.getState());
String codeVerifier = this.authStateCache.get(cacheKey);
form.put("code_verifier", codeVerifier);
}
HttpHeader httpHeader = new HttpHeader();
httpHeader.add(Constants.CONTENT_TYPE, "application/x-www-form-urlencoded");
String response = new HttpUtils(config.getHttpConfig()).post(source.accessToken(), form, httpHeader, false).getBody();
return getAuthToken(response);
}
/**
* 使用token换取用户信息
*
* @param authToken token信息
* @return 用户信息
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
*/
@Override
public AuthUser getUserInfo(AuthToken authToken) {
String idToken = authToken.getIdToken();
if (StringUtils.isEmpty(idToken)) {
Map<String, String> form = new HashMap<>(7);
form.put("access_token", authToken.getAccessToken());
form.put("getNickName", "1");
form.put("nsp_svc", "GOpen.User.getInfo");
HttpHeader httpHeader = new HttpHeader();
httpHeader.add(Constants.CONTENT_TYPE, "application/x-www-form-urlencoded");
String response = new HttpUtils(config.getHttpConfig()).post(source.userInfo(), form, httpHeader, false).getBody();
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
return AuthUser.builder()
.rawUserInfo(object)
.uuid(object.getString("unionID"))
.username(object.getString("displayName"))
.nickname(object.getString("displayName"))
.gender(AuthUserGender.UNKNOWN)
.avatar(object.getString("headPictureURL"))
.token(authToken)
.source(source.toString())
.build();
}
String payload = new String(Base64.getUrlDecoder().decode(idToken.split("\\.")[1]), StandardCharsets.UTF_8);
JSONObject object = JSONObject.parseObject(payload);
return AuthUser.builder()
.rawUserInfo(object)
.uuid(object.getString("sub"))
.username(object.getString("name"))
.nickname(object.getString("nickname"))
.gender(AuthUserGender.UNKNOWN)
.avatar(object.getString("picture"))
.token(authToken)
.source(source.toString())
.build();
}
/**
* 刷新access token (续期)
*
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
@Override
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
Map<String, String> form = new HashMap<>(7);
form.put("client_id", config.getClientId());
form.put("client_secret", config.getClientSecret());
form.put("refresh_token", authToken.getRefreshToken());
form.put("grant_type", "refresh_token");
HttpHeader httpHeader = new HttpHeader();
httpHeader.add(Constants.CONTENT_TYPE, "application/x-www-form-urlencoded");
String response = new HttpUtils(config.getHttpConfig()).post(source.refresh(), form, httpHeader, false).getBody();
return AuthResponse.<AuthToken>builder().code(SUCCESS.getCode()).data(getAuthToken(response)).build();
}
private AuthToken getAuthToken(String response) {
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.refreshToken(object.getString("refresh_token"))
.idToken(object.getString("id_token"))
.build();
}
/**
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数,可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
String realState = getRealState(state);
UrlBuilder builder = UrlBuilder.fromBaseUrl(super.authorize(realState))
.queryParam("access_type", "offline")
.queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthHuaweiV3Scope.values())));
if (config.isPkce()) {
String cacheKey = this.source.getName().concat(":code_verifier:").concat(realState);
String codeVerifier = PkceUtil.generateCodeVerifier();
String codeChallengeMethod = "S256";
String codeChallenge = PkceUtil.generateCodeChallenge(codeChallengeMethod, codeVerifier);
builder.queryParam("code_challenge", codeChallenge)
.queryParam("code_challenge_method", codeChallengeMethod);
// 缓存 codeVerifier 十分钟
this.authStateCache.cache(cacheKey, codeVerifier, TimeUnit.MINUTES.toMillis(10));
}
return builder.build();
}
/**
* 校验响应结果
*
* @param object 接口返回的结果
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("NSP_STATUS")) {
throw new AuthException(object.getString("error"));
}
if (object.containsKey("error")) {
throw new AuthException(object.getString("sub_error") + ":" + object.getString("error_description"));
}
}
}
@@ -39,7 +39,7 @@ public class AuthJdRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> params = new HashMap<>(7);
params.put("app_key", config.getClientId());
@@ -61,7 +61,7 @@ public class AuthJdRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
UrlBuilder urlBuilder = UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("app_key", config.getClientId())
@@ -103,7 +103,7 @@ public class AuthJdRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
Map<String, String> params = new HashMap<>(7);
params.put("app_key", config.getClientId());
params.put("app_secret", config.getClientSecret());
@@ -114,7 +114,7 @@ public class AuthJdRequest extends AuthDefaultRequest {
this.checkResponse(object);
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(object.getString("access_token"))
@@ -109,8 +109,8 @@ public class AuthKujialeRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken authToken) {
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
String response = new HttpUtils(config.getHttpConfig()).post(refreshTokenUrl(authToken.getRefreshToken())).getBody();
return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(getAuthToken(response)).build();
return AuthResponse.<AuthToken>builder().code(AuthResponseStatus.SUCCESS.getCode()).data(getAuthToken(response)).build();
}
}
@@ -36,7 +36,7 @@ public class AuthLineRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> params = new HashMap<>();
params.put("grant_type", "authorization_code");
params.put("code", authCallback.getCode());
@@ -56,7 +56,7 @@ public class AuthLineRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userInfo = new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
.add("Content-Type", "application/x-www-form-urlencoded")
.add("Authorization", "Bearer ".concat(authToken.getAccessToken())), false).getBody();
@@ -88,7 +88,7 @@ public class AuthLineRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
Map<String, String> params = new HashMap<>();
params.put("grant_type", "refresh_token");
params.put("refresh_token", oldToken.getRefreshToken());
@@ -96,7 +96,7 @@ public class AuthLineRequest extends AuthDefaultRequest {
params.put("client_secret", config.getClientSecret());
String response = new HttpUtils(config.getHttpConfig()).post(source.accessToken(), params, false).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(accessTokenObject.getString("access_token"))
@@ -36,12 +36,12 @@ public class AuthLinkedinRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpHeader httpHeader = new HttpHeader();
httpHeader.add("Host", "api.linkedin.com");
@@ -34,7 +34,7 @@ public class AuthMeituanRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> form = new HashMap<>(7);
form.put("app_id", config.getClientId());
form.put("secret", config.getClientSecret());
@@ -54,7 +54,7 @@ public class AuthMeituanRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
Map<String, String> form = new HashMap<>(5);
form.put("app_id", config.getClientId());
form.put("secret", config.getClientSecret());
@@ -78,7 +78,7 @@ public class AuthMeituanRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
Map<String, String> form = new HashMap<>(7);
form.put("app_id", config.getClientId());
form.put("secret", config.getClientSecret());
@@ -90,7 +90,7 @@ public class AuthMeituanRequest extends AuthDefaultRequest {
this.checkResponse(object);
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(object.getString("access_token"))
@@ -38,7 +38,7 @@ public class AuthMiRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return getToken(accessTokenUrl(authCallback.getCode()));
}
@@ -64,7 +64,7 @@ public class AuthMiRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
// 获取用户信息
String userResponse = doGetUserInfo(authToken);
@@ -110,8 +110,8 @@ public class AuthMiRequest extends AuthDefaultRequest {
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
return AuthResponse.builder()
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(refreshTokenUrl(authToken.getRefreshToken())))
.build();
@@ -3,6 +3,9 @@ package me.zhyd.oauth.request;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.utils.GlobalAuthUtils;
/**
* 微软中国登录(世纪华联)
@@ -20,4 +23,14 @@ public class AuthMicrosoftCnRequest extends AbstractAuthMicrosoftRequest {
super(config, AuthDefaultSource.MICROSOFT_CN, authStateCache);
}
@Override
protected void checkConfig(AuthConfig config) {
super.checkConfig(config);
// 微软中国的回调地址必须为https的链接或者localhost,不允许使用http
if (AuthDefaultSource.MICROSOFT_CN == source && !GlobalAuthUtils.isHttpsProtocolOrLocalHost(config.getRedirectUri())) {
// Microsoft's redirect uri must use the HTTPS or localhost
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI, source);
}
}
}
@@ -3,12 +3,15 @@ package me.zhyd.oauth.request;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.utils.GlobalAuthUtils;
/**
* 微软登录
* update 2021-08-24 mroldx (xzfqq5201314@gmail.com)
*
* @author yangkai.shen (https://xkcoding.com)
* @update:2021-08-24 mroldx (xzfqq5201314@gmail.com)
* @since 1.5.0
*/
public class AuthMicrosoftRequest extends AbstractAuthMicrosoftRequest {
@@ -21,4 +24,14 @@ public class AuthMicrosoftRequest extends AbstractAuthMicrosoftRequest {
super(config, AuthDefaultSource.MICROSOFT, authStateCache);
}
@Override
protected void checkConfig(AuthConfig config) {
super.checkConfig(config);
// 微软的回调地址必须为https的链接或者localhost,不允许使用http
if (AuthDefaultSource.MICROSOFT == source && !GlobalAuthUtils.isHttpsProtocolOrLocalHost(config.getRedirectUri())) {
// Microsoft's redirect uri must use the HTTPS or localhost
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI, source);
}
}
}
@@ -40,7 +40,7 @@ public class AuthOktaRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String tokenUrl = accessTokenUrl(authCallback.getCode());
return getAuthToken(tokenUrl);
}
@@ -64,22 +64,22 @@ public class AuthOktaRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken authToken) {
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
if (null == authToken.getRefreshToken()) {
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.ILLEGAL_TOKEN.getCode())
.msg(AuthResponseStatus.ILLEGAL_TOKEN.getMsg())
.build();
}
String refreshUrl = refreshTokenUrl(authToken.getRefreshToken());
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getAuthToken(refreshUrl))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
HttpHeader header = new HttpHeader()
.add("Authorization", "Bearer " + authToken.getAccessToken());
String response = new HttpUtils(config.getHttpConfig()).post(userInfoUrl(authToken), null, header, false).getBody();
@@ -28,7 +28,7 @@ public class AuthOschinaRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
@@ -41,7 +41,7 @@ public class AuthOschinaRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
@@ -36,7 +36,7 @@ public class AuthPinterestRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
this.checkResponse(accessTokenObject);
@@ -47,7 +47,7 @@ public class AuthPinterestRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userinfoUrl = userInfoUrl(authToken);
// TODO: 是否需要 .setFollowRedirects(true)
String response = new HttpUtils(config.getHttpConfig()).get(userinfoUrl).getBody();
@@ -34,7 +34,7 @@ public class AuthProginnRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> params = new HashMap<>();
params.put("code", authCallback.getCode());
params.put("client_id", config.getClientId());
@@ -54,7 +54,7 @@ public class AuthProginnRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userInfo = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(userInfo);
this.checkResponse(object);
@@ -0,0 +1,100 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
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.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* QQ小程序登陆 Request 请求
* <p>
* 参照微信小程序实现
*
* @author hurentian
* @since 2024-10-08
*/
public class AuthQQMiniProgramRequest extends AuthDefaultRequest {
public AuthQQMiniProgramRequest(AuthConfig config) {
super(config, AuthDefaultSource.QQ_MINI_PROGRAM);
}
public AuthQQMiniProgramRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.QQ_MINI_PROGRAM, authStateCache);
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
// 参见 https://q.qq.com/wiki/develop/miniprogram/server/open_port/port_login.html#code2session 文档
// 使用 code 获取对应的 openIdunionId 等字段
String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode())).getBody();
JSCode2SessionResponse accessTokenObject = JSONObject.parseObject(response, JSCode2SessionResponse.class);
assert accessTokenObject != null;
checkResponse(accessTokenObject);
// 拼装结果
return AuthToken.builder()
.openId(accessTokenObject.getOpenid())
.unionId(accessTokenObject.getUnionId())
.accessToken(accessTokenObject.getSessionKey())
.build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
// 参见 https://q.qq.com/wiki/develop/game/API/open-port/user-info.html#qq-getuserinfo 文档
// 如果需要用户信息需要在小程序调用函数后传给后端
return AuthUser.builder()
.username("")
.nickname("")
.avatar("")
.uuid(authToken.getOpenId())
.token(authToken)
.source(source.toString())
.build();
}
/**
* 检查响应内容是否正确
*
* @param response 请求响应内容
*/
private void checkResponse(JSCode2SessionResponse response) {
if (response.getErrorCode() != 0) {
throw new AuthException(response.getErrorCode(), response.getErrorMsg());
}
}
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("appid", config.getClientId())
.queryParam("secret", config.getClientSecret())
.queryParam("js_code", code)
.queryParam("grant_type", "authorization_code")
.build();
}
@Data
@SuppressWarnings("SpellCheckingInspection")
private static class JSCode2SessionResponse {
@JSONField(name = "errcode")
private int errorCode;
@JSONField(name = "errmsg")
private String errorMsg;
@JSONField(name = "session_key")
private String sessionKey;
private String openid;
@JSONField(name = "unionid")
private String unionId;
}
}
@@ -33,19 +33,19 @@ public class AuthQqRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doGetAuthorizationCode(authCallback.getCode());
return getAuthToken(response);
}
@Override
public AuthResponse refresh(AuthToken authToken) {
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
String response = new HttpUtils(config.getHttpConfig()).get(refreshTokenUrl(authToken.getRefreshToken())).getBody();
return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(getAuthToken(response)).build();
return AuthResponse.<AuthToken>builder().code(AuthResponseStatus.SUCCESS.getCode()).data(getAuthToken(response)).build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String openId = this.getOpenId(authToken);
String response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response);
@@ -38,12 +38,12 @@ public class AuthRenrenRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject userObj = JSONObject.parseObject(response).getJSONObject("response");
@@ -60,8 +60,8 @@ public class AuthRenrenRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken authToken) {
return AuthResponse.builder()
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
return AuthResponse.<AuthToken>builder()
.code(SUCCESS.getCode())
.data(getToken(this.refreshTokenUrl(authToken.getRefreshToken())))
.build();
@@ -5,6 +5,7 @@ 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;
/**
* JustAuth {@code Request}公共接口所有平台的{@code Request}都需要实现该接口
@@ -43,13 +44,32 @@ public interface AuthRequest {
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
/**
* 获取access token
*
* @param authCallback 授权成功后的回调参数
* @return token
* @see AuthDefaultRequest#authorize()
* @see AuthDefaultRequest#authorize(String)
*/
AuthToken getAccessToken(AuthCallback authCallback);
/**
* 使用token换取用户信息
*
* @param authToken token信息
* @return 用户信息
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
*/
AuthUser getUserInfo(AuthToken authToken);
/**
* 第三方登录
*
* @param authCallback 用于接收回调参数的实体
* @return 返回登录成功后的用户信息
*/
default AuthResponse login(AuthCallback authCallback) {
default AuthResponse<AuthUser> login(AuthCallback authCallback) {
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
@@ -69,7 +89,7 @@ public interface AuthRequest {
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
default AuthResponse refresh(AuthToken authToken) {
default AuthResponse<AuthToken> refresh(AuthToken authToken) {
throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED);
}
}
@@ -35,7 +35,7 @@ public class AuthSlackRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
HttpHeader header = new HttpHeader()
.add("Content-Type", "application/x-www-form-urlencoded");
String response = new HttpUtils(config.getHttpConfig())
@@ -51,7 +51,7 @@ public class AuthSlackRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
HttpHeader header = new HttpHeader()
.add("Content-Type", "application/x-www-form-urlencoded")
.add("Authorization", "Bearer ".concat(authToken.getAccessToken()));
@@ -37,7 +37,7 @@ public class AuthStackOverflowRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String accessTokenUrl = accessTokenUrl(authCallback.getCode());
Map<String, String> form = MapUtil.parseStringToMap(accessTokenUrl, false);
HttpHeader httpHeader = new HttpHeader();
@@ -54,7 +54,7 @@ public class AuthStackOverflowRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userInfoUrl = UrlBuilder.fromBaseUrl(this.source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("site", "stackoverflow")
@@ -33,7 +33,7 @@ public class AuthTaobaoRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder().accessCode(authCallback.getCode()).build();
}
@@ -58,7 +58,7 @@ public class AuthTaobaoRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String response = doPostAuthorizationCode(authToken.getAccessCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
if (accessTokenObject.containsKey("error")) {
@@ -79,11 +79,11 @@ public class AuthTaobaoRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
String tokenUrl = refreshTokenUrl(oldToken.getRefreshToken());
String response = new HttpUtils(config.getHttpConfig()).post(tokenUrl).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getAuthToken(accessTokenObject))
.build();
@@ -38,7 +38,7 @@ public class AuthTeambitionRequest extends AuthDefaultRequest {
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> form = new HashMap<>(7);
form.put("client_id", config.getClientId());
form.put("client_secret", config.getClientSecret());
@@ -57,7 +57,7 @@ public class AuthTeambitionRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
HttpHeader httpHeader = new HttpHeader();
@@ -87,7 +87,7 @@ public class AuthTeambitionRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
String uid = oldToken.getUid();
String refreshToken = oldToken.getRefreshToken();
@@ -99,7 +99,7 @@ public class AuthTeambitionRequest extends AuthDefaultRequest {
this.checkResponse(refreshTokenObject);
return AuthResponse.builder()
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.accessToken(refreshTokenObject.getString("access_token"))
@@ -29,7 +29,7 @@ public class AuthToutiaoRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doGetAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
@@ -43,7 +43,7 @@ public class AuthToutiaoRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String userResponse = doGetUserInfo(authToken);
JSONObject userProfile = JSONObject.parseObject(userResponse);
@@ -69,9 +69,7 @@ public class AuthTwitterRequest extends AuthDefaultRequest {
HttpHeader httpHeader = new HttpHeader();
httpHeader.add("Authorization", header);
httpHeader.add("User-Agent", "themattharris' HTTP Client");
httpHeader.add("Host", "api.twitter.com");
httpHeader.add("Accept", "*/*");
httpHeader.add("User-Agent", "'JustAuth' HTTP Client Simple-Http");
String requestToken = new HttpUtils(config.getHttpConfig()).post(baseUrl, null, httpHeader).getBody();
Map<String, String> res = MapUtil.parseStringToMap(requestToken, false);
@@ -90,7 +88,7 @@ public class AuthTwitterRequest extends AuthDefaultRequest {
* @return access token
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> oauthParams = buildOauthParams();
oauthParams.put("oauth_token", authCallback.getOauth_token());
oauthParams.put("oauth_verifier", authCallback.getOauth_verifier());
@@ -117,7 +115,7 @@ public class AuthTwitterRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
Map<String, String> queryParams = new HashMap<>(5);
queryParams.put("include_entities", Boolean.toString(true));
queryParams.put("include_email", Boolean.toString(true));
@@ -31,6 +31,7 @@ public class AuthWeChatEnterpriseQrcodeRequest extends AbstractAuthWeChatEnterpr
.queryParam("agentid", config.getAgentId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("state", getRealState(state))
.queryParam("lang", config.getLang())
.build();
}
}
@@ -0,0 +1,51 @@
package me.zhyd.oauth.request;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.utils.GlobalAuthUtils;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* <p>
* 新版企业微信 Web 登录参考 <a href="https://developer.work.weixin.qq.com/document/path/98152">https://developer.work.weixin.qq.com/document/path/98152</a>
* </p>
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @since 1.16.7
*/
public class AuthWeChatEnterpriseQrcodeV2Request extends AbstractAuthWeChatEnterpriseRequest {
public AuthWeChatEnterpriseQrcodeV2Request(AuthConfig config) {
super(config, AuthDefaultSource.WECHAT_ENTERPRISE_V2);
}
public AuthWeChatEnterpriseQrcodeV2Request(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.WECHAT_ENTERPRISE_V2, authStateCache);
}
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("login_type", config.getLoginType())
// 登录类型为企业自建应用/服务商代开发应用时填企业 CorpID第三方登录时填登录授权 SuiteID
.queryParam("appid", config.getClientId())
// 企业自建应用/服务商代开发应用 AgentID当login_type=CorpApp时填写
.queryParam("agentid", config.getAgentId())
.queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
.queryParam("state", getRealState(state))
.queryParam("lang", config.getLang())
.build()
.concat("#wechat_redirect");
}
@Override
protected void checkConfig(AuthConfig config) {
super.checkConfig(config);
if ("CorpApp".equals(config.getLoginType()) && StringUtils.isEmpty(config.getAgentId())) {
throw new AuthException(AuthResponseStatus.ILLEGAL_WECHAT_AGENT_ID, source);
}
}
}
@@ -41,14 +41,14 @@ public class AuthWeChatEnterpriseThirdQrcodeRequest extends AbstractAuthWeChatEn
}
@Override
public AuthResponse login(AuthCallback authCallback) {
public AuthResponse<AuthUser> login(AuthCallback authCallback) {
try {
if (!config.isIgnoreCheckState()) {
AuthChecker.checkState(authCallback.getState(), source, authStateCache);
}
AuthToken authToken = this.getAccessToken(authCallback);
AuthUser user = this.getUserInfo(authToken);
return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build();
return AuthResponse.<AuthUser>builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build();
} catch (Exception e) {
Log.error("Failed to login with oauth authorization.", e);
return this.responseError(e);
@@ -56,13 +56,14 @@ public class AuthWeChatEnterpriseThirdQrcodeRequest extends AbstractAuthWeChatEn
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
try {
String response = doGetAuthorizationCode(accessTokenUrl());
JSONObject object = this.checkResponse(response);
AuthToken authToken = AuthToken.builder()
.accessToken(object.getString("provider_access_token"))
.expireIn(object.getIntValue("expires_in"))
.code(authCallback.getCode())
.build();
return authToken;
} catch (Exception e) {
@@ -81,7 +82,7 @@ public class AuthWeChatEnterpriseThirdQrcodeRequest extends AbstractAuthWeChatEn
/**
* 获取token的URL
*
* @return
* @return accessTokenUrl
*/
protected String accessTokenUrl() {
return UrlBuilder.fromBaseUrl(source.accessToken())
@@ -89,7 +90,7 @@ public class AuthWeChatEnterpriseThirdQrcodeRequest extends AbstractAuthWeChatEn
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
JSONObject response = this.checkResponse(doGetUserInfo(authToken));
return AuthUser.builder()
.rawUserInfo(response)
@@ -5,6 +5,7 @@ import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.scope.AuthWeChatEnterpriseWebScope;
import me.zhyd.oauth.utils.AuthScopeUtils;
import me.zhyd.oauth.utils.GlobalAuthUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
@@ -28,7 +29,8 @@ public class AuthWeChatEnterpriseWebRequest extends AbstractAuthWeChatEnterprise
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("appid", config.getClientId())
.queryParam("redirect_uri", config.getRedirectUri())
.queryParam("agentid", config.getAgentId())
.queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
.queryParam("response_type", "code")
.queryParam("scope", this.getScopes(",", false, AuthScopeUtils.getDefaultScopes(AuthWeChatEnterpriseWebScope.values())))
.queryParam("state", getRealState(state).concat("#wechat_redirect"))
@@ -12,10 +12,7 @@ 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.AuthScopeUtils;
import me.zhyd.oauth.utils.GlobalAuthUtils;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import me.zhyd.oauth.utils.*;
/**
* 微信公众平台登录
@@ -39,25 +36,34 @@ public class AuthWeChatMpRequest extends AuthDefaultRequest {
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String openId = authToken.getOpenId();
String scope = authToken.getScope();
if (!StringUtils.isEmpty(scope) && !scope.contains("snsapi_userinfo")) {
return AuthUser.builder()
.rawUserInfo(JSONObject.parseObject(JSONObject.toJSONString(authToken)))
.uuid(openId)
.snapshotUser(authToken.isSnapshotUser())
.token(authToken)
.source(source.toString())
.build();
}
String response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city"));
if (object.containsKey("unionid")) {
authToken.setUnionId(object.getString("unionid"));
}
return AuthUser.builder()
.rawUserInfo(object)
.username(object.getString("nickname"))
@@ -65,6 +71,7 @@ public class AuthWeChatMpRequest extends AuthDefaultRequest {
.avatar(object.getString("headimgurl"))
.location(location)
.uuid(openId)
.snapshotUser(authToken.isSnapshotUser())
.gender(AuthUserGender.getWechatRealGender(object.getString("sex")))
.token(authToken)
.source(source.toString())
@@ -72,8 +79,8 @@ public class AuthWeChatMpRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
return AuthResponse.builder()
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build();
@@ -108,6 +115,7 @@ public class AuthWeChatMpRequest extends AuthDefaultRequest {
.expireIn(accessTokenObject.getIntValue("expires_in"))
.openId(accessTokenObject.getString("openid"))
.scope(accessTokenObject.getString("scope"))
.snapshotUser(accessTokenObject.getIntValue("is_snapshotuser") == 1)
.build();
}
@@ -36,12 +36,12 @@ public class AuthWeChatOpenRequest extends AuthDefaultRequest {
* @return 所有信息
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return this.getToken(accessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String openId = authToken.getOpenId();
String response = doGetUserInfo(authToken);
@@ -69,8 +69,8 @@ public class AuthWeChatOpenRequest extends AuthDefaultRequest {
}
@Override
public AuthResponse refresh(AuthToken oldToken) {
return AuthResponse.builder()
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken())))
.build();
@@ -0,0 +1,100 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
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.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 微信小程序授权登录
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @author yudaocode
* @version 1.0.0
* @since 1.0.0
*/
public class AuthWechatMiniProgramRequest extends AuthDefaultRequest {
public AuthWechatMiniProgramRequest(AuthConfig config) {
super(config, AuthDefaultSource.WECHAT_MINI_PROGRAM);
}
public AuthWechatMiniProgramRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.WECHAT_MINI_PROGRAM, authStateCache);
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
// 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档
// 使用 code 获取对应的 openIdunionId 等字段
String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode())).getBody();
JSCode2SessionResponse accessTokenObject = JSONObject.parseObject(response, JSCode2SessionResponse.class);
assert accessTokenObject != null;
checkResponse(accessTokenObject);
// 拼装结果
return AuthToken.builder()
.openId(accessTokenObject.getOpenid())
.unionId(accessTokenObject.getUnionId())
.accessToken(accessTokenObject.getSessionKey())
.build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
// 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档
// 如果需要用户信息需要在小程序调用函数后传给后端
return AuthUser.builder()
.username("")
.nickname("")
.avatar("")
.uuid(authToken.getOpenId())
.token(authToken)
.source(source.toString())
.build();
}
/**
* 检查响应内容是否正确
*
* @param response 请求响应内容
*/
private void checkResponse(JSCode2SessionResponse response) {
if (response.getErrorCode() != 0) {
throw new AuthException(response.getErrorCode(), response.getErrorMsg());
}
}
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("appid", config.getClientId())
.queryParam("secret", config.getClientSecret())
.queryParam("js_code", code)
.queryParam("grant_type", "authorization_code")
.build();
}
@Data
@SuppressWarnings("SpellCheckingInspection")
private static class JSCode2SessionResponse {
@JSONField(name = "errcode")
private int errorCode;
@JSONField(name = "errmsg")
private String errorMsg;
@JSONField(name = "session_key")
private String sessionKey;
private String openid;
@JSONField(name = "unionid")
private String unionId;
}
}
@@ -33,7 +33,7 @@ public class AuthWeiboRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
if (accessTokenObject.containsKey("error")) {
@@ -48,7 +48,7 @@ public class AuthWeiboRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
String uid = authToken.getUid();
String oauthParam = String.format("uid=%s&access_token=%s", uid, accessToken);
@@ -42,7 +42,7 @@ public class AuthXmlyRequest extends AuthDefaultRequest {
* @see AuthDefaultRequest#authorize(String)
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> map = new HashMap<>(9);
map.put("code", authCallback.getCode());
map.put("client_id", config.getClientId());
@@ -63,21 +63,6 @@ public class AuthChecker {
if (!GlobalAuthUtils.isHttpProtocol(redirectUri) && !GlobalAuthUtils.isHttpsProtocol(redirectUri)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI, source);
}
// facebook的回调地址必须为https的链接
if (AuthDefaultSource.FACEBOOK == source && !GlobalAuthUtils.isHttpsProtocol(redirectUri)) {
// Facebook's redirect uri must use the HTTPS protocol
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI, source);
}
// 微软的回调地址必须为https的链接或者localhost,不允许使用http
if (AuthDefaultSource.MICROSOFT == source && !GlobalAuthUtils.isHttpsProtocolOrLocalHost(redirectUri)) {
// Microsoft's redirect uri must use the HTTPS or localhost
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI, source);
}
// 微软中国的回调地址必须为https的链接或者localhost,不允许使用http
if (AuthDefaultSource.MICROSOFT_CN == source && !GlobalAuthUtils.isHttpsProtocolOrLocalHost(redirectUri)) {
// Microsoft's redirect uri must use the HTTPS or localhost
throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI, source);
}
}
/**
@@ -95,7 +80,7 @@ public class AuthChecker {
return;
}
String code = callback.getCode();
if (source == AuthDefaultSource.HUAWEI) {
if (StringUtils.isEmpty(code) && source == AuthDefaultSource.HUAWEI) {
code = callback.getAuthorization_code();
}
if (StringUtils.isEmpty(code)) {
@@ -82,6 +82,10 @@ public class AuthRequestBuilderTest {
.stackOverflowKey("asd")
.deviceId("asd")
.clientOsType(3)
.kid("kid")
.teamId("teamid")
.ignoreCheckState(true)
.ignoreCheckRedirectUri(true)
.build();
for (AuthDefaultSource value : AuthDefaultSource.values()) {
@@ -97,6 +101,18 @@ public class AuthRequestBuilderTest {
System.out.println(authRequest.authorize(AuthStateUtils.createState()));
continue;
}
case WECHAT_MINI_PROGRAM: {
// 小程序不支持获取调用 authorize
AuthRequest authRequest = new AuthWechatMiniProgramRequest(config);
System.out.println(value.getTargetClass());
continue;
}
case QQ_MINI_PROGRAM: {
// 小程序不支持获取调用 authorize
AuthRequest authRequest = new AuthQQMiniProgramRequest(config);
System.out.println(value.getTargetClass());
continue;
}
default:
AuthRequest authRequest = AuthRequestBuilder.builder()
.source(value.getName())
@@ -16,7 +16,7 @@ public class AuthUserTest {
.nickname("test")
.build();
String json = JSON.toJSONString(user);
Assert.assertEquals(json, "{\"nickname\":\"test\"}");
Assert.assertEquals(json, "{\"nickname\":\"test\",\"snapshotUser\":false}");
}
@@ -36,7 +36,7 @@ public class AuthExtendRequest extends AuthDefaultRequest {
* @see AuthDefaultRequest#authorize(String)
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder()
.openId("openId")
.expireIn(1000)
@@ -56,7 +56,7 @@ public class AuthExtendRequest extends AuthDefaultRequest {
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
*/
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
return AuthUser.builder()
.username("test")
.nickname("test")
@@ -87,8 +87,8 @@ public class AuthExtendRequest extends AuthDefaultRequest {
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
return AuthResponse.builder()
public AuthResponse<AuthToken> refresh(AuthToken authToken) {
return AuthResponse.<AuthToken>builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(AuthToken.builder()
.openId("openId")
@@ -45,10 +45,10 @@ public class AuthExtendRequestTest {
.code("code")
.state(state)
.build();
AuthResponse response = request.login(callback);
AuthResponse<AuthUser> response = request.login(callback);
Assert.assertNotNull(response);
AuthUser user = (AuthUser) response.getData();
AuthUser user = response.getData();
Assert.assertNotNull(user);
System.out.println(JSON.toJSONString(user));
}
@@ -74,7 +74,7 @@ public class AuthExtendRequestTest {
.redirectUri("http://redirectUri")
.build());
AuthResponse response = request.refresh(AuthToken.builder().build());
AuthResponse<AuthToken> response = request.refresh(AuthToken.builder().build());
Assert.assertNotNull(response);
System.out.println(JSON.toJSONString(response.getData()));
@@ -0,0 +1,20 @@
package me.zhyd.oauth.request;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.junit.Test;
import static org.junit.Assert.*;
public class AuthWeChatEnterpriseWebRequestTest {
@Test
public void authorize() {
AuthRequest request = new AuthWeChatEnterpriseWebRequest(AuthConfig.builder()
.clientId("a")
.clientSecret("a")
.redirectUri("https://www.justauth.cn")
.build());
System.out.println(request.authorize(AuthStateUtils.createState()));
}
}