From 9fd2b9b9195e23513855500ca90809d062845f2f Mon Sep 17 00:00:00 2001 From: "yadong.zhang" Date: Fri, 1 Jan 2021 17:48:49 +0800 Subject: [PATCH] =?UTF-8?q?:egg:=20=E6=AD=A3=E5=BC=8F=E5=90=AF=E7=94=A8?= =?UTF-8?q?=E9=A3=9E=E4=B9=A6=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOGS.md | 3 + .../zhyd/oauth/config/AuthDefaultSource.java | 17 +-- .../java/me/zhyd/oauth/model/AuthToken.java | 1 + .../zhyd/oauth/request/AuthFeishuRequest.java | 101 ++++++++++++------ 4 files changed, 81 insertions(+), 41 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 296e369..1957300 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -3,6 +3,9 @@ ### 2021/1/1 - 发布 v1.15.9 +- 新增 + - 修复并正式启用 飞书 平台的第三方登录 + - AuthToken 类中新增 `refreshTokenExpireIn` 记录 refresh token 的有效期 - PR - 合并 [Github #101](https://gitee.com/yadong.zhang/JustAuth/pulls/101):支持喜马拉雅登录 - 合并 [Github #105](https://gitee.com/yadong.zhang/JustAuth/pulls/105):支持企业微信网页授权登录 diff --git a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java index d2cd990..2accd42 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java @@ -141,7 +141,7 @@ public enum AuthDefaultSource implements AuthSource { }, /** * Coding, - * + *

* 参考 https://help.coding.net/docs/project/open/oauth.html#%E7%94%A8%E6%88%B7%E6%8E%88%E6%9D%83 中的说明, * 新版的 coding API 地址需要传入用户团队名,这儿使用动态参数,方便在 request 中使用 */ @@ -730,30 +730,31 @@ public enum AuthDefaultSource implements AuthSource { }, /** - * 飞书 - * 注意:该平台暂时存在问题,请不要使用。待修复完成后会重新发版 + * 飞书平台,企业自建应用授权登录,原逻辑由 beacon 集成于 1.14.0 版,但最新的飞书 api 已修改,并且飞书平台一直为 {@code Deprecated} 状态 + *

+ * 所以,最终修改该平台的实际发布版本为 1.15.9 * - * @since 1.14.0 + * @since 1.15.9 */ FEISHU { @Override public String authorize() { - return "https://open.feishu.cn/connect/qrconnect/page/sso/"; + return "https://open.feishu.cn/open-apis/authen/v1/index"; } @Override public String accessToken() { - return "https://open.feishu.cn/connect/qrconnect/oauth2/access_token/"; + return "https://open.feishu.cn/open-apis/authen/v1/access_token"; } @Override public String userInfo() { - return "https://open.feishu.cn/connect/qrconnect/oauth2/user_info/"; + return "https://open.feishu.cn/open-apis/authen/v1/user_info"; } @Override public String refresh() { - return "https://open.feishu.cn/connect/qrconnect/oauth2/access_token/"; + return "https://open.feishu.cn/open-apis/authen/v1/refresh_access_token"; } }, /** diff --git a/src/main/java/me/zhyd/oauth/model/AuthToken.java b/src/main/java/me/zhyd/oauth/model/AuthToken.java index 99f8776..e729b1d 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthToken.java +++ b/src/main/java/me/zhyd/oauth/model/AuthToken.java @@ -19,6 +19,7 @@ public class AuthToken implements Serializable { private String accessToken; private int expireIn; private String refreshToken; + private int refreshTokenExpireIn; private String uid; private String openId; private String accessCode; diff --git a/src/main/java/me/zhyd/oauth/request/AuthFeishuRequest.java b/src/main/java/me/zhyd/oauth/request/AuthFeishuRequest.java index 6556724..de8309f 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthFeishuRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthFeishuRequest.java @@ -3,9 +3,11 @@ package me.zhyd.oauth.request; import com.alibaba.fastjson.JSON; 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.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthResponse; @@ -13,40 +15,63 @@ import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.utils.GlobalAuthUtils; import me.zhyd.oauth.utils.HttpUtils; +import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.UrlBuilder; /** - * 注意:该平台暂时存在问题,请不要使用。待修复完成后会重新发版by yadong.zhang + * 飞书平台,企业自建应用授权登录,原逻辑由 beacon 集成于 1.14.0 版,但最新的飞书 api 已修改,并且飞书平台一直为 {@code Deprecated} 状态 + *

+ * 所以,最终修改该平台的实际发布版本为 1.15.9 * * @author beacon - * @since 1.14.0 + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) 重构业务逻辑 20210101 + * @since 1.15.9 */ -@Deprecated public class AuthFeishuRequest extends AuthDefaultRequest { public AuthFeishuRequest(AuthConfig config) { super(config, AuthDefaultSource.FEISHU); - throw new AuthException(AuthResponseStatus.FAILURE); } - @Override - protected AuthToken getAccessToken(AuthCallback authCallback) { + public AuthFeishuRequest(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthDefaultSource.FEISHU, authStateCache); + } + + /** + * 获取 app_access_token(企业自建应用) + *

+ * Token 有效期为 2 小时,在此期间调用该接口 token 不会改变。当 token 有效期小于 30 分的时候,再次请求获取 token 的时候, + * 会生成一个新的 token,与此同时老的 token 依然有效。 + * + * @return appAccessToken + */ + private String getAppAccessToken() { + String cacheKey = this.source.getName().concat(":app_access_token:").concat(config.getClientId()); + String cacheAppAccessToken = this.authStateCache.get(cacheKey); + if (StringUtils.isNotEmpty(cacheAppAccessToken)) { + return cacheAppAccessToken; + } + String url = "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/"; JSONObject requestObject = new JSONObject(); requestObject.put("app_id", config.getClientId()); requestObject.put("app_secret", config.getClientSecret()); - requestObject.put("grant_type", "authorization_code"); - requestObject.put("code", authCallback.getCode()); - String response = new HttpUtils(config.getHttpConfig()).post(source.accessToken(), requestObject.toJSONString(), new HttpHeader() + String response = new HttpUtils(config.getHttpConfig()).post(url, requestObject.toJSONString(), new HttpHeader() .add("Content-Type", "application/json")); JSONObject jsonObject = JSON.parseObject(response); this.checkResponse(jsonObject); - return AuthToken.builder() - .accessToken(jsonObject.getString("access_token")) - .refreshToken(jsonObject.getString("refresh_token")) - .expireIn(jsonObject.getIntValue("expires_in")) - .tokenType(jsonObject.getString("token_type")) - .openId(jsonObject.getString("open_id")) - .build(); + String appAccessToken = jsonObject.getString("app_access_token"); + // 缓存 app access token + this.authStateCache.cache(cacheKey, appAccessToken, jsonObject.getLongValue("expire") * 1000); + return appAccessToken; + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + JSONObject requestObject = new JSONObject(); + requestObject.put("app_access_token", this.getAppAccessToken()); + requestObject.put("grant_type", "authorization_code"); + requestObject.put("code", authCallback.getCode()); + return getToken(requestObject, this.source.accessToken()); } @@ -57,39 +82,49 @@ public class AuthFeishuRequest extends AuthDefaultRequest { .add("Content-Type", "application/json") .add("Authorization", "Bearer " + accessToken), false); JSONObject object = JSON.parseObject(response); + this.checkResponse(object); + JSONObject data = object.getJSONObject("data"); return AuthUser.builder() .rawUserInfo(object) - .avatar(object.getString("AvatarUrl")) - .username(object.getString("Mobile")) - .email(object.getString("Email")) - .nickname("Name") + .uuid(data.getString("union_id")) + .username(data.getString("name")) + .nickname(data.getString("name")) + .avatar(data.getString("avatar_url")) + .email(data.getString("email")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(source.toString()) .build(); } @Override public AuthResponse refresh(AuthToken authToken) { JSONObject requestObject = new JSONObject(); - requestObject.put("app_id", config.getClientId()); - requestObject.put("app_secret", config.getClientSecret()); + requestObject.put("app_access_token", this.getAppAccessToken()); requestObject.put("grant_type", "refresh_token"); requestObject.put("refresh_token", authToken.getRefreshToken()); - String response = new HttpUtils(config.getHttpConfig()).post(source.refresh(), requestObject.toJSONString(), new HttpHeader() - .add("Content-Type", "application/json")); - JSONObject jsonObject = JSON.parseObject(response); - this.checkResponse(jsonObject); return AuthResponse.builder() .code(AuthResponseStatus.SUCCESS.getCode()) - .data(AuthToken.builder() - .accessToken(jsonObject.getString("access_token")) - .refreshToken(jsonObject.getString("refresh_token")) - .expireIn(jsonObject.getIntValue("expires_in")) - .tokenType(jsonObject.getString("token_type")) - .openId(jsonObject.getString("open_id")) - .build()) + .data(getToken(requestObject, this.source.refresh())) .build(); } + private AuthToken getToken(JSONObject param, String url) { + String response = new HttpUtils(config.getHttpConfig()).post(url, param.toJSONString(), new HttpHeader() + .add("Content-Type", "application/json")); + JSONObject jsonObject = JSON.parseObject(response); + this.checkResponse(jsonObject); + JSONObject data = jsonObject.getJSONObject("data"); + return AuthToken.builder() + .accessToken(data.getString("access_token")) + .refreshToken(data.getString("refresh_token")) + .expireIn(data.getIntValue("expires_in")) + .tokenType(data.getString("token_type")) + .openId(data.getString("open_id")) + .build(); + } + @Override public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) -- GitLab