diff --git a/README.md b/README.md
index cd1c132..825d7cb 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,7 @@
 |
 |
 |
+ ![]() |
 |
@@ -178,4 +179,4 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经
| 支付宝 | 微信 |
| :------------: | :------------: |
-|
|
|
\ No newline at end of file
+|
|
|
diff --git a/example.md b/example.md
index fb5f25c..cc87789 100644
--- a/example.md
+++ b/example.md
@@ -80,10 +80,6 @@ _注:非全部平台,部分平台可能不存在图例_
暂无
-#### 授权csdn
-
-暂无
-
#### 授权Pinterest

@@ -92,4 +88,16 @@ _注:非全部平台,部分平台可能不存在图例_

-_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_
\ No newline at end of file
+#### 授权Stack Overflow
+
+暂无
+
+#### 授权Twitter
+
+暂无
+
+#### 授权csdn
+
+暂无
+
+_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_
diff --git a/src/main/java/me/zhyd/oauth/config/AuthConfig.java b/src/main/java/me/zhyd/oauth/config/AuthConfig.java
index 8b1f7da..57b9656 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 029259f..159ce0e 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/AuthLinkedinRequest.java b/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java
index 110647f..ac48430 100644
--- a/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java
+++ b/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java
@@ -140,7 +140,7 @@ public class AuthLinkedinRequest extends AuthDefaultRequest {
private AuthToken getToken(String accessTokenUrl) {
HttpResponse response = HttpRequest.post(accessTokenUrl)
.header("Host", "www.linkedin.com")
- .header("Content-Type", "application/x-www-form-urlencoded")
+ .contentType("application/x-www-form-urlencoded")
.execute();
String accessTokenStr = response.body();
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
diff --git a/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java
index da30f93..71b2129 100644
--- a/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java
+++ b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java
@@ -14,6 +14,8 @@ import me.zhyd.oauth.url.entity.AuthUserInfoEntity;
import java.util.HashMap;
import java.util.Map;
+import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap;
+
/**
* 微软登录
*
@@ -40,12 +42,10 @@ public class AuthMicrosoftRequest extends AuthDefaultRequest {
* @return token对象
*/
private AuthToken getToken(String accessTokenUrl) {
- Map paramMap = new HashMap<>(6);
- HttpUtil.decodeParamMap(accessTokenUrl, "UTF-8").forEach(paramMap::put);
HttpResponse response = HttpRequest.post(accessTokenUrl)
.header("Host", "https://login.microsoftonline.com")
- .header("Content-Type", "application/x-www-form-urlencoded")
- .form(paramMap)
+ .contentType("application/x-www-form-urlencoded")
+ .form(parseQueryToMap(accessTokenUrl))
.execute();
String accessTokenStr = response.body();
JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr);
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 0000000..be5c186
--- /dev/null
+++ b/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java
@@ -0,0 +1,68 @@
+package me.zhyd.oauth.request;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson.JSONObject;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.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 static me.zhyd.oauth.config.AuthSource.STACK_OVERFLOW;
+import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap;
+
+/**
+ * Stack Overflow登录
+ *
+ * @author hongwei.peng (pengisgood(at)gmail(dot)com)
+ * @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(parseQueryToMap(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();
+ }
+}
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 0000000..1498e98
--- /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);
+ }
+}
diff --git a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
index 22bb699..f313704 100644
--- a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
+++ b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
@@ -1,6 +1,7 @@
package me.zhyd.oauth.utils;
import cn.hutool.core.codec.Base64;
+import cn.hutool.http.HttpUtil;
import me.zhyd.oauth.exception.AuthException;
import javax.crypto.Mac;
@@ -82,6 +83,12 @@ public class GlobalAuthUtil {
return res;
}
+ public static Map parseQueryToMap(String url) {
+ Map paramMap = new HashMap<>();
+ HttpUtil.decodeParamMap(url, "UTF-8").forEach(paramMap::put);
+ return paramMap;
+ }
+
public static boolean isHttpProtocol(String url) {
if (StringUtils.isEmpty(url)) {
return false;