diff --git a/example.md b/example.md index fb5f25c702900936dc1e2fc71fe4030b99147dfe..8579a1282e41322318caf1ea9fca35d609f974a1 100644 --- a/example.md +++ b/example.md @@ -84,6 +84,8 @@ _注:非全部平台,部分平台可能不存在图例_ 暂无 +_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_ + #### 授权Pinterest ![授权Pinterest](https://images.gitee.com/uploads/images/2019/0718/155012_6290f500_784199.jpeg "在这里输入图片标题") @@ -92,4 +94,10 @@ _注:非全部平台,部分平台可能不存在图例_ ![授权Renre](https://images.gitee.com/uploads/images/2019/0718/155035_8e26c10a_784199.jpeg "在这里输入图片标题") -_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_ \ No newline at end of file +#### 授权Stack Overflow + +暂无 + +#### 授权Twitter + +暂无 diff --git a/src/main/java/me/zhyd/oauth/config/AuthConfig.java b/src/main/java/me/zhyd/oauth/config/AuthConfig.java index 8b1f7da07eebfb75e1e6a16624e7e13b7a42b1a1..57b965693e6669ae9886f94e279fa7ccb7113964 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthConfig.java +++ b/src/main/java/me/zhyd/oauth/config/AuthConfig.java @@ -51,4 +51,11 @@ public class AuthConfig { * 1.8.0版本新增参数 */ private String state; + + /** + * Stack Overflow Key + *

+ * 1.9.0版本新增参数 + */ + private String stackOverflowKey; } diff --git a/src/main/java/me/zhyd/oauth/config/AuthSource.java b/src/main/java/me/zhyd/oauth/config/AuthSource.java index 029259f2c0020dfd4036325ebb9e8159ab8f5c6c..159ce0ed3bd3d39fb6d79a9f3ebe45d99781b147 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthSource.java @@ -489,6 +489,26 @@ public enum AuthSource { public String userInfo() { return "https://api.pinterest.com/v1/me"; } + }, + + /** + * Stack Overflow + */ + STACK_OVERFLOW { + @Override + public String authorize() { + return "https://stackoverflow.com/oauth"; + } + + @Override + public String accessToken() { + return "https://stackoverflow.com/oauth/access_token/json"; + } + + @Override + public String userInfo() { + return "https://api.stackexchange.com/2.2/me"; + } }; /** diff --git a/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java b/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..5f002e78c3ddd72123c544b83088eb62a36d0312 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java @@ -0,0 +1,78 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.model.AuthUserGender; +import me.zhyd.oauth.url.AuthStackOverflowUrlBuilder; +import me.zhyd.oauth.url.entity.AuthUserInfoEntity; + +import java.util.HashMap; +import java.util.Map; + +import static me.zhyd.oauth.config.AuthSource.STACK_OVERFLOW; + +/** + * Stack Overflow登录 + * + * @author hongwei.peng (pengisgood(at)gmail(dot)com) + * @version 1.9.0 + * @since 1.9.0 + */ +public class AuthStackOverflowRequest extends AuthDefaultRequest { + + public AuthStackOverflowRequest(AuthConfig config) { + super(config, STACK_OVERFLOW, new AuthStackOverflowUrlBuilder()); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + String accessTokenUrl = this.urlBuilder.getAccessTokenUrl(authCallback.getCode()); + HttpResponse response = HttpRequest.post(accessTokenUrl) + .contentType("application/x-www-form-urlencoded") + .form(buildBody(accessTokenUrl)) + .execute(); + JSONObject accessTokenObject = JSONObject.parseObject(response.body()); + if (!response.isOk()) { + throw new AuthException("Unable to get token from Stack Overflow using code [" + authCallback.getCode() + "]: " + accessTokenObject); + } + + return AuthToken.builder() + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires")) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String accessToken = authToken.getAccessToken(); + HttpResponse response = HttpRequest.get(this.urlBuilder.getUserInfoUrl(AuthUserInfoEntity.builder() + .accessToken(accessToken) + .build())).execute(); + JSONObject userObj = JSONObject.parseObject(response.body()).getJSONArray("items").getJSONObject(0); + + return AuthUser.builder() + .uuid(userObj.getString("user_id")) + .avatar(userObj.getString("profile_image")) + .location(userObj.getString("location")) + .nickname(userObj.getString("display_name")) + .blog(userObj.getString("website_url")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(STACK_OVERFLOW) + .build(); + } + + private Map buildBody(String accessTokenUrl) { + Map paramMap = new HashMap<>(); + HttpUtil.decodeParamMap(accessTokenUrl, "UTF-8").forEach(paramMap::put); + return paramMap; + } + +} diff --git a/src/main/java/me/zhyd/oauth/url/AuthStackOverflowUrlBuilder.java b/src/main/java/me/zhyd/oauth/url/AuthStackOverflowUrlBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..1498e98b7e709f6e8e1ad46d633c1b6b6db3c92b --- /dev/null +++ b/src/main/java/me/zhyd/oauth/url/AuthStackOverflowUrlBuilder.java @@ -0,0 +1,48 @@ +package me.zhyd.oauth.url; + +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthResponseStatus; +import me.zhyd.oauth.url.entity.AuthUserInfoEntity; + +import java.text.MessageFormat; + +import static me.zhyd.oauth.config.AuthSource.STACK_OVERFLOW; + +/** + * Stack Overflow相关的URL构建类 + * + * @author hongwei.peng (pengisgood(at)gmail(dot)com) + * @version 1.9.0 + * @since 1.9.0 + */ +public class AuthStackOverflowUrlBuilder extends AuthDefaultUrlBuilder { + + private static final String SO_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&redirect_uri={3}&code={4}"; + private static final String SO_USER_INFO_PATTERN = "{0}?access_token={1}&site=stackoverflow&key={2}"; + private static final String SO_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}"; + + @Override + public String getAccessTokenUrl(String code) { + return MessageFormat.format(SO_ACCESS_TOKEN_PATTERN, STACK_OVERFLOW.accessToken(), config.getClientId(), config.getClientSecret(), config.getRedirectUri(), code); + } + + @Override + public String getUserInfoUrl(AuthUserInfoEntity userInfoEntity) { + return MessageFormat.format(SO_USER_INFO_PATTERN, STACK_OVERFLOW.userInfo(), userInfoEntity.getAccessToken(), config.getStackOverflowKey()); + } + + @Override + public String getAuthorizeUrl() { + return MessageFormat.format(SO_AUTHORIZE_PATTERN, STACK_OVERFLOW.authorize(), config.getClientId(), config.getRedirectUri(), this.getRealState(config.getState())); + } + + @Override + public String getRefreshUrl(String refreshToken) { + throw new AuthException(AuthResponseStatus.UNSUPPORTED); + } + + @Override + public String getRevokeUrl(String accessToken) { + throw new AuthException(AuthResponseStatus.UNSUPPORTED); + } +}